Compare commits

..

No commits in common. "aa27cc0b34491bd2a1cd4c768f2ff86242a71875" and "74de53874beaeae633080fdef0e0572b69dca32e" have entirely different histories.

2 changed files with 15 additions and 70 deletions

View file

@ -1,2 +0,0 @@
[*]
indent_style = tab

View file

@ -12,31 +12,17 @@ const target = Symbol("Proxy Target")
/* Custom Event Classes */ /* Custom Event Classes */
/**
* @typedef {Object} Change
* @property {string} property
* @property {any} from
* @property {any} to
* @property {boolean} mutation - The change happened inside the value without a new assignment
*/
/** Event fired for every change before the internal state has been updated that can be canceled. */
export class SynchronousChangeEvent extends Event { export class SynchronousChangeEvent extends Event {
/** @param {Change} change */
constructor(change) { constructor(change) {
super('synchronous', {cancelable: true}) super('synchronous', {cancelable: true})
this.change = Object.freeze(change) this.change = change
} }
} }
/** Event fired for one or more changed values after the internal state has been updated. */
export class MultiChangeEvent extends Event { export class MultiChangeEvent extends Event {
/** @type {any} */
#final #final
/** @type {any} */
#values #values
/** @param {Change[]} changes */
constructor(...changes) { constructor(...changes) {
super('change') super('change')
this.changes = changes this.changes = changes
@ -81,17 +67,17 @@ export class ValueChangeEvent extends MultiChangeEvent {
export class Observable extends EventTarget { export class Observable extends EventTarget {
#synchronous #synchronous
/** @type Change[]> */ /** @type Array<{name:string, from, to}> */
#queue #queue
#abortController = new AbortController #abortController = new AbortController
#ref = new WeakRef(this) #ref = new WeakRef(this)
get ref() { return this.#ref } get ref() { return this.#ref }
constructor({synchronous=false}={}) { observable = true
super()
Object.defineProperty(this, "observable", {value: true, configurable: false, writable: false})
constructor({synchronous}={}) {
super()
if (this.constructor === Observable) { if (this.constructor === Observable) {
throw new TypeError("Cannot instantiate abstract class") throw new TypeError("Cannot instantiate abstract class")
} }
@ -103,37 +89,24 @@ export class Observable extends EventTarget {
}) })
} }
/** proxy(prop, {get, set, ...options}={}) {
* @param {string} prop
*/
proxy(prop, {get=undefined, set=undefined, ...options}={}) {
const proxy = new ProxiedObservableValue(this, prop, options) const proxy = new ProxiedObservableValue(this, prop, options)
if (get) proxy.get = get if (get) proxy.get = get
if (set) proxy.set = set if (set) proxy.set = set
return proxy return proxy
} }
/**
* @param {string} prop
* @param {function} callback
*/
subscribe(prop, callback) { subscribe(prop, callback) {
if (!callback) return this.subscribe("value", prop)
const controller = new AbortController() const controller = new AbortController()
// @ts-ignore
this.addEventListener("change", ({final}) => { this.addEventListener("change", ({final}) => {
if (final.has(prop)) return callback(final.get(prop)) if (final.has(prop)) return callback(final.get(prop))
}, {signal: controller.signal}) }, {signal: controller.signal})
callback(this.value)
callback(this[prop])
return () => controller.abort() return () => controller.abort()
} }
/** Queues up a change event
* @param {string} property - Name of the changed property
* @param {any} from
* @param {any} to
* @param {boolean} mutation - whether a change was an assignment or a mutation (nested change)
*/
enqueue(property, from, to, mutation=false) { enqueue(property, from, to, mutation=false) {
const change = {property, from, to, mutation} const change = {property, from, to, mutation}
if (!this.dispatchEvent(new SynchronousChangeEvent(change))) return false if (!this.dispatchEvent(new SynchronousChangeEvent(change))) return false
@ -152,8 +125,7 @@ export class Observable extends EventTarget {
return true return true
} }
/** @param {any[]} _args */ emit() {
emit(..._args) {
throw new TypeError(`${this.constructor.name} did not define an 'emit' method`) throw new TypeError(`${this.constructor.name} did not define an 'emit' method`)
} }
@ -244,14 +216,6 @@ export class ObservableValue extends Observable {
} }
} }
/**
* @param {function(any):undefined} callback
* @param {function(any):undefined} _callback
*/
subscribe(callback, _callback) {
this.constructor.prototype.subscribe.call(this, "value", callback)
}
emit(...changes) { emit(...changes) {
this.dispatchEvent(new ValueChangeEvent(...changes)) this.dispatchEvent(new ValueChangeEvent(...changes))
} }
@ -397,13 +361,9 @@ export class ObservableElement extends Observable {
#value #value
#changedValue = false #changedValue = false
/** constructor(target, {get, equal, ...options}={}) {
* @param {HTMLElement} target
*/
constructor(target, {get=undefined, equal=undefined, ...options}={}) {
// @ts-ignore
super(options) super(options)
Object.defineProperty(this, "target", {value: target, configurable: false, writable: false}) this[target] = target
this.#getValue = get ?? (target => target.value) this.#getValue = get ?? (target => target.value)
this.#equal = equal ?? ((a, b) => a===b) this.#equal = equal ?? ((a, b) => a===b)
@ -425,19 +385,19 @@ export class ObservableElement extends Observable {
get value() { return this.#value } get value() { return this.#value }
update() { update() {
const current = this.#getValue(this.target) const current = this.#getValue(this[target])
if (this.#equal(this.#value, current)) return if (this.#equal(this.#value, current)) return
this.#value = current this.#value = current
if (this.synchronous) { if (this.synchronous) {
this.dispatchEvent(new ValueChangeEvent(["value", current])) this.dispatchEvent(new MultiChangeEvent(["value", current]))
} else { } else {
if (!this.#changedValue) { if (!this.#changedValue) {
queueMicrotask(() => { queueMicrotask(() => {
this.#changedValue = false this.#changedValue = false
this.dispatchEvent(new ValueChangeEvent(["value", this.#changedValue])) this.dispatchEvent(new MultiChangeEvent(["value", this.#changedValue]))
}) })
this.#changedValue = current this.#changedValue = current
} }
@ -447,31 +407,18 @@ export class ObservableElement extends Observable {
export class MapStorage extends Storage { export class MapStorage extends Storage {
#map = new Map() #map = new Map()
/**
* @param {number} index
* @return {string}
*/
key(index) { key(index) {
return [...this.#map.keys()][index] return [...this.#map.keys()][index]
} }
/**
* @param {string} keyName
* @return {any}
*/
getItem(keyName) { getItem(keyName) {
if (this.#map.has(keyName)) if (this.#map.has(keyName))
return this.#map.get(keyName) return this.#map.get(keyName)
else else
return null return null
} }
/**
* @param {string} keyName
* @param {any} keyValue
*/
setItem(keyName, keyValue) { setItem(keyName, keyValue) {
this.#map.set(keyName, String(keyValue)) this.#map.set(keyName, String(keyValue))
} }
/** @param {string} keyName */
removeItem(keyName) { removeItem(keyName) {
this.#map.delete(keyName) this.#map.delete(keyName)
} }