chore: refactor to reactive controller
This commit is contained in:
parent
006a47e530
commit
17ae20df86
9 changed files with 79 additions and 91 deletions
|
|
@ -88,15 +88,16 @@ The pseudo-elements associated with the slider track/thumb are not tree-abiding,
|
|||
```
|
||||
|
||||
This means you will need to style the slotted native input from the LightDOM,
|
||||
and for this we added a helper method to `LionInputRange` which inserts a `<style>` element
|
||||
and for this we added added our ScopedStylesController as a controller to `LionInputRange`.
|
||||
This controller inserts a `<style>` element
|
||||
that emulates scoping by generating a uniquely generated class on the LionInputRange component.
|
||||
This prevents the styling from conflicting with other elements on the page.
|
||||
|
||||
To use it when extending, override `static rangeStyles(scope)`:
|
||||
To use it when extending, override `static scopedStyles(scope)`:
|
||||
|
||||
```js
|
||||
class MyInputRange extends LionInputRange {
|
||||
static rangeStyles(scope) {
|
||||
static scopedStyles(scope) {
|
||||
return css`
|
||||
.${scope} .form-control::-webkit-slider-runnable-track {
|
||||
background-color: lightgreen;
|
||||
|
|
|
|||
2
packages/core/index.d.ts
vendored
2
packages/core/index.d.ts
vendored
|
|
@ -80,7 +80,7 @@ export { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|||
export { DelegateMixin } from './src/DelegateMixin.js';
|
||||
export { DisabledMixin } from './src/DisabledMixin.js';
|
||||
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
|
||||
export { ScopedStylesMixin } from './src/ScopedStylesMixin.js';
|
||||
export { ScopedStylesController } from './src/ScopedStylesController.js';
|
||||
export { SlotMixin } from './src/SlotMixin.js';
|
||||
export { UpdateStylesMixin } from './src/UpdateStylesMixin.js';
|
||||
export { browserDetection } from './src/browserDetection.js';
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|||
export { DelegateMixin } from './src/DelegateMixin.js';
|
||||
export { DisabledMixin } from './src/DisabledMixin.js';
|
||||
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
|
||||
export { ScopedStylesMixin } from './src/ScopedStylesMixin.js';
|
||||
export { ScopedStylesController } from './src/ScopedStylesController.js';
|
||||
export { SlotMixin } from './src/SlotMixin.js';
|
||||
export { UpdateStylesMixin } from './src/UpdateStylesMixin.js';
|
||||
export { browserDetection } from './src/browserDetection.js';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import { unsafeCSS, css } from 'lit';
|
||||
|
||||
/**
|
||||
* @typedef {import('lit').ReactiveControllerHost} ReactiveControllerHost
|
||||
* @typedef {import('lit').ReactiveController} ReactiveController
|
||||
* @implements {ReactiveController}
|
||||
*/
|
||||
export class ScopedStylesController {
|
||||
/**
|
||||
* @param {import('lit').CSSResult} scope
|
||||
* @return {import('lit').CSSResultGroup}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
static scopedStyles(scope) {
|
||||
return css``;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ReactiveControllerHost & import('lit').LitElement} host
|
||||
*/
|
||||
constructor(host) {
|
||||
(this.host = host).addController(this);
|
||||
// Perhaps use constructable stylesheets instead once Safari supports replace(Sync) methods
|
||||
this.__styleTag = document.createElement('style');
|
||||
this.scopedClass = `${this.host.localName}-${Math.floor(Math.random() * 10000)}`;
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
this.host.classList.add(this.scopedClass);
|
||||
this.__setupStyleTag();
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
this.__teardownStyleTag();
|
||||
}
|
||||
|
||||
__setupStyleTag() {
|
||||
this.__styleTag.textContent = /** @type {typeof ScopedStylesController} */ (
|
||||
this.host.constructor
|
||||
)
|
||||
.scopedStyles(unsafeCSS(this.scopedClass))
|
||||
.toString();
|
||||
this.host.insertBefore(this.__styleTag, this.host.childNodes[0]);
|
||||
}
|
||||
|
||||
__teardownStyleTag() {
|
||||
this.host.removeChild(this.__styleTag);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
import { unsafeCSS, css } from 'lit';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/ScopedStylesMixinTypes').ScopedStylesMixin} ScopedStylesMixin
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {ScopedStylesMixin}
|
||||
* @param {import('@open-wc/dedupe-mixin').Constructor<import('lit').LitElement>} superclass
|
||||
*/
|
||||
const ScopedStylesMixinImplementation = superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
// @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
|
||||
class ScopedStylesHost extends superclass {
|
||||
/**
|
||||
* @param {import('lit').CSSResult} scope
|
||||
* @return {import('lit').CSSResultGroup}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
static scopedStyles(scope) {
|
||||
return css``;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// Perhaps use constructable stylesheets instead once Safari supports replace(Sync) methods
|
||||
this.__styleTag = document.createElement('style');
|
||||
this.scopedClass = `${this.localName}-${Math.floor(Math.random() * 10000)}`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.classList.add(this.scopedClass);
|
||||
this.__setupStyleTag();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.__teardownStyleTag();
|
||||
}
|
||||
|
||||
__setupStyleTag() {
|
||||
this.__styleTag.textContent = /** @type {typeof ScopedStylesHost} */ (this.constructor)
|
||||
.scopedStyles(unsafeCSS(this.scopedClass))
|
||||
.toString();
|
||||
this.insertBefore(this.__styleTag, this.childNodes[0]);
|
||||
}
|
||||
|
||||
__teardownStyleTag() {
|
||||
this.removeChild(this.__styleTag);
|
||||
}
|
||||
};
|
||||
|
||||
export const ScopedStylesMixin = dedupeMixin(ScopedStylesMixinImplementation);
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { expect, fixture } from '@open-wc/testing';
|
||||
// import { html as _html } from 'lit/static-html.js';
|
||||
import { LitElement, css, html } from '../index.js';
|
||||
import { ScopedStylesMixin } from '../src/ScopedStylesMixin.js';
|
||||
import { ScopedStylesController } from '../src/ScopedStylesController.js';
|
||||
|
||||
describe('ScopedStylesMixin', () => {
|
||||
class Scoped extends ScopedStylesMixin(LitElement) {
|
||||
class Scoped extends LitElement {
|
||||
/**
|
||||
* @param {import('lit').CSSResult} scope
|
||||
* @returns {import('lit').CSSResultGroup}
|
||||
|
|
@ -17,6 +17,11 @@ describe('ScopedStylesMixin', () => {
|
|||
`;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.scopedStylesController = new ScopedStylesController(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <p class="test">Some Text</p> `;
|
||||
}
|
||||
|
|
@ -31,18 +36,24 @@ describe('ScopedStylesMixin', () => {
|
|||
});
|
||||
|
||||
it('contains the scoped css class for the slotted input style', async () => {
|
||||
const el = /** @type {Scoped} */ (await fixture(html`<scoped-el></scoped-el>`));
|
||||
expect(el.classList.contains(el.scopedClass)).to.equal(true);
|
||||
const el = /** @type {Scoped & ScopedStylesController} */ (
|
||||
await fixture(html`<scoped-el></scoped-el>`)
|
||||
);
|
||||
expect(el.classList.contains(el.scopedStylesController.scopedClass)).to.equal(true);
|
||||
});
|
||||
|
||||
it('adds a style tag as the first child which contains a class selector to the element', async () => {
|
||||
const el = /** @type {Scoped} */ (await fixture(html` <scoped-el></scoped-el> `));
|
||||
const el = /** @type {Scoped & ScopedStylesController} */ (
|
||||
await fixture(html` <scoped-el></scoped-el> `)
|
||||
);
|
||||
expect(el.children[0].tagName).to.equal('STYLE');
|
||||
expect(el.children[0].innerHTML).to.contain(el.scopedClass);
|
||||
expect(el.children[0].innerHTML).to.contain(el.scopedStylesController.scopedClass);
|
||||
});
|
||||
|
||||
it('the scoped styles are applied correctly to the DOM elements', async () => {
|
||||
const el = /** @type {Scoped} */ (await fixture(html`<scoped-el></scoped-el>`));
|
||||
const el = /** @type {Scoped & ScopedStylesController} */ (
|
||||
await fixture(html`<scoped-el></scoped-el>`)
|
||||
);
|
||||
const testText = /** @type {HTMLElement} */ (el.querySelector('.test'));
|
||||
const cl = Array.from(el.classList);
|
||||
expect(cl.find(item => item.startsWith('scoped-el-'))).to.not.be.undefined;
|
||||
20
packages/core/types/ScopedStylesMixinTypes.d.ts
vendored
20
packages/core/types/ScopedStylesMixinTypes.d.ts
vendored
|
|
@ -1,20 +0,0 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { CSSResultGroup, CSSResult, LitElement } from 'lit';
|
||||
|
||||
export declare class ScopedStylesHost {
|
||||
static scopedStyles(scope: CSSResult): CSSResultGroup;
|
||||
|
||||
__styleTag: HTMLStyleElement;
|
||||
|
||||
scopedClass: string;
|
||||
|
||||
private __setupStyleTag(): void;
|
||||
|
||||
private __teardownStyleTag(): void;
|
||||
}
|
||||
|
||||
export declare function ScopedStylesMixinImplementation<T extends Constructor<LitElement>>(
|
||||
superclass: T,
|
||||
): T & Constructor<ScopedStylesHost> & Pick<typeof ScopedStylesHost, keyof typeof ScopedStylesHost>;
|
||||
|
||||
export type ScopedStylesMixin = typeof ScopedStylesMixinImplementation;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { css, html, ScopedStylesMixin } from '@lion/core';
|
||||
import { css, html, ScopedStylesController } from '@lion/core';
|
||||
import { LionInput } from '@lion/input';
|
||||
import { formatNumber } from '@lion/localize';
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ import { formatNumber } from '@lion/localize';
|
|||
*
|
||||
* @customElement `lion-input-range`
|
||||
*/
|
||||
export class LionInputRange extends ScopedStylesMixin(LionInput) {
|
||||
export class LionInputRange extends LionInput {
|
||||
/** @type {any} */
|
||||
static get properties() {
|
||||
return {
|
||||
|
|
@ -59,6 +59,8 @@ export class LionInputRange extends ScopedStylesMixin(LionInput) {
|
|||
|
||||
constructor() {
|
||||
super();
|
||||
/** @type {ScopedStylesController} */
|
||||
this.scopedStylesController = new ScopedStylesController(this);
|
||||
this.min = Infinity;
|
||||
this.max = Infinity;
|
||||
this.step = 1;
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ export class LionInput extends NativeTextFieldMixin(LionField) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
* @param {PropertyKey} [name]
|
||||
* @param {?} [oldValue]
|
||||
*/
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
|
|
|||
Loading…
Reference in a new issue