[code-demo] Bugfixes

Slotted previews still don't work well with isolated demos :(
This commit is contained in:
Lea Verou
2024-12-09 20:27:23 -05:00
parent 1aebdf4a06
commit 86a80d3e78
4 changed files with 43 additions and 15 deletions

View File

@@ -80,9 +80,9 @@ const templates = {
}
let preview = '';
if (attributes.viewport === undefined) {
preview = `<div style="display:contents" slot="preview">${code.textContent}</div>`;
}
// if (attributes.viewport === undefined) {
preview = `<div style="display:contents" slot="preview">${code.textContent}</div>`;
// }
return `${includes}
<wa-code-demo ${attributesString}>

View File

@@ -61,6 +61,10 @@ export default css`
}
}
wa-viewport-demo + slot[name='preview'].has-slotted {
display: none;
}
#source {
border-block-end: inherit;
overflow: hidden;

View File

@@ -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`
<div id="preview" part="preview">
${viewportHTML}
<slot
name="preview"
class=${classMap({ 'has-slotted': customPreview })}
@slotchange=${this.handleSlotChange}
.innerHTML=${customPreview || this.viewport ? '' : code}
></slot>
@@ -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<Node>): string {
return getInnerHTML(nodes, node => {
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'TEMPLATE') {
const template = node as HTMLTemplateElement;
return template.innerHTML;
}
return undefined;
});
}

View File

@@ -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 slots 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<Node>, 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;
}