-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
168a63d
commit 939f1db
Showing
9 changed files
with
95 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,51 @@ | ||
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. | ||
|
||
import { IConverter, IDisposable, IPropertyChanged, PubSub, Result } from "chili-core"; | ||
import { IConverter, IDisposable, IPropertyChanged } from "chili-core"; | ||
|
||
export type Key = string | number | symbol; | ||
|
||
export class Binding<T extends IPropertyChanged = IPropertyChanged, K extends keyof T = any> | ||
implements IDisposable | ||
{ | ||
static #bindings = new WeakMap<IPropertyChanged, Set<Binding>>(); | ||
#cache = new Map<object, Set<Key>>(); | ||
export class Binding<T extends IPropertyChanged = IPropertyChanged> implements IDisposable { | ||
readonly #targets: [element: any, property: any][] = []; | ||
|
||
constructor( | ||
readonly dataContext: T, | ||
readonly path: K, | ||
readonly converter?: IConverter, | ||
) { | ||
this.cacheBinding(dataContext); | ||
this.dataContext.onPropertyChanged(this.onPropertyChanged); | ||
} | ||
|
||
get value() { | ||
return this.dataContext[this.path]; | ||
} | ||
|
||
private cacheBinding(dataContext: T) { | ||
if (Binding.#bindings.has(dataContext)) { | ||
Binding.#bindings.get(dataContext)!.add(this); | ||
} else { | ||
Binding.#bindings.set(dataContext, new Set([this])); | ||
} | ||
} | ||
public readonly source: T, | ||
public readonly path: keyof T, | ||
public readonly converter?: IConverter, | ||
) {} | ||
|
||
private removeBindings(dataContext: T) { | ||
if (Binding.#bindings.has(dataContext)) { | ||
Binding.#bindings.get(dataContext)!.forEach((binding) => binding.dispose()); | ||
Binding.#bindings.delete(dataContext); | ||
} | ||
bindTo<U>(element: U, property: keyof U) { | ||
this.#targets.push([element, property]); | ||
this.setValue<U>(element, property); | ||
} | ||
|
||
add<T extends object, K extends keyof T>(target: T, key: K) { | ||
if (!this.#cache.has(target)) { | ||
this.#cache.set(target, new Set()); | ||
} | ||
this.#cache.get(target)!.add(key); | ||
this.setValue(target, this.#cache.get(target)!); | ||
startObserver() { | ||
this.source.onPropertyChanged(this.#onPropertyChanged); | ||
} | ||
|
||
remove<T extends object, K extends keyof T>(target: T, key: K) { | ||
this.#cache.get(target)?.delete(key); | ||
stopObserver() { | ||
this.source.removePropertyChanged(this.#onPropertyChanged); | ||
} | ||
|
||
private onPropertyChanged = (prop: K) => { | ||
if (prop === this.path) { | ||
this.#cache.forEach((keys, target) => { | ||
this.setValue(target, keys); | ||
}); | ||
#onPropertyChanged = (property: keyof T) => { | ||
if (property === this.path) { | ||
for (let [element, property] of this.#targets) { | ||
this.setValue(element, property); | ||
} | ||
} | ||
}; | ||
|
||
private setValue<T extends object>(target: T, keys: Set<Key>) { | ||
let value: any = this.dataContext[this.path]; | ||
private setValue<U>(element: U, property: keyof U) { | ||
let value: any = this.source[this.path]; | ||
if (this.converter) { | ||
let result = this.converter.convert(value); | ||
if (!result.success) { | ||
PubSub.default.pub("showToast", "toast.converter.error"); | ||
return; | ||
throw new Error(`Cannot convert value ${value}`); | ||
} | ||
value = result.value; | ||
value = result.getValue(); | ||
} | ||
keys.forEach((key) => { | ||
let scope: any = target; | ||
if (value !== scope[key] && key in scope) scope[key] = value; | ||
}); | ||
element[property] = value; | ||
} | ||
|
||
dispose() { | ||
this.dataContext.removePropertyChanged(this.onPropertyChanged); | ||
this.#cache.clear(); | ||
dispose(): void { | ||
this.stopObserver(); | ||
this.#targets.length = 0; | ||
} | ||
} | ||
|
||
export function bind<T extends IPropertyChanged>(dataContext: T, path: keyof T, converter?: IConverter) { | ||
return new Binding(dataContext, path, converter); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. | ||
|
||
import { IConverter, IDisposable, IPropertyChanged } from "chili-core"; | ||
import { Binding } from "./binding"; | ||
|
||
export abstract class BindableElement extends HTMLElement implements IDisposable { | ||
readonly #bindings: Binding<any>[] = []; | ||
|
||
bind<T extends IPropertyChanged>(dataContext: T, path: keyof T, converter?: IConverter) { | ||
let binding = new Binding(dataContext, path, converter); | ||
this.#bindings.push(binding); | ||
return binding; | ||
} | ||
|
||
connectedCallback() { | ||
this.#bindings.forEach((binding) => binding.startObserver()); | ||
} | ||
|
||
disconnectedCallback() { | ||
this.#bindings.forEach((binding) => binding.stopObserver()); | ||
} | ||
|
||
dispose(): void { | ||
this.#bindings.forEach((binding) => binding.dispose()); | ||
this.#bindings.length = 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,5 @@ | |
|
||
export * from "./binding"; | ||
export * from "./controls"; | ||
export * from "./element"; | ||
export * from "./localize"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters