From 38c304abfb1c20d53c9591a0a6d77a871768f9a2 Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Tue, 3 Dec 2024 14:19:08 +0000 Subject: [PATCH 1/7] build before publish --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 083f079..de63ebd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,6 @@ jobs: - run: npm version ${TAG_NAME} --git-tag-version=false env: TAG_NAME: ${{ github.event.release.tag_name }} - - run: npm whoami; npm --ignore-scripts publish + - run: npm whoami; npm publish env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} From 28f61c6ec235701ddeb312057b6af5f91b2969ba Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Sun, 8 Dec 2024 20:35:47 -0500 Subject: [PATCH 2/7] Accept `Node` values as Template Parts Related to [github/template-parts#62][] (see [3.2. Template Parts and Custom Template Process Callback][]) Follow-up to [github/template-parts#65][] While the changes made in [#65][] improved support for `Element` parts, the expansion from `string` to `Element | string` was not broadened enough. While all `Element` instances are DOM Nodes, not all DOM Nodes are `Element` instances. For example, this change enables support for [DocumentFragment][] instances (generated from classes like [Range][] or properties like [HTMLTemplateElement.content][]), where prior support resulted in templating the `[object DocumentFragment]` text string instead of the fragment's `Node` instances. [github/template-parts#62]: https://github.com/github/template-parts/issues/62 [3.2. Template Parts and Custom Template Process Callback]: https://github.com/WICG/webcomponents/blob/159b1600bab02fe9cd794825440a98537d53b389/proposals/Template-Instantiation.md#32-template-parts-and-custom-template-process-callback [github/template-parts#65]: https://github.com/github/template-parts/pull/65 [DocumentFragment]: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment [HTMLTemplateElement.content]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement/content --- src/processors.ts | 2 +- src/types.ts | 2 +- test/template-instance.ts | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/processors.ts b/src/processors.ts index 1115516..1f47ec0 100644 --- a/src/processors.ts +++ b/src/processors.ts @@ -19,7 +19,7 @@ export function createProcessor(processPart: PartProcessor): TemplateTypeInit { } export function processPropertyIdentity(part: TemplatePart, value: unknown): void { - part.value = value instanceof Element ? value : String(value) + part.value = value instanceof Node ? value : String(value) } export function processBooleanAttribute(part: TemplatePart, value: unknown): boolean { diff --git a/src/types.ts b/src/types.ts index a5c2520..116cea6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,7 @@ import type {TemplateInstance} from './template-instance.js' export interface TemplatePart { expression: string - value: Element | string | null + value: Node | string | null } type TemplateProcessCallback = (instance: TemplateInstance, parts: Iterable, params: unknown) => void diff --git a/test/template-instance.ts b/test/template-instance.ts index 55096de..5b5ba07 100644 --- a/test/template-instance.ts +++ b/test/template-instance.ts @@ -36,6 +36,19 @@ describe('template-instance', () => { expect(root.innerHTML).to.equal('') }) + it('applies data to templated DocumentFragment nodes', () => { + const template = document.createElement('template') + const fragment = Object.assign(document.createElement('template'), { + innerHTML: '
Hello world
', + }) + const originalHTML = `{{x}}` + template.innerHTML = originalHTML + const instance = new TemplateInstance(template, {x: fragment.content}) + expect(template.innerHTML).to.equal(originalHTML) + const root = document.createElement('div') + root.appendChild(instance) + expect(root.innerHTML).to.equal(`
Hello world
`) + }) it('can render into partial text nodes', () => { const template = document.createElement('template') const originalHTML = `Hello {{x}}!` From 79b4ee611aec5f9b7578844b06fad4e6a6e6b824 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 12 Dec 2024 17:53:22 -0500 Subject: [PATCH 3/7] Change `NodeTemplatePart.replace` type signature Replace `ChildNode` with `Node` in `NodeTemplatePart.replace(...nodes)` signature. According to the [3.2. Template Parts and Custom Template Process Callback][] section of the specification, the `NodeTemplatePart` interface declares `replace(...nodes: Array)`: ```typescript interface NodeTemplatePart : TemplatePart { readonly attribute ContainerNode parentNode; readonly attribute Node? previousSibling; readonly attribute Node? nextSibling; [NewObject] readonly NodeList replacementNodes; void replace((Node or DOMString)... nodes); void replaceHTML(DOMString html); } ``` This commit changes the type signature and adds coverage for replacing a `NodeTemplatePart` with a `DocumentFragment` instance. [3.2. Template Parts and Custom Template Process Callback]: https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md#32-template-parts-and-custom-template-process-callback --- src/node-template-part.ts | 13 +++++++------ test/template-instance.ts | 21 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/node-template-part.ts b/src/node-template-part.ts index 978840c..413251f 100644 --- a/src/node-template-part.ts +++ b/src/node-template-part.ts @@ -1,9 +1,9 @@ import {TemplatePart} from './types.js' -const parts = new WeakMap() +const parts = new WeakMap() export class NodeTemplatePart implements TemplatePart { constructor( - node: ChildNode, + node: Node, public expression: string, ) { parts.set(this, [node]) @@ -29,14 +29,15 @@ export class NodeTemplatePart implements TemplatePart { return parts.get(this)![parts.get(this)!.length - 1].nextSibling } - replace(...nodes: Array): void { - const normalisedNodes: ChildNode[] = nodes.map(node => { + replace(...nodes: Array): void { + const normalisedNodes: Node[] = nodes.map(node => { if (typeof node === 'string') return new Text(node) return node }) if (!normalisedNodes.length) normalisedNodes.push(new Text('')) - parts.get(this)![0].before(...normalisedNodes) - for (const part of parts.get(this)!) part.remove() + const node = parts.get(this)![0] + for (const normalisedNode of normalisedNodes) node.parentNode?.insertBefore(normalisedNode, node) + for (const part of parts.get(this)!) part.parentNode?.removeChild(part) parts.set(this, normalisedNodes) } } diff --git a/test/template-instance.ts b/test/template-instance.ts index 5b5ba07..4b1242b 100644 --- a/test/template-instance.ts +++ b/test/template-instance.ts @@ -1,6 +1,6 @@ import {expect} from '@open-wc/testing' import {TemplateInstance} from '../src/template-instance' -import type {NodeTemplatePart} from '../src/node-template-part' +import {NodeTemplatePart} from '../src/node-template-part' import {propertyIdentityOrBooleanAttribute, createProcessor} from '../src/processors' describe('template-instance', () => { @@ -238,6 +238,25 @@ describe('template-instance', () => { describe('edge cases', () => { describe('NodeTemplatePart', () => { + it('replace supports a DocumentFragment Node that is not a ChildNode', () => { + const template = Object.assign(document.createElement('template'), { + innerHTML: '
{{a}}
', + }) + const {content} = Object.assign(document.createElement('template'), { + innerHTML: 'after', + }) + const instance = new TemplateInstance( + template, + {a: 'before'}, + createProcessor(part => { + if (part instanceof NodeTemplatePart) part.replace(content) + }), + ) + const root = document.createElement('div') + root.appendChild(instance) + expect(root.innerHTML).to.equal('
after
') + }) + it('replaces an empty replace() call with an empty text node', () => { const template = document.createElement('template') template.innerHTML = `
{{a}}
` From 8aba104892250f3931a1369711c3ce178b238b67 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 12 Dec 2024 17:53:22 -0500 Subject: [PATCH 4/7] Implement `InnerTemplatePart` class The [3.3. Conditionals and Loops using Nested Templates][] section of the specification mentions special treatment of `