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 |
x8
x8
x8
x8
x24
x8
x8
x8
x8
x8
x8
x8
x36
x8
x8
x106
x115
x115
x115
x462
x115
x115
x115
x8
x8
x24
x25
x25
x24
x25
x75
x25
x24
x24
x25
x75
x25
x24
x75
x25
x24
x26
x78
x26
x34
x102
x34
x34
x34
x34
x129
x43
x43
x43
x56
x56
x62
x62
x56
x64
x64
x56
x56
x56
x56
x56
x43
x53
x43
x43
x43
x44
x44
x43
x34
x102
x24
x8
x8
x8
x8
x8
x8
x32 |
|
import { type Cache, type Directive, type Nullable, Phase } from "@mizu/internal/engine"
import { isValidCustomElementName } from "@std/html/is-valid-custom-element-name"
export type * from "@mizu/internal/engine"
export const typings = {
modifiers: {
flat: { type: Boolean },
},
} as const
export const _custom_element = {
name: "*custom-element",
phase: Phase.CUSTOM_ELEMENT,
typings,
init(renderer) {
renderer.cache<Cache<typeof _custom_element>>(this.name, new WeakMap())
},
setup(renderer, element, { cache }) {
if ((renderer.isHtmlElement(element)) && (cache.get(element))) {
return {
state: {
$slots: cache.get(element)!,
$attrs: Object.fromEntries(Array.from(element.attributes).map(({ name, value }) => [name, value])),
},
}
}
},
async execute(renderer, element, { cache, attributes: [attribute], ...options }) {
if (!renderer.isHtmlElement(element)) {
return
}
if ((element.tagName !== "TEMPLATE")) {
renderer.warn(`A [${this.name}] directive must be defined on a <template> element, ignoring`, element)
return { final: true }
}
const tagname = isValidCustomElementName(attribute.value) ? attribute.value : `${await renderer.evaluate(element, attribute.value || "''", options)}`
if (!tagname) {
renderer.warn(`A [${this.name}] directive must have a valid custom element name, ignoring`, element)
return { final: true }
}
if (cache.has(element)) {
return { final: true }
}
if (renderer.window.customElements.get(tagname)) {
renderer.warn(`<${tagname}> is already registered as a custom element, ignoring`, element)
return { final: true }
}
cache.set(element, null)
const parsed = renderer.parseAttribute(attribute, this.typings, { modifiers: true })
renderer.window.customElements.define(
tagname,
class extends renderer.window.HTMLElement {
connectedCallback(this: HTMLElement) {
const content = Array.from(renderer.createElement("div", { innerHTML: this.innerHTML.trim() }).childNodes) as HTMLElement[]
this.innerHTML = element.innerHTML
const slots = cache.set(this, {}).get(this)!
for (const child of content) {
const names = []
if (child.nodeType === renderer.window.Node.ELEMENT_NODE) {
names.push(...renderer.getAttributes(child, _slot.name).map((attribute) => attribute.name.slice(_slot.prefix.length)))
}
if (!names.length) {
names.push("")
}
for (const name of names) {
slots[name] ??= renderer.createElement("slot")
slots[name].appendChild(child.cloneNode(true))
}
}
Object.entries(slots).forEach(([name, content]) => {
this.querySelectorAll<HTMLSlotElement>(`slot${name ? `[name="${name}"]` : ":not([name])"}`).forEach((slot) => renderer.replaceElementWithChildNodes(slot, content))
})
this.querySelectorAll<HTMLSlotElement>("slot").forEach((slot) => renderer.replaceElementWithChildNodes(slot, slot))
if (parsed.modifiers.flat) {
renderer.replaceElementWithChildNodes(this, this)
}
}
},
)
return { final: true }
},
} as Directive<WeakMap<HTMLElement, Nullable<Record<PropertyKey, HTMLSlotElement>>>, typeof typings> & { name: string }
export const _slot = {
name: /^#(?<slot>)/,
prefix: "#",
phase: Phase.META,
} as Directive & { prefix: string }
export default [_custom_element, _slot]
|