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 |
x11
x11
x11
x11
x11
x49
x11
x11
x335
x335
x23
x23
x11
x11
x131
x131
x131
x260
x46
x46
x46
x15
x15
x46
x15
x15
x15
x15
x15
x46
x46
x46
x26
x22
x22
x108
x108
x108
x108
x22
x26
x104
x104
x39
x39
x104
x46
x46
x11
x49
x49
x11
x11
x46
x46
x85
x126
x14
x14
x14
x131
x43
x43
x43
x131
x131
x11
x11 |
I
|
import { type Directive, type Nullable, Phase } from "@mizu/internal/engine"
export type * from "@mizu/internal/engine"
export type Cache = { templates: WeakMap<Comment, { nodes: Node[]; end: Comment }>; generated: WeakMap<Node, Comment> }
export const _if = {
name: "*if",
phase: Phase.TOGGLE,
init(renderer) {
renderer.cache<Cache>(this.name, { templates: new WeakMap(), generated: new WeakMap() })
},
setup(renderer, element, { cache, state }) {
const original = (renderer.isComment(element) ? renderer.cache("*").get(element) : null) ?? element
if ((cache.generated.has(original)) && (!state[renderer.internal("templated")])) {
return false
}
},
async execute(renderer, element, { attributes: [attribute], context, state }) {
const result = Boolean(await renderer.evaluate(element, arguments[2]._directive?.value ?? attribute.value, { context, state }))
const template = (renderer.isComment(element) ? renderer.cache("*").get(element) : element) as Nullable<HTMLElement>
const names = [_if.name, arguments[2]._directive?.directive]
if ((template?.tagName === "TEMPLATE") && (!renderer.directives.some((directive) => (!names.includes(directive.name)) && (renderer.getAttributes(template, directive.name, { first: true }))))) {
const cache = renderer.cache<Cache>("*if") ?? renderer.cache<Cache>("*if", { templates: new WeakMap(), generated: new WeakMap() })
let comment = element as Comment
if (!renderer.isComment(element)) {
comment = renderer.comment(element, { expression: attribute.value, directive: _if.name, ...arguments[2]._directive })
}
if (!cache.templates.has(comment)) {
const nodes = Array.from((template as HTMLTemplateElement).content.cloneNode(true).childNodes)
const end = renderer.document.createComment(`[/${arguments[2]._directive?.directive ?? _if.name}]`)
;[...nodes, end].forEach((node) => cache.generated.set(node, comment))
cache.templates.set(comment, { nodes, end })
}
const cached = cache.templates.get(comment)!
const attached = Boolean(cached.end.parentNode)
if (result) {
if (!attached) {
let position = comment as Node
for (const node of [...cached.nodes, cached.end]) {
const current = renderer.getComment(node as HTMLElement) ?? node
comment.parentNode?.insertBefore(current, position.nextSibling)
position = current
}
}
for (const node of cached.nodes) {
const current = renderer.getComment(node as HTMLElement) ?? node
if (current.nodeType !== renderer.window.Node.TEXT_NODE) {
await renderer.render(current as HTMLElement, { context, state: { ...state, [renderer.internal("templated")]: true } })
}
}
}
else if (attached) {
while ((comment.nextSibling) && (comment.nextSibling !== cached.end)) {
comment.nextSibling.remove()
}
cached.end.remove()
}
return (comment !== element) ? { element: comment, final: true } : { final: true }
}
switch (true) {
case result && (renderer.isComment(element)) && (renderer.cache("*").has(element)): {
const original = renderer.uncomment(element)
return { element: original }
}
case (!result) && (renderer.isHtmlElement(element)): {
const comment = renderer.comment(element, { expression: attribute.value, directive: _if.name, ...arguments[2]._directive })
return { element: comment, final: true }
}
}
},
} as const satisfies Directive<{
Cache: Cache
}>
export default _if
|