93 lines
3.5 KiB
JavaScript
93 lines
3.5 KiB
JavaScript
import { dedupeMixin } from '@lion/core';
|
|
|
|
// TODO: will be moved to @Lion/core later?
|
|
|
|
/**
|
|
* @desc Why this mixin?
|
|
* - it adheres to the "Member Order Independence" web components standard:
|
|
* https://github.com/webcomponents/gold-standard/wiki/Member-Order-Independence
|
|
* - sync observers can be dependent on the outcome of the render function (or, more generically
|
|
* speaking, the light and shadow dom). This aligns with the 'updated' callback that is supported
|
|
* out of the box by LitElement, which runs after connectedCallback as well.
|
|
* - makes the propertyAccessor.`hasChanged` compatible in synchronous updates:
|
|
* `updateSync` will only be called when new value differs from old value.
|
|
* See: https://lit-element.polymer-project.org/guide/lifecycle#haschanged
|
|
* - it is a stable abstaction on top of a protected/non offical lifecycle LitElement api.
|
|
* Whenever the implementation of `_requestUpdate` changes (this happened in the past for
|
|
* `requestUpdate`) we only have to change our abstraction instead of all our components
|
|
*/
|
|
export const SyncUpdatableMixin = dedupeMixin(
|
|
superclass =>
|
|
class SyncUpdatable extends superclass {
|
|
constructor() {
|
|
super();
|
|
// Namespace for this mixin that guarantees naming clashes will not occur...
|
|
this.__SyncUpdatableNamespace = {};
|
|
}
|
|
|
|
firstUpdated(changedProperties) {
|
|
super.firstUpdated(changedProperties);
|
|
this.__SyncUpdatableNamespace.connected = true;
|
|
this.__syncUpdatableInitialize();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
this.__SyncUpdatableNamespace.connected = false;
|
|
}
|
|
|
|
/**
|
|
* Makes the propertyAccessor.`hasChanged` compatible in synchronous updates
|
|
* @param {string} name
|
|
* @param {*} oldValue
|
|
*/
|
|
static __syncUpdatableHasChanged(name, newValue, oldValue) {
|
|
const properties = this._classProperties;
|
|
if (properties.get(name) && properties.get(name).hasChanged) {
|
|
return properties.get(name).hasChanged(newValue, oldValue);
|
|
}
|
|
return newValue !== oldValue;
|
|
}
|
|
|
|
__syncUpdatableInitialize() {
|
|
const ns = this.__SyncUpdatableNamespace;
|
|
const ctor = this.constructor;
|
|
|
|
ns.initialized = true;
|
|
// Empty queue...
|
|
if (ns.queue) {
|
|
Array.from(ns.queue).forEach(name => {
|
|
if (ctor.__syncUpdatableHasChanged(name, this[name], undefined)) {
|
|
this.updateSync(name, undefined);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
_requestUpdate(name, oldValue) {
|
|
super._requestUpdate(name, oldValue);
|
|
|
|
this.__SyncUpdatableNamespace = this.__SyncUpdatableNamespace || {};
|
|
const ns = this.__SyncUpdatableNamespace;
|
|
const ctor = this.constructor;
|
|
|
|
// Before connectedCallback: queue
|
|
if (!ns.connected) {
|
|
ns.queue = ns.queue || new Set();
|
|
// Makes sure that we only initialize one time, with most up to date value
|
|
ns.queue.add(name);
|
|
} // After connectedCallback: guarded proxy to updateSync
|
|
else if (ctor.__syncUpdatableHasChanged(name, this[name], oldValue)) {
|
|
this.updateSync(name, oldValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @desc A public abstraction that has the exact same api as `_requestUpdate`.
|
|
* All code previously present in _requestUpdate can be placed in this method.
|
|
* @param {string} name
|
|
* @param {*} oldValue
|
|
*/
|
|
updateSync(name, oldValue) {} // eslint-disable-line class-methods-use-this, no-unused-vars
|
|
},
|
|
);
|