feat(core): fix types and export type definition files for core
This commit is contained in:
parent
5efd243958
commit
ec65da5da6
20 changed files with 943 additions and 703 deletions
|
|
@ -1,8 +1,3 @@
|
|||
/**
|
||||
* Info for TypeScript users:
|
||||
* For now please import types from lit-element and lit-html directly.
|
||||
*/
|
||||
|
||||
// lit-element
|
||||
export {
|
||||
css,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@
|
|||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
"files": [
|
||||
"*.d.ts",
|
||||
"*.js",
|
||||
"docs",
|
||||
"src",
|
||||
"test",
|
||||
"test-helpers",
|
||||
"translations"
|
||||
"translations",
|
||||
"types"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "../../scripts/npm-prepublish.js",
|
||||
|
|
@ -26,7 +29,7 @@
|
|||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@open-wc/dedupe-mixin": "^1.2.1",
|
||||
"@open-wc/dedupe-mixin": "^1.2.18",
|
||||
"@open-wc/scoped-elements": "^1.0.3",
|
||||
"lit-element": "^2.2.1",
|
||||
"lit-html": "^1.0.0"
|
||||
|
|
|
|||
|
|
@ -1,203 +1,177 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
/**
|
||||
* # DelegateMixin
|
||||
* Forwards defined events, methods, properties and attributes to the defined target.
|
||||
*
|
||||
* @example
|
||||
* get delegations() {
|
||||
* return {
|
||||
* ...super.delegations,
|
||||
* target: () => this.shadowRoot.getElementById('button1'),
|
||||
* events: ['click'],
|
||||
* methods: ['click'],
|
||||
* properties: ['disabled'],
|
||||
* attributes: ['disabled'],
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return html`
|
||||
* <button id="button1">with delegation</button>
|
||||
* `;
|
||||
* }
|
||||
*
|
||||
* @type {function()}
|
||||
* @polymerMixin
|
||||
* @mixinFunction
|
||||
* @typedef {import('../types/DelegateMixinTypes').DelegateMixin} DelegateMixin
|
||||
*/
|
||||
export const DelegateMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line
|
||||
class DelegateMixin extends superclass {
|
||||
constructor() {
|
||||
super();
|
||||
this.__eventsQueue = [];
|
||||
this.__propertiesQueue = {};
|
||||
this.__setupPropertyDelegation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{target: null, events: Array, methods: Array, properties: Array, attributes: Array}}
|
||||
*/
|
||||
get delegations() {
|
||||
return {
|
||||
target: null,
|
||||
events: [],
|
||||
methods: [],
|
||||
properties: [],
|
||||
attributes: [],
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @typedef DelegateEvent
|
||||
* @property {string} type - Type of event
|
||||
* @property {Array<?>} args - Event arguments
|
||||
*/
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._connectDelegateMixin();
|
||||
}
|
||||
/** @type {DelegateMixin} */
|
||||
const DelegateMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line
|
||||
class DelegateMixin extends superclass {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
updated(...args) {
|
||||
super.updated(...args);
|
||||
this._connectDelegateMixin();
|
||||
}
|
||||
/** @type {DelegateEvent[]} */
|
||||
this.__eventsQueue = [];
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Object} args
|
||||
*/
|
||||
addEventListener(type, ...args) {
|
||||
const delegatedEvents = this.delegations.events;
|
||||
if (delegatedEvents.indexOf(type) > -1) {
|
||||
if (this.delegationTarget) {
|
||||
this.delegationTarget.addEventListener(type, ...args);
|
||||
} else {
|
||||
this.__eventsQueue.push({ type, args });
|
||||
}
|
||||
/** @type {Object.<string,?>} */
|
||||
this.__propertiesQueue = {};
|
||||
this.__setupPropertyDelegation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{target: Function, events: string[], methods: string[], properties: string[], attributes: string[]}}
|
||||
*/
|
||||
get delegations() {
|
||||
return {
|
||||
target: () => {},
|
||||
events: [],
|
||||
methods: [],
|
||||
properties: [],
|
||||
attributes: [],
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._connectDelegateMixin();
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
this._connectDelegateMixin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {...Object} args
|
||||
*/
|
||||
addEventListener(type, ...args) {
|
||||
const delegatedEvents = this.delegations.events;
|
||||
if (delegatedEvents.indexOf(type) > -1) {
|
||||
if (this.delegationTarget) {
|
||||
this.delegationTarget.addEventListener(type, ...args);
|
||||
} else {
|
||||
super.addEventListener(type, ...args);
|
||||
this.__eventsQueue.push({ type, args });
|
||||
}
|
||||
} else {
|
||||
super.addEventListener(type, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
_connectDelegateMixin() {
|
||||
if (this.__connectedDelegateMixin) return;
|
||||
|
||||
if (!this.delegationTarget) {
|
||||
this.delegationTarget = this.delegations.target();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
removeAttribute(name) {
|
||||
const attributeNames = this.delegations.attributes;
|
||||
if (attributeNames.indexOf(name) > -1) {
|
||||
if (this.delegationTarget) {
|
||||
this.__emptyEventListenerQueue();
|
||||
this.__emptyPropertiesQueue();
|
||||
this.__initialAttributeDelegation();
|
||||
|
||||
this.__connectedDelegateMixin = true;
|
||||
this.delegationTarget.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
super.removeAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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];
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
__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);
|
||||
}
|
||||
if (this.__propertiesQueue[propertyName]) {
|
||||
return this.__propertiesQueue[propertyName];
|
||||
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);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
},
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
__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.args);
|
||||
});
|
||||
}
|
||||
__emptyEventListenerQueue() {
|
||||
this.__eventsQueue.forEach(ev => {
|
||||
this.delegationTarget.addEventListener(ev.type, ...ev.args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__emptyPropertiesQueue() {
|
||||
Object.keys(this.__propertiesQueue).forEach(propName => {
|
||||
this.delegationTarget[propName] = this.__propertiesQueue[propName];
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
__emptyPropertiesQueue() {
|
||||
Object.keys(this.__propertiesQueue).forEach(propName => {
|
||||
this.delegationTarget[propName] = this.__propertiesQueue[propName];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const DelegateMixin = dedupeMixin(DelegateMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -1,64 +1,67 @@
|
|||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
/**
|
||||
* #DisabledMixin
|
||||
*
|
||||
* @polymerMixin
|
||||
* @mixinFunction
|
||||
* @typedef {import('../types/DisabledMixinTypes').DisabledMixin} DisabledMixin
|
||||
*/
|
||||
export const DisabledMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class DisabledMixin extends superclass {
|
||||
static get properties() {
|
||||
return {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {DisabledMixin} */
|
||||
const DisabledMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class DisabledMixinHost extends superclass {
|
||||
static get properties() {
|
||||
return {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__requestedToBeDisabled = false;
|
||||
this.__isUserSettingDisabled = true;
|
||||
this.__restoreDisabledTo = false;
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
makeRequestToBeDisabled() {
|
||||
if (this.__requestedToBeDisabled === false) {
|
||||
this.__requestedToBeDisabled = true;
|
||||
this.__restoreDisabledTo = this.disabled;
|
||||
this.__internalSetDisabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
retractRequestToBeDisabled() {
|
||||
if (this.__requestedToBeDisabled === true) {
|
||||
this.__requestedToBeDisabled = false;
|
||||
this.__isUserSettingDisabled = true;
|
||||
|
||||
this.__restoreDisabledTo = false;
|
||||
this.disabled = false;
|
||||
this.__internalSetDisabled(this.__restoreDisabledTo);
|
||||
}
|
||||
}
|
||||
|
||||
makeRequestToBeDisabled() {
|
||||
if (this.__requestedToBeDisabled === false) {
|
||||
this.__requestedToBeDisabled = true;
|
||||
/** @param {boolean} value */
|
||||
__internalSetDisabled(value) {
|
||||
this.__isUserSettingDisabled = false;
|
||||
this.disabled = value;
|
||||
this.__isUserSettingDisabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
if (name === 'disabled') {
|
||||
if (this.__isUserSettingDisabled) {
|
||||
this.__restoreDisabledTo = this.disabled;
|
||||
}
|
||||
if (this.disabled === false && this.__requestedToBeDisabled === true) {
|
||||
this.__internalSetDisabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
retractRequestToBeDisabled() {
|
||||
if (this.__requestedToBeDisabled === true) {
|
||||
this.__requestedToBeDisabled = false;
|
||||
this.__internalSetDisabled(this.__restoreDisabledTo);
|
||||
}
|
||||
}
|
||||
|
||||
__internalSetDisabled(value) {
|
||||
this.__isUserSettingDisabled = false;
|
||||
this.disabled = value;
|
||||
this.__isUserSettingDisabled = true;
|
||||
}
|
||||
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
if (name === 'disabled') {
|
||||
if (this.__isUserSettingDisabled) {
|
||||
this.__restoreDisabledTo = this.disabled;
|
||||
}
|
||||
if (this.disabled === false && this.__requestedToBeDisabled === true) {
|
||||
this.__internalSetDisabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
export const DisabledMixin = dedupeMixin(DisabledMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -2,85 +2,91 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|||
import { DisabledMixin } from './DisabledMixin.js';
|
||||
|
||||
/**
|
||||
* #DisabledWithTabIndexMixin
|
||||
*
|
||||
* @polymerMixin
|
||||
* @mixinFunction
|
||||
* @typedef {import('../types/DisabledWithTabIndexMixinTypes').DisabledWithTabIndexMixin} DisabledWithTabIndexMixin
|
||||
*/
|
||||
export const DisabledWithTabIndexMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class DisabledWithTabIndexMixin extends DisabledMixin(superclass) {
|
||||
static get properties() {
|
||||
return {
|
||||
// we use a property here as if we use the native tabIndex we can not set a default value
|
||||
// in the constructor as it synchronously sets the attribute which is not allowed in the
|
||||
// constructor phase
|
||||
tabIndex: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
attribute: 'tabindex',
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {DisabledWithTabIndexMixin} */
|
||||
const DisabledWithTabIndexMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class DisabledWithTabIndexMixinHost extends DisabledMixin(superclass) {
|
||||
static get properties() {
|
||||
return {
|
||||
// we use a property here as if we use the native tabIndex we can not set a default value
|
||||
// in the constructor as it synchronously sets the attribute which is not allowed in the
|
||||
// constructor phase
|
||||
tabIndex: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
attribute: 'tabindex',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__isUserSettingTabIndex = true;
|
||||
this.__restoreTabIndexTo = 0;
|
||||
this.__internalSetTabIndex(0);
|
||||
}
|
||||
|
||||
makeRequestToBeDisabled() {
|
||||
super.makeRequestToBeDisabled();
|
||||
if (this.__requestedToBeDisabled === false && this.tabIndex != null) {
|
||||
this.__restoreTabIndexTo = this.tabIndex;
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__isUserSettingTabIndex = true;
|
||||
|
||||
this.__restoreTabIndexTo = 0;
|
||||
this.__internalSetTabIndex(0);
|
||||
retractRequestToBeDisabled() {
|
||||
super.retractRequestToBeDisabled();
|
||||
if (this.__requestedToBeDisabled === true) {
|
||||
this.__internalSetTabIndex(this.__restoreTabIndexTo);
|
||||
}
|
||||
}
|
||||
|
||||
makeRequestToBeDisabled() {
|
||||
super.makeRequestToBeDisabled();
|
||||
if (this.__requestedToBeDisabled === false) {
|
||||
this.__restoreTabIndexTo = this.tabIndex;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
__internalSetTabIndex(value) {
|
||||
this.__isUserSettingTabIndex = false;
|
||||
this.tabIndex = value;
|
||||
this.__isUserSettingTabIndex = true;
|
||||
}
|
||||
|
||||
retractRequestToBeDisabled() {
|
||||
super.retractRequestToBeDisabled();
|
||||
if (this.__requestedToBeDisabled === true) {
|
||||
/**
|
||||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
|
||||
if (name === 'disabled') {
|
||||
if (this.disabled) {
|
||||
this.__internalSetTabIndex(-1);
|
||||
} else {
|
||||
this.__internalSetTabIndex(this.__restoreTabIndexTo);
|
||||
}
|
||||
}
|
||||
|
||||
__internalSetTabIndex(value) {
|
||||
this.__isUserSettingTabIndex = false;
|
||||
this.tabIndex = value;
|
||||
this.__isUserSettingTabIndex = true;
|
||||
}
|
||||
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
|
||||
if (name === 'disabled') {
|
||||
if (this.disabled) {
|
||||
this.__internalSetTabIndex(-1);
|
||||
} else {
|
||||
this.__internalSetTabIndex(this.__restoreTabIndexTo);
|
||||
}
|
||||
if (name === 'tabIndex') {
|
||||
if (this.__isUserSettingTabIndex && this.tabIndex != null) {
|
||||
this.__restoreTabIndexTo = this.tabIndex;
|
||||
}
|
||||
|
||||
if (name === 'tabIndex') {
|
||||
if (this.__isUserSettingTabIndex) {
|
||||
this.__restoreTabIndexTo = this.tabIndex;
|
||||
}
|
||||
|
||||
if (this.tabIndex !== -1 && this.__requestedToBeDisabled === true) {
|
||||
this.__internalSetTabIndex(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// for ShadyDom the timing is a little different and we need to make sure
|
||||
// the tabindex gets correctly updated here
|
||||
if (this.disabled) {
|
||||
if (this.tabIndex !== -1 && this.__requestedToBeDisabled === true) {
|
||||
this.__internalSetTabIndex(-1);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
// for ShadyDom the timing is a little different and we need to make sure
|
||||
// the tabindex gets correctly updated here
|
||||
if (this.disabled) {
|
||||
this.__internalSetTabIndex(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const DisabledWithTabIndexMixin = dedupeMixin(DisabledWithTabIndexMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
export class LionSingleton {
|
||||
/**
|
||||
* @param {function()} mixin
|
||||
* @param {function} mixin
|
||||
*/
|
||||
static addInstanceMixin(mixin) {
|
||||
if (!this.__instanceMixins) {
|
||||
/** @type {function[]} */
|
||||
this.__instanceMixins = [];
|
||||
}
|
||||
this.__instanceMixins.push(mixin);
|
||||
|
|
@ -26,6 +27,8 @@ export class LionSingleton {
|
|||
Klass = mixin(Klass);
|
||||
});
|
||||
}
|
||||
// Ignoring, because it's up to the extension layer to accept arguments in its constructor
|
||||
// @ts-ignore-next-line
|
||||
return new Klass(...args);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,78 +1,59 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
/**
|
||||
* # SlotMixin
|
||||
* `SlotMixin`, when attached to the DOM it creates content for defined slots in the Light DOM.
|
||||
* The content element is created using a factory function and is assigned a slot name from the key.
|
||||
* Existing slot content is not overridden.
|
||||
*
|
||||
* The purpose is to have the default content in the Light DOM rather than hidden in Shadow DOM
|
||||
* like default slot content works natively.
|
||||
*
|
||||
* @example
|
||||
* get slots() {
|
||||
* return {
|
||||
* ...super.slots,
|
||||
* // appends <div slot="foo"></div> to the Light DOM of this element
|
||||
* foo: () => document.createElement('div'),
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* @type {function()}
|
||||
* @polymerMixin
|
||||
* @mixinFunction
|
||||
* @typedef {import('../types/SlotMixinTypes').SlotMixin} SlotMixin
|
||||
* @typedef {import('../types/SlotMixinTypes').SlotsMap} SlotsMap
|
||||
*/
|
||||
export const SlotMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line no-unused-vars, no-shadow
|
||||
class SlotMixin extends superclass {
|
||||
/**
|
||||
* @returns {{}}
|
||||
*/
|
||||
get slots() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__privateSlots = new Set(null);
|
||||
}
|
||||
/** @type {SlotMixin} */
|
||||
const SlotMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line no-unused-vars, no-shadow
|
||||
class SlotMixinHost extends superclass {
|
||||
/**
|
||||
* @return {SlotsMap}
|
||||
*/
|
||||
get slots() {
|
||||
return {};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._connectSlotMixin();
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.__privateSlots = new Set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
_connectSlotMixin() {
|
||||
if (!this.__isConnectedSlotMixin) {
|
||||
Object.keys(this.slots).forEach(slotName => {
|
||||
if (!this.querySelector(`[slot=${slotName}]`)) {
|
||||
const slotFactory = this.slots[slotName];
|
||||
const slotContent = slotFactory();
|
||||
if (slotContent instanceof Element) {
|
||||
slotContent.setAttribute('slot', slotName);
|
||||
this.appendChild(slotContent);
|
||||
this.__privateSlots.add(slotName);
|
||||
} // ignore non-elements to enable conditional slots
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
this._connectSlotMixin();
|
||||
}
|
||||
|
||||
_connectSlotMixin() {
|
||||
if (!this.__isConnectedSlotMixin) {
|
||||
Object.keys(this.slots).forEach(slotName => {
|
||||
if (!this.querySelector(`[slot=${slotName}]`)) {
|
||||
const slotFactory = this.slots[slotName];
|
||||
const slotContent = slotFactory();
|
||||
// ignore non-elements to enable conditional slots
|
||||
if (slotContent instanceof Element) {
|
||||
slotContent.setAttribute('slot', slotName);
|
||||
this.appendChild(slotContent);
|
||||
this.__privateSlots.add(slotName);
|
||||
}
|
||||
});
|
||||
this.__isConnectedSlotMixin = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.__isConnectedSlotMixin = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {string} slotName Name of the slot
|
||||
* @return {boolean} true if given slot name been created by SlotMixin
|
||||
*/
|
||||
_isPrivateSlot(slotName) {
|
||||
return this.__privateSlots.has(slotName);
|
||||
}
|
||||
},
|
||||
);
|
||||
/**
|
||||
* @param {string} slotName Name of the slot
|
||||
* @return {boolean} true if given slot name been created by SlotMixin
|
||||
*/
|
||||
_isPrivateSlot(slotName) {
|
||||
return this.__privateSlots.has(slotName);
|
||||
}
|
||||
};
|
||||
|
||||
export const SlotMixin = dedupeMixin(SlotMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -1,59 +1,78 @@
|
|||
/* global ShadyCSS */
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export const UpdateStylesMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class UpdateStylesMixin extends superclass {
|
||||
/**
|
||||
* @example
|
||||
* <my-element>
|
||||
* <style>
|
||||
* :host {
|
||||
* color: var(--foo);
|
||||
* }
|
||||
* </style>
|
||||
* </my-element>
|
||||
*
|
||||
* $0.updateStyles({'background': 'orange', '--foo': '#fff'})
|
||||
* Chrome, Firefox: <my-element style="background: orange; --foo: bar;">
|
||||
* IE: <my-element>
|
||||
* => to head: <style>color: #fff</style>
|
||||
*
|
||||
* @param {Object} updateStyles
|
||||
*/
|
||||
updateStyles(updateStyles) {
|
||||
const styleString = this.getAttribute('style') || this.getAttribute('data-style') || '';
|
||||
const currentStyles = styleString.split(';').reduce((acc, stylePair) => {
|
||||
const parts = stylePair.split(':');
|
||||
if (parts.length === 2) {
|
||||
/* eslint-disable-next-line prefer-destructuring */
|
||||
acc[parts[0]] = parts[1];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
/**
|
||||
* @typedef {import('../types/UpdateStylesMixinTypes').UpdateStylesMixin} UpdateStylesMixin
|
||||
* @typedef {import('../types/UpdateStylesMixinTypes').StylesMap} StylesMap
|
||||
*/
|
||||
|
||||
const newStyles = { ...currentStyles, ...updateStyles };
|
||||
let newStylesString = '';
|
||||
if (typeof ShadyCSS === 'object' && !ShadyCSS.nativeShadow) {
|
||||
// No ShadowDOM => IE, Edge
|
||||
const newCssVariablesObj = {};
|
||||
Object.keys(newStyles).forEach(key => {
|
||||
if (key.indexOf('--') === -1) {
|
||||
newStylesString += `${key}:${newStyles[key]};`;
|
||||
} else {
|
||||
newCssVariablesObj[key] = newStyles[key];
|
||||
}
|
||||
});
|
||||
this.setAttribute('style', newStylesString);
|
||||
ShadyCSS.styleSubtree(this, newCssVariablesObj);
|
||||
} else {
|
||||
// has shadowdom => Chrome, Firefox, Safari
|
||||
Object.keys(newStyles).forEach(key => {
|
||||
newStylesString += `${key}: ${newStyles[key]};`;
|
||||
});
|
||||
this.setAttribute('style', newStylesString);
|
||||
/** @type {UpdateStylesMixin} */
|
||||
const UpdateStylesMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
class UpdateStylesMixinHost extends superclass {
|
||||
/**
|
||||
* @example
|
||||
* <my-element>
|
||||
* <style>
|
||||
* :host {
|
||||
* color: var(--foo);
|
||||
* }
|
||||
* </style>
|
||||
* </my-element>
|
||||
*
|
||||
* $0.updateStyles({'background': 'orange', '--foo': '#fff'})
|
||||
* Chrome, Firefox: <my-element style="background: orange; --foo: bar;">
|
||||
* IE: <my-element>
|
||||
* => to head: <style>color: #fff</style>
|
||||
*
|
||||
* @param {StylesMap} updateStyles
|
||||
*/
|
||||
updateStyles(updateStyles) {
|
||||
const styleString = this.getAttribute('style') || this.getAttribute('data-style') || '';
|
||||
|
||||
/**
|
||||
* reducer function
|
||||
* @param {Object.<string, string>} acc
|
||||
* @param {string} stylePair
|
||||
*/
|
||||
const reducer = (acc, stylePair) => {
|
||||
/** @type {Array.<string>} */
|
||||
const parts = stylePair.split(':');
|
||||
if (parts.length === 2) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
acc[parts[0]] = parts[1];
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
const currentStyles = styleString.split(';').reduce(reducer, {});
|
||||
|
||||
const newStyles = { ...currentStyles, ...updateStyles };
|
||||
let newStylesString = '';
|
||||
// @ts-ignore not sure how to type ShadyCSS..
|
||||
if (typeof ShadyCSS === 'object' && !ShadyCSS.nativeShadow) {
|
||||
// No ShadowDOM => IE, Edge
|
||||
|
||||
/** @type {Object.<string, string>} */
|
||||
const newCssVariablesObj = {};
|
||||
|
||||
Object.keys(newStyles).forEach(key => {
|
||||
if (key.indexOf('--') === -1) {
|
||||
newStylesString += `${key}:${newStyles[key]};`;
|
||||
} else {
|
||||
newCssVariablesObj[key] = newStyles[key];
|
||||
}
|
||||
});
|
||||
this.setAttribute('style', newStylesString);
|
||||
// @ts-ignore not sure how to type ShadyCSS..
|
||||
ShadyCSS.styleSubtree(this, newCssVariablesObj);
|
||||
} else {
|
||||
// has shadowdom => Chrome, Firefox, Safari
|
||||
Object.keys(newStyles).forEach(key => {
|
||||
newStylesString += `${key}: ${newStyles[key]};`;
|
||||
});
|
||||
this.setAttribute('style', newStylesString);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const UpdateStylesMixin = dedupeMixin(UpdateStylesMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ if (typeof window.KeyboardEvent !== 'function') {
|
|||
const event = KeyboardEvent.prototype;
|
||||
const descriptor = Object.getOwnPropertyDescriptor(event, 'key');
|
||||
if (descriptor) {
|
||||
/** @type {Object.<string, string>} */
|
||||
const keys = {
|
||||
Win: 'Meta',
|
||||
Scroll: 'ScrollLock',
|
||||
|
|
@ -26,10 +27,13 @@ if (typeof window.KeyboardEvent !== 'function') {
|
|||
Object.defineProperty(event, 'key', {
|
||||
// eslint-disable-next-line object-shorthand, func-names
|
||||
get: function () {
|
||||
const key = descriptor.get.call(this);
|
||||
if (descriptor.get) {
|
||||
const key = descriptor.get.call(this);
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return keys.hasOwnProperty(key) ? keys[key] : key;
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return keys.hasOwnProperty(key) ? keys[key] : key;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
events: ['click'],
|
||||
};
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ describe('DelegateMixin', () => {
|
|||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
const cb = sinon.spy();
|
||||
element.addEventListener('click', cb);
|
||||
element.shadowRoot.getElementById('button1').click();
|
||||
element.shadowRoot?.getElementById('button1')?.click();
|
||||
expect(cb.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
events: ['click'],
|
||||
};
|
||||
}
|
||||
|
|
@ -47,12 +47,12 @@ describe('DelegateMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = document.createElement(tag);
|
||||
const element = /** @type {LitElement} */ (document.createElement(tag));
|
||||
const cb = sinon.spy();
|
||||
element.addEventListener('click', cb);
|
||||
document.body.appendChild(element);
|
||||
await element.updateComplete;
|
||||
element.shadowRoot.getElementById('button1').click();
|
||||
element.shadowRoot?.getElementById('button1')?.click();
|
||||
expect(cb.callCount).to.equal(1);
|
||||
|
||||
// cleanup
|
||||
|
|
@ -80,37 +80,37 @@ describe('DelegateMixin', () => {
|
|||
},
|
||||
);
|
||||
|
||||
const element = await fixture(`
|
||||
<${tag}><button slot="button">click me</button></${tag}>`);
|
||||
const element = await fixture(`<${tag}><button slot="button">click me</button></${tag}>`);
|
||||
const cb = sinon.spy();
|
||||
element.addEventListener('click', cb);
|
||||
Array.from(element.children)
|
||||
.find(child => child.slot === 'button')
|
||||
.click();
|
||||
const childEl = /** @type {HTMLElement} */ (Array.from(element.children)?.find(
|
||||
child => child.slot === 'button',
|
||||
));
|
||||
childEl?.click();
|
||||
expect(cb.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('will still support other events', async () => {
|
||||
const tag = defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
events: ['click'],
|
||||
};
|
||||
}
|
||||
class FooDelegate extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
events: ['click'],
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<button id="button1">with delegation</button>`;
|
||||
}
|
||||
render() {
|
||||
return html`<button id="button1">with delegation</button>`;
|
||||
}
|
||||
|
||||
foo() {
|
||||
this.dispatchEvent(new CustomEvent('foo-event', { bubbles: true, composed: true }));
|
||||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
foo() {
|
||||
this.dispatchEvent(new CustomEvent('foo-event', { bubbles: true, composed: true }));
|
||||
}
|
||||
}
|
||||
|
||||
const tag = defineCE(FooDelegate);
|
||||
const element = /** @type {FooDelegate} */ (await fixture(`<${tag}></${tag}>`));
|
||||
const cb = sinon.spy();
|
||||
element.addEventListener('foo-event', cb);
|
||||
element.foo();
|
||||
|
|
@ -123,7 +123,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
methods: ['click'],
|
||||
};
|
||||
}
|
||||
|
|
@ -133,9 +133,9 @@ describe('DelegateMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
const element = /** @type {HTMLElement} */ (await fixture(`<${tag}></${tag}>`));
|
||||
const cb = sinon.spy();
|
||||
element.shadowRoot.getElementById('button1').addEventListener('click', cb);
|
||||
element.shadowRoot?.getElementById('button1')?.addEventListener('click', cb);
|
||||
element.click();
|
||||
expect(cb.callCount).to.equal(1);
|
||||
});
|
||||
|
|
@ -147,56 +147,69 @@ describe('DelegateMixin', () => {
|
|||
this.foo = { a: 'a', b: 'b' };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?} a
|
||||
* @param {?} b
|
||||
*/
|
||||
setFooAandB(a, b) {
|
||||
this.foo.a = a;
|
||||
this.foo.b = b;
|
||||
}
|
||||
}
|
||||
customElements.define('delegate-argument-sub', DelegateArgumentSub);
|
||||
const tag = defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('sub'),
|
||||
methods: ['setFooAandB'],
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<delegate-argument-sub id="sub"></delegate-argument-sub>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
class DelegateArgumentParent extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot?.getElementById('sub'),
|
||||
methods: ['setFooAandB'],
|
||||
};
|
||||
}
|
||||
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
element.disabled = true;
|
||||
render() {
|
||||
return html`<delegate-argument-sub id="sub"></delegate-argument-sub>`;
|
||||
}
|
||||
}
|
||||
const tag = defineCE(DelegateArgumentParent);
|
||||
|
||||
const element = /** @type {DelegateArgumentParent} */ (await fixture(`<${tag}></${tag}>`));
|
||||
|
||||
// @ts-ignore because this method, even though it doesn't exist on the parent, gets delegated through delegations to the child, where it does exist!
|
||||
element.setFooAandB('newA', 'newB');
|
||||
expect(element.shadowRoot.getElementById('sub').foo.a).to.equal('newA');
|
||||
expect(element.shadowRoot.getElementById('sub').foo.b).to.equal('newB');
|
||||
|
||||
const sub = /** @type {DelegateArgumentSub} */ (element.shadowRoot?.getElementById('sub'));
|
||||
expect(sub.foo.a).to.equal('newA');
|
||||
expect(sub.foo.b).to.equal('newB');
|
||||
});
|
||||
|
||||
it('will set delegated properties', async () => {
|
||||
const tag = defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
properties: ['disabled'],
|
||||
};
|
||||
}
|
||||
class PropDelegate extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
properties: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<button id="button1">with delegation</button>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
render() {
|
||||
return html`<button id="button1">with delegation</button>`;
|
||||
}
|
||||
}
|
||||
const tag = defineCE(PropDelegate);
|
||||
const element = /** @type {PropDelegate} */ (await fixture(`<${tag}></${tag}>`));
|
||||
|
||||
// @ts-ignore ignoring this one, because disabled is delegated through target so it indeed does not inherently exist on the div element
|
||||
element.disabled = true;
|
||||
|
||||
await element.updateComplete;
|
||||
expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true);
|
||||
expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true);
|
||||
|
||||
/** @typedef {Object.<string,boolean>} Btn */
|
||||
/** @typedef {Btn & HTMLElement} DelegatedBtn */
|
||||
const btn = /** @type {DelegatedBtn} */ (element.shadowRoot?.getElementById('button1'));
|
||||
expect(btn?.disabled).to.equal(true);
|
||||
expect(btn?.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
|
||||
it('delegates properties before delegation target is attached to DOM', async () => {
|
||||
|
|
@ -205,7 +218,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
properties: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
|
@ -215,12 +228,18 @@ describe('DelegateMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = document.createElement(tag);
|
||||
/** @typedef {Object.<string,boolean>} Btn */
|
||||
/** @typedef {Btn & LitElement} DelegatedEl */
|
||||
const element = /** @type {DelegatedEl} */ (document.createElement(tag));
|
||||
|
||||
element.disabled = true;
|
||||
document.body.appendChild(element);
|
||||
await element.updateComplete;
|
||||
expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true);
|
||||
|
||||
/** @typedef {Btn & HTMLElement} DelegatedBtn */
|
||||
const btn = /** @type {DelegatedBtn} */ (element.shadowRoot?.getElementById('button1'));
|
||||
|
||||
expect(btn?.disabled).to.equal(true);
|
||||
// cleanup
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
|
@ -231,7 +250,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
attributes: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
|
@ -241,11 +260,11 @@ describe('DelegateMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
const element = /** @type {LitElement} */ (await fixture(`<${tag}></${tag}>`));
|
||||
element.setAttribute('disabled', '');
|
||||
await element.updateComplete;
|
||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
||||
expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true);
|
||||
expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
|
||||
it('will read inital attributes', async () => {
|
||||
|
|
@ -254,7 +273,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
attributes: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
|
@ -266,7 +285,7 @@ describe('DelegateMixin', () => {
|
|||
);
|
||||
const element = await fixture(`<${tag} disabled></${tag}>`);
|
||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
||||
expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true);
|
||||
expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
|
||||
it('will delegate removeAttribute', async () => {
|
||||
|
|
@ -275,7 +294,7 @@ describe('DelegateMixin', () => {
|
|||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.getElementById('button1'),
|
||||
target: () => this.shadowRoot?.getElementById('button1'),
|
||||
attributes: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
|
@ -285,135 +304,146 @@ describe('DelegateMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag} disabled></${tag}>`);
|
||||
element.removeAttribute('disabled', '');
|
||||
const element = /** @type {LitElement} */ (await fixture(`<${tag} disabled></${tag}>`));
|
||||
element.removeAttribute('disabled');
|
||||
await element.updateComplete;
|
||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
||||
expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(false);
|
||||
expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(false);
|
||||
});
|
||||
|
||||
it('respects user defined values for delegated attributes and properties', async () => {
|
||||
const tag = defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
// this just means itś config is set to the queue when called before connectedCallback
|
||||
target: () => this.scheduledElement,
|
||||
attributes: ['type'],
|
||||
properties: ['type'],
|
||||
};
|
||||
}
|
||||
class ScheduledElement extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
// this just means itś config is set to the queue when called before connectedCallback
|
||||
target: () => this.scheduledElement,
|
||||
attributes: ['type'],
|
||||
properties: ['type'],
|
||||
};
|
||||
}
|
||||
|
||||
get scheduledElement() {
|
||||
return this.querySelector('input');
|
||||
}
|
||||
get scheduledElement() {
|
||||
return this.querySelector('input');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.type = 'email'; // 1. here we set the delegated prop and it should be scheduled
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.type = 'email'; // 1. here we set the delegated prop and it should be scheduled
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// 2. this is where we add teh delegation target (so after 1)
|
||||
this.appendChild(document.createElement('input'));
|
||||
super.connectedCallback(); // let the DelegateMixin do its work
|
||||
}
|
||||
},
|
||||
);
|
||||
connectedCallback() {
|
||||
// 2. this is where we add teh delegation target (so after 1)
|
||||
this.appendChild(document.createElement('input'));
|
||||
super.connectedCallback(); // let the DelegateMixin do its work
|
||||
}
|
||||
}
|
||||
|
||||
const tag = defineCE(ScheduledElement);
|
||||
const tagName = unsafeStatic(tag);
|
||||
|
||||
// Here, the Application Developerd tries to set the type via attribute
|
||||
const elementAttr = await fixture(`<${tag} type="radio"></${tag}>`);
|
||||
expect(elementAttr.scheduledElement.type).to.equal('radio');
|
||||
const elementAttr = /** @type {ScheduledElement} */ (await fixture(
|
||||
`<${tag} type="radio"></${tag}>`,
|
||||
));
|
||||
expect(elementAttr.scheduledElement?.type).to.equal('radio');
|
||||
// Here, the Application Developer tries to set the type via property
|
||||
const elementProp = await fixture(html`<${tagName} .type=${'radio'}></${tagName}>`);
|
||||
expect(elementProp.scheduledElement.type).to.equal('radio');
|
||||
const elementProp = /** @type {ScheduledElement} */ (await fixture(
|
||||
html`<${tagName} .type=${'radio'}></${tagName}>`,
|
||||
));
|
||||
expect(elementProp.scheduledElement?.type).to.equal('radio');
|
||||
});
|
||||
|
||||
it(`uses attribute value as a fallback for delegated property getter
|
||||
when property not set by user and delegationTarget not ready`, async () => {
|
||||
const tag = defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.delegatedEl,
|
||||
properties: ['type'],
|
||||
attributes: ['type'],
|
||||
};
|
||||
}
|
||||
class FallbackEl extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.delegatedEl,
|
||||
properties: ['type'],
|
||||
attributes: ['type'],
|
||||
};
|
||||
}
|
||||
|
||||
get delegatedEl() {
|
||||
// returns null, so we can test that "cached" attr is used as fallback
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag} type="radio"></${tag}>`);
|
||||
get delegatedEl() {
|
||||
// returns null, so we can test that "cached" attr is used as fallback
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const tag = defineCE(FallbackEl);
|
||||
const element = /** @type {FallbackEl} */ (await fixture(`<${tag} type="radio"></${tag}>`));
|
||||
expect(element.delegatedEl).to.equal(null);
|
||||
// @ts-ignore ignoring this one, because type is delegated through target so it indeed does not inherently exist on the div element
|
||||
expect(element.type).to.equal('radio'); // value retrieved from host instead of delegatedTarget
|
||||
});
|
||||
|
||||
it('works with connectedCallback', async () => {
|
||||
const tag = await defineCE(
|
||||
class extends DelegateMixin(HTMLElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}><div></div></${tag}>`);
|
||||
class ConnectedElement extends DelegateMixin(HTMLElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
}
|
||||
const tag = await defineCE(ConnectedElement);
|
||||
const element = /** @type {ConnectedElement} */ (await fixture(`<${tag}><div></div></${tag}>`));
|
||||
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
element.foo = 'new';
|
||||
expect(element.querySelector('div').foo).to.equal('new');
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
expect(element.querySelector('div')?.foo).to.equal('new');
|
||||
});
|
||||
|
||||
it('works with shadow dom', async () => {
|
||||
const tag = await defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
class A extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.shadowRoot?.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div></div>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
render() {
|
||||
return html`<div></div>`;
|
||||
}
|
||||
}
|
||||
const tag = await defineCE(A);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
element.foo = 'new';
|
||||
expect(element.shadowRoot.querySelector('div').foo).to.equal('new');
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
expect(element.shadowRoot?.querySelector('div')?.foo).to.equal('new');
|
||||
});
|
||||
|
||||
it('works with light dom', async () => {
|
||||
const tag = await defineCE(
|
||||
class extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
class A extends DelegateMixin(LitElement) {
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
target: () => this.querySelector('div'),
|
||||
properties: ['foo'],
|
||||
};
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div></div>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
render() {
|
||||
return html`<div></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
const tag = await defineCE(A);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
element.foo = 'new';
|
||||
expect(element.querySelector('div').foo).to.equal('new');
|
||||
// @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element
|
||||
expect(element.querySelector('div')?.foo).to.equal('new');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,21 +3,26 @@ import { LitElement } from '../index.js';
|
|||
import { DisabledMixin } from '../src/DisabledMixin.js';
|
||||
|
||||
describe('DisabledMixin', () => {
|
||||
class CanBeDisabled extends DisabledMixin(LitElement) {}
|
||||
before(() => {
|
||||
class CanBeDisabled extends DisabledMixin(LitElement) {}
|
||||
customElements.define('can-be-disabled', CanBeDisabled);
|
||||
});
|
||||
|
||||
it('reflects disabled to attribute', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
expect(el.hasAttribute('disabled')).to.be.false;
|
||||
el.makeRequestToBeDisabled();
|
||||
el.disabled = true;
|
||||
await el.updateComplete;
|
||||
expect(el.hasAttribute('disabled')).to.be.true;
|
||||
});
|
||||
|
||||
it('can be requested to be disabled', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
await el.updateComplete;
|
||||
|
|
@ -25,7 +30,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will not allow to become enabled after makeRequestToBeDisabled()', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
|
||||
|
|
@ -34,14 +41,18 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will stay disabled after retractRequestToBeDisabled() if it was disabled before', async () => {
|
||||
const el = await fixture(html`<can-be-disabled disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
});
|
||||
|
||||
it('will become enabled after retractRequestToBeDisabled() if it was enabled before', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
@ -49,7 +60,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('may allow multiple calls to makeRequestToBeDisabled()', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
el.makeRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
@ -57,7 +70,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will restore last state after retractRequestToBeDisabled()', async () => {
|
||||
const el = await fixture(html`<can-be-disabled></can-be-disabled>`);
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
el.makeRequestToBeDisabled();
|
||||
el.disabled = true;
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@ import { LitElement } from '../index.js';
|
|||
import { DisabledWithTabIndexMixin } from '../src/DisabledWithTabIndexMixin.js';
|
||||
|
||||
describe('DisabledWithTabIndexMixin', () => {
|
||||
class WithTabIndex extends DisabledWithTabIndexMixin(LitElement) {}
|
||||
before(() => {
|
||||
class WithTabIndex extends DisabledWithTabIndexMixin(LitElement) {}
|
||||
customElements.define('can-be-disabled-with-tab-index', WithTabIndex);
|
||||
});
|
||||
|
||||
it('has an initial tabIndex of 0', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
expect(el.tabIndex).to.equal(0);
|
||||
expect(el.getAttribute('tabindex')).to.equal('0');
|
||||
});
|
||||
|
||||
it('sets tabIndex to -1 if disabled', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
el.disabled = true;
|
||||
expect(el.tabIndex).to.equal(-1);
|
||||
await el.updateComplete;
|
||||
|
|
@ -28,9 +28,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('disabled does not override user provided tabindex', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5" disabled></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
|
|
@ -38,9 +38,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('can be disabled imperatively', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
|
||||
el.disabled = false;
|
||||
|
|
@ -55,9 +55,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('will not allow to change tabIndex after makeRequestToBeDisabled()', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
el.makeRequestToBeDisabled();
|
||||
|
||||
el.tabIndex = 5;
|
||||
|
|
@ -67,9 +67,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('will restore last tabIndex after retractRequestToBeDisabled()', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5"></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.tabIndex).to.equal(-1);
|
||||
await el.updateComplete;
|
||||
|
|
@ -96,9 +96,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('may allow multiple calls to retractRequestToBeDisabled', async () => {
|
||||
const el = await fixture(html`
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`);
|
||||
`));
|
||||
el.retractRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ describe('LionSingleton', () => {
|
|||
|
||||
it('supports parameters for .getInstance(foo, bar)', async () => {
|
||||
class MySingleton extends LionSingleton {
|
||||
/**
|
||||
*
|
||||
* @param {string} foo
|
||||
* @param {string} bar
|
||||
*/
|
||||
constructor(foo, bar) {
|
||||
super();
|
||||
this.foo = foo;
|
||||
|
|
@ -59,6 +64,7 @@ describe('LionSingleton', () => {
|
|||
});
|
||||
|
||||
it('can at any time add mixins via .addInstanceMixin()', () => {
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
const MyMixin = superclass =>
|
||||
class extends superclass {
|
||||
constructor() {
|
||||
|
|
@ -72,6 +78,7 @@ describe('LionSingleton', () => {
|
|||
const mySingleton = MySingleton.getInstance();
|
||||
expect(mySingleton.myMixin).to.be.true;
|
||||
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
const OtherMixin = superclass =>
|
||||
class extends superclass {
|
||||
constructor() {
|
||||
|
|
@ -89,6 +96,7 @@ describe('LionSingleton', () => {
|
|||
});
|
||||
|
||||
it('can provide new instances (with applied Mixins) via .getNewInstance()', async () => {
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
const MyMixin = superclass =>
|
||||
class extends superclass {
|
||||
constructor() {
|
||||
|
|
@ -100,13 +108,19 @@ describe('LionSingleton', () => {
|
|||
|
||||
MySingleton.addInstanceMixin(MyMixin);
|
||||
const singletonOne = MySingleton.getNewInstance();
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
singletonOne.one = true;
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
expect(singletonOne.myMixin).to.be.true;
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
expect(singletonOne.one).to.be.true;
|
||||
|
||||
const singletonTwo = MySingleton.getNewInstance();
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
expect(singletonTwo.myMixin).to.be.true;
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
expect(singletonTwo.one).to.be.undefined;
|
||||
// @ts-ignore because we're getting rid of LionSingleton altogether
|
||||
expect(singletonOne.one).to.be.true; // to be sure
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ describe('SlotMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
expect(element.children[0].slot).to.equal('feedback');
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
expect(el.children[0].slot).to.equal('feedback');
|
||||
});
|
||||
|
||||
it('does not override user provided slots', async () => {
|
||||
|
|
@ -31,7 +31,7 @@ describe('SlotMixin', () => {
|
|||
);
|
||||
const el = await fixture(`<${tag}><p slot="feedback">user-content</p></${tag}>`);
|
||||
expect(el.children[0].tagName).to.equal('P');
|
||||
expect(el.children[0].innerText).to.equal('user-content');
|
||||
expect(/** @type HTMLParagraphElement */ (el.children[0]).innerText).to.equal('user-content');
|
||||
});
|
||||
|
||||
it('supports complex dom trees as element', async () => {
|
||||
|
|
@ -57,10 +57,12 @@ describe('SlotMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const element = await fixture(`<${tag}></${tag}>`);
|
||||
expect(element.children[0].slot).to.equal('feedback');
|
||||
expect(element.children[0].getAttribute('foo')).to.equal('bar');
|
||||
expect(element.children[0].children[0].innerText).to.equal('cat');
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
expect(el.children[0].slot).to.equal('feedback');
|
||||
expect(el.children[0].getAttribute('foo')).to.equal('bar');
|
||||
expect(/** @type HTMLParagraphElement */ (el.children[0].children[0]).innerText).to.equal(
|
||||
'cat',
|
||||
);
|
||||
});
|
||||
|
||||
it('supports conditional slots', async () => {
|
||||
|
|
@ -82,35 +84,37 @@ describe('SlotMixin', () => {
|
|||
}
|
||||
},
|
||||
);
|
||||
const elementSlot = await fixture(`<${tag}><${tag}>`);
|
||||
expect(elementSlot.querySelector('#someSlot')).to.exist;
|
||||
const elSlot = await fixture(`<${tag}><${tag}>`);
|
||||
expect(elSlot.querySelector('#someSlot')).to.exist;
|
||||
renderSlot = false;
|
||||
const elementNoSlot = await fixture(`<${tag}><${tag}>`);
|
||||
expect(elementNoSlot.querySelector('#someSlot')).to.not.exist;
|
||||
const elNoSlot = await fixture(`<${tag}><${tag}>`);
|
||||
expect(elNoSlot.querySelector('#someSlot')).to.not.exist;
|
||||
});
|
||||
|
||||
it("allows to check which slots have been created via this._isPrivateSlot('slotname')", async () => {
|
||||
let renderSlot = true;
|
||||
const tag = defineCE(
|
||||
class extends SlotMixin(HTMLElement) {
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
conditional: () => (renderSlot ? document.createElement('div') : undefined),
|
||||
};
|
||||
}
|
||||
class SlotPrivateText extends SlotMixin(HTMLElement) {
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
conditional: () => (renderSlot ? document.createElement('div') : undefined),
|
||||
};
|
||||
}
|
||||
|
||||
didCreateConditionalSlot() {
|
||||
return this._isPrivateSlot('conditional');
|
||||
}
|
||||
},
|
||||
);
|
||||
const el = await fixture(`<${tag}><${tag}>`);
|
||||
didCreateConditionalSlot() {
|
||||
return this._isPrivateSlot('conditional');
|
||||
}
|
||||
}
|
||||
|
||||
const tag = defineCE(SlotPrivateText);
|
||||
const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));
|
||||
expect(el.didCreateConditionalSlot()).to.be.true;
|
||||
const elUserSlot = await fixture(`<${tag}><p slot="conditional">foo</p><${tag}>`);
|
||||
const elUserSlot = /** @type {SlotPrivateText} */ (await fixture(
|
||||
`<${tag}><p slot="conditional">foo</p><${tag}>`,
|
||||
));
|
||||
expect(elUserSlot.didCreateConditionalSlot()).to.be.false;
|
||||
renderSlot = false;
|
||||
const elNoSlot = await fixture(`<${tag}><${tag}>`);
|
||||
const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));
|
||||
expect(elNoSlot.didCreateConditionalSlot()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,82 +4,75 @@ import { UpdateStylesMixin } from '../src/UpdateStylesMixin.js';
|
|||
|
||||
describe('UpdateStylesMixin', () => {
|
||||
it('handles css variables && direct e.g. host css properties correctly', async () => {
|
||||
const tag = defineCE(
|
||||
class extends UpdateStylesMixin(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
text-align: right;
|
||||
class UpdateStylesElement extends UpdateStylesMixin(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
text-align: right;
|
||||
|
||||
--color: rgb(128, 128, 128);
|
||||
}
|
||||
--color: rgb(128, 128, 128);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<h1 id="header">hey</h1>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal(
|
||||
'rgb(128, 128, 128)',
|
||||
);
|
||||
render() {
|
||||
return html`<h1 id="header">hey</h1>`;
|
||||
}
|
||||
}
|
||||
|
||||
const tag = defineCE(UpdateStylesElement);
|
||||
const el = /** @type {UpdateStylesElement} */ (await fixture(`<${tag}></${tag}>`));
|
||||
const header = /** @type {Element} */ (el.shadowRoot?.getElementById('header'));
|
||||
|
||||
expect(window.getComputedStyle(header).color).to.equal('rgb(128, 128, 128)');
|
||||
expect(window.getComputedStyle(el).textAlign).to.equal('right');
|
||||
el.updateStyles({
|
||||
'--color': 'rgb(255, 0, 0)',
|
||||
'text-align': 'center',
|
||||
});
|
||||
|
||||
await tag.updateComplete;
|
||||
expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal(
|
||||
'rgb(255, 0, 0)',
|
||||
);
|
||||
await el.updateComplete;
|
||||
expect(window.getComputedStyle(header).color).to.equal('rgb(255, 0, 0)');
|
||||
expect(window.getComputedStyle(el).textAlign).to.equal('center');
|
||||
});
|
||||
|
||||
it('preserves existing styles', async () => {
|
||||
const tag = defineCE(
|
||||
class extends UpdateStylesMixin(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
--color: rgb(128, 128, 128);
|
||||
}
|
||||
class UpdateStylesElement extends UpdateStylesMixin(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
--color: rgb(128, 128, 128);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<h1 id="header">hey</h1>`;
|
||||
}
|
||||
},
|
||||
);
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal(
|
||||
'rgb(128, 128, 128)',
|
||||
);
|
||||
render() {
|
||||
return html`<h1 id="header">hey</h1>`;
|
||||
}
|
||||
}
|
||||
const tag = defineCE(UpdateStylesElement);
|
||||
const el = /** @type {UpdateStylesElement} */ (await fixture(`<${tag}></${tag}>`));
|
||||
const header = /** @type {Element} */ (el.shadowRoot?.getElementById('header'));
|
||||
|
||||
expect(window.getComputedStyle(header).color).to.equal('rgb(128, 128, 128)');
|
||||
el.updateStyles({ '--color': 'rgb(255, 0, 0)' });
|
||||
|
||||
await tag.updateComplete;
|
||||
expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal(
|
||||
'rgb(255, 0, 0)',
|
||||
);
|
||||
expect(window.getComputedStyle(header).color).to.equal('rgb(255, 0, 0)');
|
||||
el.updateStyles({ 'text-align': 'left' });
|
||||
|
||||
await tag.updateComplete;
|
||||
const styles = window.getComputedStyle(el.shadowRoot.getElementById('header'));
|
||||
const styles = window.getComputedStyle(header);
|
||||
expect(styles.color).to.equal('rgb(255, 0, 0)');
|
||||
expect(styles.textAlign).to.equal('left');
|
||||
});
|
||||
|
|
|
|||
51
packages/core/types/DelegateMixinTypes.d.ts
vendored
Normal file
51
packages/core/types/DelegateMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export type Delegations = {
|
||||
target: Function;
|
||||
events: string[];
|
||||
methods: string[];
|
||||
properties: string[];
|
||||
attributes: string[];
|
||||
};
|
||||
|
||||
export declare class DelegateMixinHost {
|
||||
delegations: Delegations;
|
||||
|
||||
protected _connectDelegateMixin(): void;
|
||||
|
||||
private __setupPropertyDelegation(): void;
|
||||
|
||||
private __initialAttributeDelegation(): void;
|
||||
|
||||
private __emptyEventListenerQueue(): void;
|
||||
|
||||
private __emptyPropertiesQueue(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* # DelegateMixin
|
||||
* Forwards defined events, methods, properties and attributes to the defined target.
|
||||
*
|
||||
* @example
|
||||
* get delegations() {
|
||||
* return {
|
||||
* ...super.delegations,
|
||||
* target: () => this.shadowRoot.getElementById('button1'),
|
||||
* events: ['click'],
|
||||
* methods: ['click'],
|
||||
* properties: ['disabled'],
|
||||
* attributes: ['disabled'],
|
||||
* };
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return html`
|
||||
* <button id="button1">with delegation</button>
|
||||
* `;
|
||||
* }
|
||||
*/
|
||||
declare function DelegateMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<DelegateMixinHost>;
|
||||
|
||||
export type DelegateMixin = typeof DelegateMixinImplementation;
|
||||
29
packages/core/types/DisabledMixinTypes.d.ts
vendored
Normal file
29
packages/core/types/DisabledMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export declare class DisabledMixinHost {
|
||||
static get properties(): {
|
||||
disabled: {
|
||||
type: BooleanConstructor;
|
||||
reflect: boolean;
|
||||
};
|
||||
};
|
||||
disabled: boolean;
|
||||
|
||||
/**
|
||||
* Makes request to make the element disabled
|
||||
*/
|
||||
public makeRequestToBeDisabled(): void;
|
||||
|
||||
/**
|
||||
* Retract request to make the element disabled and restore disabled to previous
|
||||
*/
|
||||
public retractRequestToBeDisabled(): void;
|
||||
|
||||
private __internalSetDisabled(value: boolean): void;
|
||||
}
|
||||
|
||||
export declare function DisabledMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<DisabledMixinHost>;
|
||||
|
||||
export type DisabledMixin = typeof DisabledMixinImplementation;
|
||||
29
packages/core/types/DisabledWithTabIndexMixinTypes.d.ts
vendored
Normal file
29
packages/core/types/DisabledWithTabIndexMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { DisabledMixinHost } from './DisabledMixinTypes';
|
||||
export declare class DisabledWithTabIndexMixinHost {
|
||||
static get properties(): {
|
||||
tabIndex: {
|
||||
type: NumberConstructor;
|
||||
reflect: boolean;
|
||||
attribute: string;
|
||||
};
|
||||
};
|
||||
tabIndex: number;
|
||||
/**
|
||||
* Makes request to make the element disabled and set the tabindex
|
||||
*/
|
||||
public makeRequestToBeDisabled(): void;
|
||||
|
||||
/**
|
||||
* Retract request to make the element disabled and restore disabled and tabindex to previous
|
||||
*/
|
||||
public retractRequestToBeDisabled(): void;
|
||||
|
||||
private __internalSetTabIndex(value: boolean): void;
|
||||
}
|
||||
|
||||
export declare function DisabledWithTabIndexMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<DisabledWithTabIndexMixinHost> & Constructor<DisabledMixinHost>;
|
||||
|
||||
export type DisabledWithTabIndexMixin = typeof DisabledWithTabIndexMixinImplementation;
|
||||
53
packages/core/types/SlotMixinTypes.d.ts
vendored
Normal file
53
packages/core/types/SlotMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
|
||||
declare function slotFunction(): HTMLElement | undefined;
|
||||
|
||||
export type SlotsMap = {
|
||||
[key: string]: typeof slotFunction;
|
||||
};
|
||||
|
||||
export declare class SlotMixinHost {
|
||||
/**
|
||||
* Obtains all the slots to create
|
||||
*/
|
||||
slots: SlotsMap;
|
||||
|
||||
/**
|
||||
* Starts the creation of slots
|
||||
*/
|
||||
protected _connectSlotMixin(): void;
|
||||
|
||||
/**
|
||||
* Useful to decide if a given slot should be manipulated depending on if it was auto generated
|
||||
* or not.
|
||||
*
|
||||
* @param {string} slotName Name of the slot
|
||||
* @return {boolean} true if given slot name been created by SlotMixin
|
||||
*/
|
||||
protected _isPrivateSlot(slotName: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* # SlotMixin
|
||||
*
|
||||
* `SlotMixin`, when attached to the DOM it creates content for defined slots in the Light DOM.
|
||||
* The content element is created using a factory function and is assigned a slot name from the key.
|
||||
* Existing slot content is not overridden.
|
||||
*
|
||||
* The purpose is to have the default content in the Light DOM rather than hidden in Shadow DOM
|
||||
* like default slot content works natively.
|
||||
*
|
||||
* @example
|
||||
* get slots() {
|
||||
* return {
|
||||
* ...super.slots,
|
||||
* // appends <div slot="foo"></div> to the Light DOM of this element
|
||||
* foo: () => document.createElement('div'),
|
||||
* };
|
||||
* }
|
||||
*/
|
||||
export declare function SlotMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<SlotMixinHost>;
|
||||
|
||||
export type SlotMixin = typeof SlotMixinImplementation;
|
||||
34
packages/core/types/UpdateStylesMixinTypes.d.ts
vendored
Normal file
34
packages/core/types/UpdateStylesMixinTypes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
|
||||
export type StylesMap = {
|
||||
[key: string]: string;
|
||||
};
|
||||
export declare class UpdateStylesMixinHost {
|
||||
/**
|
||||
* @example
|
||||
* <my-element>
|
||||
* <style>
|
||||
* :host {
|
||||
* color: var(--foo);
|
||||
* }
|
||||
* </style>
|
||||
* </my-element>
|
||||
*
|
||||
* $0.updateStyles({'background': 'orange', '--foo': '#fff'})
|
||||
* Chrome, Firefox: <my-element style="background: orange; --foo: bar;">
|
||||
* IE: <my-element>
|
||||
* => to head: <style>color: #fff</style>
|
||||
*
|
||||
* @param {StylesMap} updateStyles
|
||||
*/
|
||||
public updateStyles(updateStyles: StylesMap): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* # UpdateStylesMixin
|
||||
*/
|
||||
declare function UpdateStylesMixinImplementation<T extends Constructor<HTMLElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<UpdateStylesMixinHost>;
|
||||
|
||||
export type UpdateStylesMixin = typeof UpdateStylesMixinImplementation;
|
||||
Loading…
Reference in a new issue