From 86a80d3e781d15f1c3cace77056d7465726999f8 Mon Sep 17 00:00:00 2001 From: Lea Verou Date: Mon, 9 Dec 2024 20:27:23 -0500 Subject: [PATCH] [code-demo] Bugfixes Slotted previews still don't work well with isolated demos :( --- docs/_utils/code-examples.js | 6 ++--- src/components/code-demo/code-demo.styles.ts | 4 +++ src/components/code-demo/code-demo.ts | 26 +++++++++++++++----- src/internal/slot.ts | 22 ++++++++++++----- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/_utils/code-examples.js b/docs/_utils/code-examples.js index 1e39eb12f..4efc227a8 100644 --- a/docs/_utils/code-examples.js +++ b/docs/_utils/code-examples.js @@ -80,9 +80,9 @@ const templates = { } let preview = ''; - if (attributes.viewport === undefined) { - preview = `
${code.textContent}
`; - } + // if (attributes.viewport === undefined) { + preview = `
${code.textContent}
`; + // } return `${includes} diff --git a/src/components/code-demo/code-demo.styles.ts b/src/components/code-demo/code-demo.styles.ts index 4992791f8..ef332dcc0 100644 --- a/src/components/code-demo/code-demo.styles.ts +++ b/src/components/code-demo/code-demo.styles.ts @@ -61,6 +61,10 @@ export default css` } } + wa-viewport-demo + slot[name='preview'].has-slotted { + display: none; + } + #source { border-block-end: inherit; overflow: hidden; diff --git a/src/components/code-demo/code-demo.ts b/src/components/code-demo/code-demo.ts index e8cef5d0a..88bca48f6 100644 --- a/src/components/code-demo/code-demo.ts +++ b/src/components/code-demo/code-demo.ts @@ -1,4 +1,5 @@ import '../icon/icon.js'; +import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { getInnerHTML, HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; @@ -78,11 +79,9 @@ export default class WaCodeDemo extends WebAwesomeElement { private readonly hasSlotController = new HasSlotController(this, 'preview'); render() { - const code = this.getDemoHTML({ type: 'preview' }); - // FIXME Ideally we don't want to render the contents of the code element anywhere if a custom preview is provided. + // NOTE We don't want to render the contents of the code element anywhere if a custom preview is provided. // That way, providing a custom preview can also be used to sanitize the code. - const customPreview = this.hasUpdated ? this.hasSlotController.test('preview') : true; - + const code = this.getDemoHTML({ type: 'preview' }); let viewportHTML: string | TemplateResult = ''; if (this.viewport) { @@ -94,11 +93,14 @@ export default class WaCodeDemo extends WebAwesomeElement { `; } + const customPreview = this.hasUpdated ? this.hasSlotController.test('preview') : true; + return html`
${viewportHTML} @@ -180,7 +182,7 @@ export default class WaCodeDemo extends WebAwesomeElement { let code; const customPreview = this.hasUpdated ? this.hasSlotController.test('preview') : true; if (options.type === 'preview' && customPreview && this.previewSlot) { - code = getInnerHTML(this.previewSlot); + code = getHTML(this.previewSlot.assignedNodes({ flatten: true })); } else { code = this.querySelector?.('code')?.textContent ?? this.textContent; } @@ -197,10 +199,12 @@ export default class WaCodeDemo extends WebAwesomeElement { private handleSlotChange(e: Event) { const slot = e.target as HTMLSlotElement; - if (slot.name === 'preview') { + if (slot.name === 'preview' && !this.viewport) { const assignedNodes = slot.assignedNodes(); for (const node of assignedNodes) { + // Unwrap templates + // FIXME this will mess up the order of the nodes if there are mixed templates & regular nodes if (node.nodeName === 'TEMPLATE') { const content = (node as HTMLTemplateElement).content; const clone = content.cloneNode(true); @@ -360,3 +364,13 @@ function dedent(code: string) { return code.replace(new RegExp(`^${minIndent}`, 'gm'), ''); } + +function getHTML(nodes: Iterable): string { + return getInnerHTML(nodes, node => { + if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'TEMPLATE') { + const template = node as HTMLTemplateElement; + return template.innerHTML; + } + return undefined; + }); +} diff --git a/src/internal/slot.ts b/src/internal/slot.ts index cb895b5c5..a52ce5b40 100644 --- a/src/internal/slot.ts +++ b/src/internal/slot.ts @@ -61,14 +61,24 @@ export class HasSlotController implements ReactiveController { } /** - * Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated - * HTML as a string. This is useful because we can't use slot.innerHTML as an alternative. + * Given a list of nodes, this function iterates over all of them and returns the concatenated + * HTML as a string. This is useful for getting the HTML that corresponds to a slot’s assigned nodes (since we can't use slot.innerHTML as an alternative). + * @param nodes - The list of nodes to iterate over. + * @param callback - A function that can be used to customize the HTML output for specific types of nodes. If the function returns undefined, the default HTML output will be used. */ -export function getInnerHTML(slot: HTMLSlotElement): string { - const nodes = slot.assignedNodes({ flatten: true }); +export function getInnerHTML(nodes: Iterable, callback?: (node: Node) => string | undefined): string { let html = ''; - [...nodes].forEach(node => { + for (const node of nodes) { + if (callback) { + const customHTML = callback(node); + + if (customHTML !== undefined) { + html += customHTML; + continue; + } + } + if (node.nodeType === Node.ELEMENT_NODE) { html += (node as HTMLElement).outerHTML; } @@ -76,7 +86,7 @@ export function getInnerHTML(slot: HTMLSlotElement): string { if (node.nodeType === Node.TEXT_NODE) { html += node.textContent; } - }); + } return html; }