202 lines
5.6 KiB
JavaScript
202 lines
5.6 KiB
JavaScript
/* eslint-disable class-methods-use-this */
|
|
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
|
|
/**
|
|
* @typedef {import('../types/DelegateMixinTypes').DelegateMixin} DelegateMixin
|
|
*/
|
|
|
|
/**
|
|
* @typedef DelegateEvent
|
|
* @property {string} type - Type of event
|
|
* @property {(event: Event) => unknown} handler - Event arguments
|
|
* @property {boolean | AddEventListenerOptions} [opts]
|
|
*/
|
|
|
|
/**
|
|
* @type {DelegateMixin}
|
|
* @param {import('@open-wc/dedupe-mixin').Constructor<import('../index').LitElement>} superclass
|
|
*/
|
|
const DelegateMixinImplementation = superclass =>
|
|
// eslint-disable-next-line
|
|
class extends superclass {
|
|
constructor() {
|
|
super();
|
|
|
|
/**
|
|
* @type {DelegateEvent[]}
|
|
* @private
|
|
*/
|
|
this.__eventsQueue = [];
|
|
|
|
/**
|
|
* @type {Object.<string,?>}
|
|
* @private
|
|
*/
|
|
this.__propertiesQueue = {};
|
|
/** @private */
|
|
this.__setupPropertyDelegation();
|
|
}
|
|
|
|
/**
|
|
* @returns {{target: Function, events: string[], methods: string[], properties: string[], attributes: string[]}}
|
|
*/
|
|
get delegations() {
|
|
return {
|
|
target: () => {},
|
|
events: [],
|
|
methods: [],
|
|
properties: [],
|
|
attributes: [],
|
|
};
|
|
}
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
this._connectDelegateMixin();
|
|
}
|
|
|
|
/** @param {import('lit-element').PropertyValues } changedProperties */
|
|
updated(changedProperties) {
|
|
super.updated(changedProperties);
|
|
this._connectDelegateMixin();
|
|
}
|
|
|
|
/**
|
|
* @param {string} type
|
|
* @param {(event: Event) => unknown} handler
|
|
* @param {boolean | AddEventListenerOptions} [opts]
|
|
*/
|
|
addEventListener(type, handler, opts) {
|
|
const delegatedEvents = this.delegations.events;
|
|
if (delegatedEvents.indexOf(type) > -1) {
|
|
if (this.delegationTarget) {
|
|
this.delegationTarget.addEventListener(type, handler, opts);
|
|
} else {
|
|
this.__eventsQueue.push({ type, handler });
|
|
}
|
|
} else {
|
|
super.addEventListener(type, handler, opts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {string} value
|
|
*/
|
|
setAttribute(name, value) {
|
|
const attributeNames = this.delegations.attributes;
|
|
if (attributeNames.indexOf(name) > -1) {
|
|
if (this.delegationTarget) {
|
|
this.delegationTarget.setAttribute(name, value);
|
|
}
|
|
super.removeAttribute(name);
|
|
} else {
|
|
super.setAttribute(name, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
removeAttribute(name) {
|
|
const attributeNames = this.delegations.attributes;
|
|
if (attributeNames.indexOf(name) > -1) {
|
|
if (this.delegationTarget) {
|
|
this.delegationTarget.removeAttribute(name);
|
|
}
|
|
}
|
|
super.removeAttribute(name);
|
|
}
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
_connectDelegateMixin() {
|
|
if (this.__connectedDelegateMixin) return;
|
|
|
|
if (!this.delegationTarget) {
|
|
this.delegationTarget = this.delegations.target();
|
|
}
|
|
|
|
if (this.delegationTarget) {
|
|
this.__emptyEventListenerQueue();
|
|
this.__emptyPropertiesQueue();
|
|
this.__initialAttributeDelegation();
|
|
|
|
this.__connectedDelegateMixin = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
__setupPropertyDelegation() {
|
|
const propertyNames = this.delegations.properties.concat(this.delegations.methods);
|
|
propertyNames.forEach(propertyName => {
|
|
Object.defineProperty(this, propertyName, {
|
|
get() {
|
|
const target = this.delegationTarget;
|
|
if (target) {
|
|
if (typeof target[propertyName] === 'function') {
|
|
return target[propertyName].bind(target);
|
|
}
|
|
return target[propertyName];
|
|
}
|
|
if (this.__propertiesQueue[propertyName]) {
|
|
return this.__propertiesQueue[propertyName];
|
|
}
|
|
// This is the moment the attribute is not delegated (and thus removed) yet.
|
|
// and the property is not set, but the attribute is (it serves as a fallback for
|
|
// __propertiesQueue).
|
|
return this.getAttribute(propertyName);
|
|
},
|
|
set(newValue) {
|
|
if (this.delegationTarget) {
|
|
const oldValue = this.delegationTarget[propertyName];
|
|
this.delegationTarget[propertyName] = newValue;
|
|
// connect with observer system if available
|
|
if (typeof this.triggerObserversFor === 'function') {
|
|
this.triggerObserversFor(propertyName, newValue, oldValue);
|
|
}
|
|
} else {
|
|
this.__propertiesQueue[propertyName] = newValue;
|
|
}
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
__initialAttributeDelegation() {
|
|
const attributeNames = this.delegations.attributes;
|
|
attributeNames.forEach(attributeName => {
|
|
const attributeValue = this.getAttribute(attributeName);
|
|
if (typeof attributeValue === 'string') {
|
|
this.delegationTarget.setAttribute(attributeName, attributeValue);
|
|
super.removeAttribute(attributeName);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
__emptyEventListenerQueue() {
|
|
this.__eventsQueue.forEach(ev => {
|
|
this.delegationTarget.addEventListener(ev.type, ev.handler, ev.opts);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
__emptyPropertiesQueue() {
|
|
Object.keys(this.__propertiesQueue).forEach(propName => {
|
|
this.delegationTarget[propName] = this.__propertiesQueue[propName];
|
|
});
|
|
}
|
|
};
|
|
|
|
export const DelegateMixin = dedupeMixin(DelegateMixinImplementation);
|