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}} diff --git a/CODEOWNERS b/CODEOWNERS index 68f394d..e6bf555 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @github/primer-reviewers +* @github/web-systems-reviewers diff --git a/src/index.ts b/src/index.ts index 5ea3b61..176d5d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export {TemplateInstance} from './template-instance.js' export {parse} from './template-string-parser.js' export {AttributeTemplatePart, AttributeValueSetter} from './attribute-template-part.js' +export {InnerTemplatePart} from './inner-template-part.js' export {NodeTemplatePart} from './node-template-part.js' export { createProcessor, diff --git a/src/inner-template-part.ts b/src/inner-template-part.ts new file mode 100644 index 0000000..5f599c3 --- /dev/null +++ b/src/inner-template-part.ts @@ -0,0 +1,11 @@ +import {NodeTemplatePart} from './node-template-part.js' + +export class InnerTemplatePart extends NodeTemplatePart { + constructor(public template: HTMLTemplateElement) { + super(template, template.getAttribute('expression') ?? '') + } + + get directive(): string { + return this.template.getAttribute('directive') ?? '' + } +} 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/src/processors.ts b/src/processors.ts index 1115516..9146b54 100644 --- a/src/processors.ts +++ b/src/processors.ts @@ -2,16 +2,16 @@ import type {TemplatePart, TemplateTypeInit} from './types.js' import type {TemplateInstance} from './template-instance.js' import {AttributeTemplatePart} from './attribute-template-part.js' -type PartProcessor = (part: TemplatePart, value: unknown) => void +type PartProcessor = (part: TemplatePart, value: unknown, state: unknown) => void export function createProcessor(processPart: PartProcessor): TemplateTypeInit { return { - processCallback(_: TemplateInstance, parts: Iterable, params: unknown): void { - if (typeof params !== 'object' || !params) return + processCallback(_: TemplateInstance, parts: Iterable, state: unknown): void { + if (typeof state !== 'object' || !state) return for (const part of parts) { - if (part.expression in params) { - const value = (params as Record)[part.expression] ?? '' - processPart(part, value) + if (part.expression in state) { + const value = (state as Record)[part.expression] ?? '' + processPart(part, value, state) } } }, @@ -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/template-instance.ts b/src/template-instance.ts index 7f4deba..a10cc20 100644 --- a/src/template-instance.ts +++ b/src/template-instance.ts @@ -1,5 +1,6 @@ import {parse} from './template-string-parser.js' import {AttributeValueSetter, AttributeTemplatePart} from './attribute-template-part.js' +import {InnerTemplatePart} from './inner-template-part.js' import {NodeTemplatePart} from './node-template-part.js' import {propertyIdentity} from './processors.js' import {TemplatePart, TemplateTypeInit} from './types.js' @@ -9,8 +10,12 @@ function* collectParts(el: DocumentFragment): Generator { let node while ((node = walker.nextNode())) { if (node instanceof HTMLTemplateElement) { - for (const part of collectParts(node.content)) { - yield part + if (node.hasAttribute('directive')) { + yield new InnerTemplatePart(node) + } else { + for (const part of collectParts(node.content)) { + yield part + } } } else if (node instanceof Element && node.hasAttributes()) { for (let i = 0; i < node.attributes.length; i += 1) { 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/processors.ts b/test/processors.ts index b83b4da..89131c0 100644 --- a/test/processors.ts +++ b/test/processors.ts @@ -1,5 +1,6 @@ import {expect} from '@open-wc/testing' import {TemplateInstance} from '../src/template-instance' +import {InnerTemplatePart} from '../src/inner-template-part' import type {TemplateTypeInit} from '../src/types' import {createProcessor} from '../src/processors' describe('createProcessor', () => { @@ -29,4 +30,23 @@ describe('createProcessor', () => { instance.update({y: 'world'}) expect(calls).to.eql(0) }) + + describe('handling InnerTemplatePart', () => { + beforeEach(() => { + processor = createProcessor(part => { + if (part instanceof InnerTemplatePart) calls += 1 + }) + }) + + it('detects InnerTemplatePart instances with