Skip to content
This repository was archived by the owner on Jan 9, 2025. It is now read-only.

Commit 532e115

Browse files
authored
Merge pull request #5 from github/feat-add-support-for-iterables-in-templates
feat: add support for iterables in templates
2 parents f4ede91 + 97bfffd commit 532e115

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

src/html.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,40 @@ function processDocumentFragment(part: TemplatePart, value: unknown): boolean {
2525
return false
2626
}
2727

28+
function isIterable(value: unknown): value is Iterable<unknown> {
29+
return typeof value === 'object' && Symbol.iterator in ((value as unknown) as Record<symbol, unknown>)
30+
}
31+
32+
function processIterable(part: TemplatePart, value: unknown): boolean {
33+
if (!isIterable(value)) return false
34+
if (part instanceof NodeTemplatePart) {
35+
const nodes = []
36+
for (const item of value) {
37+
if (item instanceof TemplateResult) {
38+
const fragment = document.createDocumentFragment()
39+
render(item, fragment)
40+
nodes.push(...fragment.children)
41+
} else if (item instanceof DocumentFragment) {
42+
nodes.push(...item.children)
43+
} else {
44+
nodes.push(String(item))
45+
}
46+
}
47+
if (nodes.length) part.replace(...nodes)
48+
return true
49+
} else {
50+
part.value = Array.from(value).join(' ')
51+
return true
52+
}
53+
}
54+
2855
export function processPart(part: TemplatePart, value: unknown): void {
2956
processDirective(part, value) ||
3057
processBooleanAttribute(part, value) ||
3158
processEvent(part, value) ||
3259
processSubTemplate(part, value) ||
3360
processDocumentFragment(part, value) ||
61+
processIterable(part, value) ||
3462
processPropertyIdentity(part, value)
3563
}
3664

test/html.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,69 @@ describe('render', () => {
4242
})
4343
})
4444

45+
describe('iterables', () => {
46+
it('supports arrays of strings in nodes', () => {
47+
const main = list => html`<div>${list}</div>`
48+
render(main(['one', 'two', 'three']), surface)
49+
expect(surface.innerHTML).to.equal('<div>onetwothree</div>')
50+
render(main(['four', 'five', 'six']), surface)
51+
expect(surface.innerHTML).to.equal('<div>fourfivesix</div>')
52+
})
53+
54+
it('supports other strings iterables in nodes', () => {
55+
const main = list => html`<div>${list}</div>`
56+
render(main(new Set(['one', 'two', 'three'])), surface)
57+
expect(surface.innerHTML).to.equal('<div>onetwothree</div>')
58+
render(
59+
main(
60+
new Map([
61+
[4, 'four'],
62+
[5, 'five'],
63+
[6, 'six']
64+
]).values()
65+
),
66+
surface
67+
)
68+
expect(surface.innerHTML).to.equal('<div>fourfivesix</div>')
69+
})
70+
71+
it('supports iterables of strings in attributes', () => {
72+
const main = list => html`<div class="${list}"></div>`
73+
render(main(['one', 'two', 'three']), surface)
74+
expect(surface.innerHTML).to.equal('<div class="one two three"></div>')
75+
render(main(new Set(['four', 'five', 'six'])), surface)
76+
expect(surface.innerHTML).to.equal('<div class="four five six"></div>')
77+
})
78+
79+
it('supports nested iterables of document fragments', () => {
80+
// prettier-ignore
81+
const main = list => html`<ul>${list}</ul>`
82+
render(
83+
main(
84+
['One', 'Two'].map(text => {
85+
const f = document.createDocumentFragment()
86+
const li = document.createElement('li')
87+
li.textContent = text
88+
f.append(li)
89+
return f
90+
})
91+
),
92+
surface
93+
)
94+
expect(surface.innerHTML).to.equal('<ul><li>One</li><li>Two</li></ul>')
95+
})
96+
97+
it('supports nested iterables of templates', () => {
98+
const child = item => html`<li>${item.name}</li>`
99+
// prettier-ignore
100+
const main = list => html`<ul>${list.map(child)}</ul>`
101+
render(main([{name: 'One'}, {name: 'Two'}, {name: 'Three'}]), surface)
102+
expect(surface.innerHTML).to.equal('<ul><li>One</li><li>Two</li><li>Three</li></ul>')
103+
render(main([{name: 'Two'}, {name: 'Three'}, {name: 'Four'}]), surface)
104+
expect(surface.innerHTML).to.equal('<ul><li>Two</li><li>Three</li><li>Four</li></ul>')
105+
})
106+
})
107+
45108
describe('directives', () => {
46109
it('handles directives differently', () => {
47110
const setAsFoo = directive(() => part => {

0 commit comments

Comments
 (0)