/* 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} superclass */ const DelegateMixinImplementation = superclass => // eslint-disable-next-line class extends superclass { constructor() { super(); /** * @type {DelegateEvent[]} * @private */ this.__eventsQueue = []; /** * @type {Object.} * @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);