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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 |
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x10
x44
x10
x10
x179
x19
x19
x19
x19
x19
x19
x19
x19
x19
x19
x10
x10
x27
x1
x1
x27
x1
x1
x1
x27
x27
x1
x1
x1
x27
x4
x4
x27
x2
x2
x2
x18
x18
x18
x27
x1
x1
x1
x18
x18
x18
x18
x21
x4
x4
x17
x17
x17
x21
x20
x20
x15
x15
x20
x12
x12
x20
x20
x20
x20
x20
x17
x17
x17
x17
x21
x3
x3
x21
x18
x18
x27
x10
x10
x10
x10
x10
x10
x10 |
|
import { type Directive, type Nullable, Phase } from "@mizu/internal/engine"
import { isValidCustomElementName } from "@std/html/unstable-is-valid-custom-element-name"
import { _once } from "@mizu/once"
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(this.name, new WeakMap())
},
setup(renderer, element, { cache }) {
if ((renderer.isHtmlElement(element)) && (cache.get(element))) {
return {
state: {
$slots: cache.get(element)!,
$attrs: new Proxy({}, {
has: (_, name: string) => element.hasAttribute(String(name)),
get: (_, name: string) => element.getAttribute(String(name)) ?? undefined,
}),
},
}
}
},
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 })
let flat = parsed.modifiers.flat
if (flat && (!renderer.cache(_once.name))) {
renderer.warn(`[${this.name}] .flat modifier requires [${_once.name}] directive to be loaded, ignoring`, element)
flat = false
}
renderer.window.customElements.define(
tagname,
class extends renderer.window.HTMLElement {
connectedCallback(this: HTMLElement) {
if ((cache.has(this)) || (renderer.elementHasPhase(this, Phase.EXPAND))) {
return
}
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 (flat && (!renderer.getAttributes(this, _once.name, { first: true }))) {
renderer.setAttribute(this, `${_once.name}.flat`)
}
}
},
)
return { final: true }
},
} as const satisfies Directive<{
Cache: WeakMap<HTMLElement, Nullable<Record<PropertyKey, HTMLSlotElement>>>
Typings: typeof typings
}>
export const _slot = {
name: /^#(?<slot>)/,
prefix: "#",
phase: Phase.META,
} as const satisfies Directive<{ Name: RegExp }>
export default [_custom_element, _slot]
|