mirror of
https://github.com/jlengrand/open-wc.git
synced 2026-03-10 08:31:19 +00:00
fix(scoped-elements): elements not scoped by directives
This commit is contained in:
committed by
Lars den Bakker
parent
94f832967f
commit
71f7438308
@@ -39,12 +39,13 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@open-wc/dedupe-mixin": "^1.2.13",
|
||||
"lit-element": "^2.2.1"
|
||||
"lit-html": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@open-wc/building-rollup": "^0.22.11",
|
||||
"@open-wc/testing": "^2.5.8",
|
||||
"es-dev-server": "^1.46.0",
|
||||
"lit-element": "^2.2.1",
|
||||
"npm-run-all": "4.1.3",
|
||||
"rollup": "^1.31.1"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { TemplateResult } from 'lit-element';
|
||||
import { TemplateResult } from 'lit-html';
|
||||
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
import { transform } from './transform.js';
|
||||
import { defineScopedElement, registerElement } from './registerElement.js';
|
||||
import { shadyTemplateFactory } from './shadyTemplateFactory.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('lit-html/lib/shady-render').ShadyRenderOptions} ShadyRenderOptions
|
||||
@@ -28,18 +29,18 @@ const tagsCaches = new WeakMap();
|
||||
*
|
||||
* @param {ReadonlyArray} items
|
||||
* @param {Object.<string, typeof HTMLElement>} scopedElements
|
||||
* @param {Map<TemplateStringsArray, TemplateStringsArray>} cache
|
||||
* @param {Map<TemplateStringsArray, TemplateStringsArray>} templateCache
|
||||
* @param {Map<string, string>} tagsCache
|
||||
* @returns {ReadonlyArray}
|
||||
*/
|
||||
const transformArray = (items, scopedElements, cache, tagsCache) =>
|
||||
const transformArray = (items, scopedElements, templateCache, tagsCache) =>
|
||||
items.map(value => {
|
||||
if (value instanceof TemplateResult) {
|
||||
return transformTemplate(value, scopedElements, cache, tagsCache);
|
||||
return transformTemplate(value, scopedElements, templateCache, tagsCache);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return transformArray(value, scopedElements, cache, tagsCache);
|
||||
return transformArray(value, scopedElements, templateCache, tagsCache);
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -62,6 +63,17 @@ const transformTemplate = (template, scopedElements, templateCache, tagsCache) =
|
||||
template.processor,
|
||||
);
|
||||
|
||||
const scopedElementsTemplateFactory = (
|
||||
scopeName,
|
||||
scopedElements,
|
||||
templateCache,
|
||||
tagsCache,
|
||||
) => template => {
|
||||
const newTemplate = transformTemplate(template, scopedElements, templateCache, tagsCache);
|
||||
|
||||
return shadyTemplateFactory(scopeName)(newTemplate);
|
||||
};
|
||||
|
||||
export const ScopedElementsMixin = dedupeMixin(
|
||||
superclass =>
|
||||
// eslint-disable-next-line no-shadow
|
||||
@@ -74,6 +86,11 @@ export const ScopedElementsMixin = dedupeMixin(
|
||||
* @override
|
||||
*/
|
||||
static render(template, container, options) {
|
||||
if (!options || typeof options !== 'object' || !options.scopeName) {
|
||||
throw new Error('The `scopeName` option is required.');
|
||||
}
|
||||
const { scopeName } = options;
|
||||
|
||||
if (!templateCaches.has(this)) {
|
||||
templateCaches.set(this, new Map());
|
||||
}
|
||||
@@ -84,15 +101,17 @@ export const ScopedElementsMixin = dedupeMixin(
|
||||
const templateCache = templateCaches.get(this);
|
||||
const tagsCache = tagsCaches.get(this);
|
||||
const { scopedElements } = this;
|
||||
const transformedTemplate = transformTemplate(
|
||||
template,
|
||||
scopedElements,
|
||||
templateCache,
|
||||
tagsCache,
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return super.render(transformedTemplate, container, options);
|
||||
return super.render(template, container, {
|
||||
...options,
|
||||
templateFactory: scopedElementsTemplateFactory(
|
||||
scopeName,
|
||||
scopedElements,
|
||||
templateCache,
|
||||
tagsCache,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
47
packages/scoped-elements/src/shadyTemplateFactory.js
Normal file
47
packages/scoped-elements/src/shadyTemplateFactory.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { templateCaches } from 'lit-html/lib/template-factory.js';
|
||||
import { marker, Template } from 'lit-html/lib/template.js';
|
||||
|
||||
const getTemplateCacheKey = (type, scopeName) => `${type}--${scopeName}`;
|
||||
|
||||
let compatibleShadyCSSVersion = true;
|
||||
|
||||
// @ts-ignore
|
||||
const { ShadyCSS } = window;
|
||||
|
||||
if (typeof ShadyCSS === 'undefined') {
|
||||
compatibleShadyCSSVersion = false;
|
||||
} else if (typeof ShadyCSS.prepareTemplateDom === 'undefined') {
|
||||
compatibleShadyCSSVersion = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template factory which scopes template DOM using ShadyCSS.
|
||||
* @param scopeName {string}
|
||||
*/
|
||||
export const shadyTemplateFactory = scopeName => result => {
|
||||
const cacheKey = getTemplateCacheKey(result.type, scopeName);
|
||||
let templateCache = templateCaches.get(cacheKey);
|
||||
if (templateCache === undefined) {
|
||||
templateCache = {
|
||||
stringsArray: new WeakMap(),
|
||||
keyString: new Map(),
|
||||
};
|
||||
templateCaches.set(cacheKey, templateCache);
|
||||
}
|
||||
let template = templateCache.stringsArray.get(result.strings);
|
||||
if (template !== undefined) {
|
||||
return template;
|
||||
}
|
||||
const key = result.strings.join(marker);
|
||||
template = templateCache.keyString.get(key);
|
||||
if (template === undefined) {
|
||||
const element = result.getTemplateElement();
|
||||
if (compatibleShadyCSSVersion) {
|
||||
ShadyCSS.prepareTemplateDom(element, scopeName);
|
||||
}
|
||||
template = new Template(result, element);
|
||||
templateCache.keyString.set(key, template);
|
||||
}
|
||||
templateCache.stringsArray.set(result.strings, template);
|
||||
return template;
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { expect, fixture, defineCE } from '@open-wc/testing';
|
||||
import { expect, fixture, defineCE, waitUntil } from '@open-wc/testing';
|
||||
import { LitElement, html } from 'lit-element';
|
||||
import { until } from 'lit-html/directives/until.js';
|
||||
import { repeat } from 'lit-html/directives/repeat';
|
||||
import { ScopedElementsMixin } from '../index.js';
|
||||
import { getFromGlobalTagsCache } from '../src/globalTagsCache.js';
|
||||
|
||||
@@ -350,4 +352,79 @@ describe('ScopedElementsMixin', () => {
|
||||
expect(el.constructor.getScopedTagName('unregistered-feature')).to.match(tagRegExp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('directives integration', () => {
|
||||
it('should work with until(...)', async () => {
|
||||
const content = new Promise(resolve => {
|
||||
setTimeout(
|
||||
() =>
|
||||
resolve(
|
||||
html`
|
||||
<feature-a id="feat"></feature-a>
|
||||
`,
|
||||
),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
const tag = defineCE(
|
||||
class ContainerElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'feature-a': FeatureA,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${until(
|
||||
content,
|
||||
html`
|
||||
<span>Loading...</span>
|
||||
`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
|
||||
expect(el.shadowRoot.getElementById('feat')).to.be.null;
|
||||
|
||||
await waitUntil(() => el.shadowRoot.getElementById('feat') !== null);
|
||||
const feature = el.shadowRoot.getElementById('feat');
|
||||
|
||||
expect(feature).shadowDom.to.equal('<div>Element A</div>');
|
||||
});
|
||||
|
||||
it('should work with repeat(...)', async () => {
|
||||
const tag = defineCE(
|
||||
class ContainerElement extends ScopedElementsMixin(LitElement) {
|
||||
static get scopedElements() {
|
||||
return {
|
||||
'feature-a': FeatureA,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${repeat(
|
||||
[...Array(10).keys()],
|
||||
() => html`
|
||||
<feature-a data-type="child"></feature-a>
|
||||
`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const el = await fixture(`<${tag}></${tag}>`);
|
||||
|
||||
el.shadowRoot.querySelectorAll('[data-type="child"]').forEach(child => {
|
||||
expect(child).shadowDom.to.equal('<div>Element A</div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user