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 |
x10
x10
x10
x10
x10
x10
x64
x10
x10
x333
x352
x352
x10
x10
x50
x50
x50
x400
x50
x59
x60
x180
x60
x67
x268
x59
x198
x63
x274
x63
x59
x60
x180
x60
x59
x88
x50
x308
x308
x77
x88
x88
x226
x50
x50
x50
x50
x133
x931
x1111
x133
x133
x204
x612
x204
x204
x204
x234
x234
x204
x133
x133
x135
x135
x133
x133
x1197
x133
x88
x50
x142
x152
x152
x152
x142
x264
x50
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 = {
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]
|