Skip to content

Commit

Permalink
Merge pull request #75 from seanpdoyle/inner-template-part
Browse files Browse the repository at this point in the history
Implement `InnerTemplatePart` class
  • Loading branch information
keithamus authored Dec 13, 2024
2 parents 743b970 + 8aba104 commit 187af82
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 9 deletions.
11 changes: 11 additions & 0 deletions src/inner-template-part.ts
Original file line number Diff line number Diff line change
@@ -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') ?? ''
}
}
12 changes: 6 additions & 6 deletions src/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TemplatePart>, params: unknown): void {
if (typeof params !== 'object' || !params) return
processCallback(_: TemplateInstance, parts: Iterable<TemplatePart>, state: unknown): void {
if (typeof state !== 'object' || !state) return
for (const part of parts) {
if (part.expression in params) {
const value = (params as Record<string, unknown>)[part.expression] ?? ''
processPart(part, value)
if (part.expression in state) {
const value = (state as Record<string, unknown>)[part.expression] ?? ''
processPart(part, value, state)
}
}
},
Expand Down
9 changes: 7 additions & 2 deletions src/template-instance.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -9,8 +10,12 @@ function* collectParts(el: DocumentFragment): Generator<TemplatePart> {
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) {
Expand Down
20 changes: 20 additions & 0 deletions test/processors.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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 <template> element', () => {
template.innerHTML = '<template directive="if" expression="x">{{x}}</template>'
new TemplateInstance(template, {x: true}, processor)
expect(calls).to.eql(1)
})

it('does not detect InnerTemplatePart instances without <template> element', () => {
new TemplateInstance(template, {x: true}, processor)
expect(calls).to.eql(0)
})
})
})
29 changes: 28 additions & 1 deletion test/template-instance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {expect} from '@open-wc/testing'
import {TemplateInstance} from '../src/template-instance'
import {NodeTemplatePart} from '../src/node-template-part'
import {propertyIdentityOrBooleanAttribute, createProcessor} from '../src/processors'
import {InnerTemplatePart} from '../src/inner-template-part'
import {processPropertyIdentity, propertyIdentityOrBooleanAttribute, createProcessor} from '../src/processors'

describe('template-instance', () => {
it('applies data to templated text nodes', () => {
Expand Down Expand Up @@ -354,5 +355,31 @@ describe('template-instance', () => {
expect(processCallCount).to.equal(2)
})
})

describe('handling InnerTemplatePart', () => {
it('makes outer state available to inner parts', () => {
const processor = createProcessor((part, value, state) => {
if (part instanceof InnerTemplatePart && part.directive === 'if') {
if (typeof state === 'object' && (state as Record<string, unknown>)[part.expression]) {
part.replace(new TemplateInstance(part.template, state, processor))
} else {
part.replace()
}
} else {
processPropertyIdentity(part, value)
}
})
const template = Object.assign(document.createElement('template'), {
innerHTML: '{{x}}<template directive="if" expression="y">{{y}}</template>',
})

const root = document.createElement('div')
root.appendChild(new TemplateInstance(template, {x: 'x', y: 'y'}, processor))
expect(root.innerHTML).to.equal('xy')

root.replaceChildren(new TemplateInstance(template, {x: 'x', y: false}, processor))
expect(root.innerHTML).to.equal('x')
})
})
})
})

0 comments on commit 187af82

Please sign in to comment.