1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 |
x10
x10
x10
x10
x10
x10
x10
x66
x10
x10
x347
x366
x366
x10
x10
x51
x51
x51
x408
x51
x60
x61
x183
x61
x68
x272
x60
x201
x64
x278
x64
x60
x61
x183
x61
x60
x90
x51
x316
x316
x79
x90
x90
x231
x51
x51
x51
x51
x137
x959
x1139
x137
x137
x211
x633
x211
x211
x211
x241
x241
x211
x137
x137
x139
x139
x137
x137
x1233
x137
x90
x51
x146
x156
x156
x156
x146
x270
x51
x10
x10
x10
x10
x10
x40 |
|
import { type Arg, type Cache, type Directive, type Nullable, Phase } from "@mizu/internal/engine"
import { Expression } from "./parse.ts"
export type * from "@mizu/internal/engine"
export const _for: Directive<{
Cache: WeakMap<HTMLElement | Comment, Nullable<{ element: HTMLElement; items: Map<string, HTMLElement> }>>
}> = {
name: "*for",
phase: Phase.EXPAND,
init(this: typeof _for, renderer) {
renderer.cache<Cache<typeof this>>(this.name, new WeakMap())
},
setup(this: typeof _for, renderer, element, { cache, state }) {
if ((cache.get(element) === null) && (!state[renderer.internal("iterating")])) {
return false
}
},
async execute(this: typeof _for, renderer, element, { cache, context, state, attributes: [attribute] }) {
const iterations = [] as Record<PropertyKey, unknown>[]
try {
const loop = `()=>{for(${attribute.value}){${renderer.internal("iterations")}.push({${Expression.parse(attribute.value).join(",")}})}}`
await renderer.evaluate(null, loop, { context, state: { ...state, [renderer.internal("iterations")]: iterations }, args: [] })
} catch (error) {
if (!(error instanceof SyntaxError)) {
renderer.warn(`[${this.name}] error while evaluating expression: ${error}`, element)
return { final: true }
}
try {
const value = await renderer.evaluate(null, attribute.value, { context, state })
if (typeof value === "number") {
iterations.push(...Array.from({ length: value }, () => ({})))
} else {
iterations.push(...Object.entries(value as Arg<typeof Object.entries>).map(([$key, $value]) => ({ $key, $value })))
}
} catch {
renderer.warn(`[${this.name}] syntax error in expression: ${attribute.value}`, element)
return { final: true }
}
}
let comment = element as unknown as Comment
if (!renderer.isComment(element)) {
comment = renderer.comment(element, { directive: this.name as string, expression: attribute.value })
cache.set(comment, { element, items: new Map() })
}
const cached = cache.get(comment)!
element = cached.element
const identifiable = renderer.getAttributes(element, _id.name, { first: true })?.value
let position = comment as Comment | HTMLElement
const generated = new Set<string>()
const pending = []
for (let i = 0; i < iterations.length; i++) {
const iteration = context.with(iterations[i])
const meta = { $i: i, $I: i + 1, $iterations: iterations.length, $first: i === 0, $last: i === iterations.length - 1 }
const id = identifiable ? `${await renderer.evaluate(null, identifiable, { context: iteration, state: { ...state, ...meta } })}` : `${i}`
generated.add(id)
if (!cached.items.has(id)) {
const item = element.cloneNode(true) as HTMLElement
item.removeAttributeNode(renderer.getAttributes(item, this.name, { first: true })!)
cached.items.set(id, item)
cache.set(item, null)
if (identifiable) {
renderer.setAttribute(item, _id.name, id)
}
}
const item = cached.items.get(id)!
if (renderer.getComment(item)) {
renderer.uncomment(renderer.getComment(item)!)
}
comment.parentNode?.insertBefore(item, position.nextSibling)
position = item
pending.push(await renderer.render(item, { context: iteration, state: { ...state, [renderer.internal("iterating")]: true, ...meta, $id: id } }))
}
await Promise.allSettled(pending)
for (const [id, item] of cached.items) {
if (!generated.has(id)) {
cached.items.delete(id)
item.remove()
}
}
return { final: true }
},
}
export const _id = {
name: "*id",
phase: Phase.META,
} as Directive & { name: string }
export default [_for, _id]
|