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 |
x9
x9
x9
x9
x9
x9
x61
x9
x9
x321
x340
x340
x9
x9
x48
x48
x48
x384
x48
x56
x57
x171
x57
x63
x252
x56
x177
x59
x254
x59
x56
x57
x171
x57
x56
x85
x48
x296
x296
x74
x85
x85
x218
x48
x48
x48
x48
x129
x903
x1083
x129
x129
x198
x594
x198
x198
x198
x228
x228
x198
x129
x129
x131
x131
x129
x129
x1161
x129
x85
x48
x138
x148
x148
x148
x138
x255
x48
x9
x9
x9
x9
x9
x36 |
|
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 = {
name: "*for",
phase: Phase.EXPAND,
init(renderer) {
renderer.cache<Cache<typeof _for>>(this.name, new WeakMap())
},
setup(renderer, element, { cache, state }) {
if ((cache.get(element) === null) && (!state[renderer.internal("iterating")])) {
return false
}
},
async execute(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 }
},
} as Directive<WeakMap<HTMLElement | Comment, Nullable<{ element: HTMLElement; items: Map<string, HTMLElement> }>>>
export const _id = {
name: "*id",
phase: Phase.META,
} as Directive & { name: string }
export default [_for, _id]
|