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 |
x8
x8
x8
x8
x8
x8
x8
x8
x74
x74
x74
x8
x8
x93
x94
x94
x93
x407
x93
x93
x96
x96
x96
x499
x96
x97
x97
x96
x93
x221
x346
x456
x235
x235
x705
x256
x265
x256
x278
x278
x235
x235
x235
x459
x238
x238
x714
x262
x275
x297
x297
x297
x297
x297
x302
x302
x275
x262
x282
x282
x238
x238
x239
x239
x238
x238
x221
x305
x305
x221
x234
x236
x236
x236
x245
x221
x221
x93
x8
x8
x8
x8 |
|
import { type Cache, type Directive, Phase } from "@mizu/internal/engine"
import { boolean } from "./boolean.ts"
export type * from "@mizu/internal/engine"
export const _bind = {
name: /^:(?!:)(?<attribute>)/,
prefix: ":",
phase: Phase.ATTRIBUTE,
multiple: true,
init(renderer) {
if (!renderer.cache(this.name)) {
renderer.cache<Cache<typeof _bind>>(this.name, new WeakMap())
}
},
async execute(renderer, element, { attributes, cache, ...options }) {
if (!renderer.isHtmlElement(element)) {
return
}
const cached = cache.get(element) ?? cache.set(element, {}).get(element)!
const parsed = attributes.map((attribute) => renderer.parseAttribute(attribute, null, { prefix: this.prefix }))
const shorthand = parsed.findIndex(({ name }) => !name.length)
if (~shorthand) {
const [attribute] = parsed.splice(shorthand, 1)
const value = await renderer.evaluate(element, attribute.value, options)
if (typeof value === "object") {
parsed.unshift(...Object.entries(value ?? {}).map(([name, value]) => ({ ...attribute, name, value })))
} else {
renderer.warn(`[${this.name}] empty shorthand expects an object but got ${typeof value}, ignoring`, element)
}
}
for (const { name, value: expression, attribute } of parsed) {
const value = attribute.name === ":" ? expression : await renderer.evaluate(element, expression, options)
switch (true) {
case name === "class": {
cached.class ??= element.getAttribute("class") ?? ""
element.setAttribute("class", `${cached.class}`)
;[value].flat(Infinity).forEach((classlist) => {
if ((typeof classlist === "object") && classlist) {
Object.entries(classlist).forEach(([name, value]) => value && element.classList.add(name))
} else if (typeof classlist === "string") {
element.classList.add(...classlist.split(" ").map((name) => name.trim()).filter(Boolean))
}
})
break
}
case name === "style": {
cached.style ??= element.getAttribute("style") ?? ""
element.setAttribute("style", `${cached.style}`)
;[value].flat(Infinity).forEach((style) => {
if ((typeof style === "object") && style) {
Object.entries(style).forEach(([name, value]) => {
const property = name.startsWith("--") ? name : name.replace(/([a-z])([A-Z])/g, "$1-$2")
const [match = "", priority = ""] = `${value}`.match(/\s!(important)[\s;]*$/) ?? []
const style = `${value}`.replace(match, "")
element.style.setProperty(property, style, priority)
if ((typeof value === "number") && (!element.style.getPropertyValue(property))) {
element.style.setProperty(property, `${value}px`)
}
})
} else if (typeof style === "string") {
element.style.cssText += style
}
})
if (!element.style.length) {
element.removeAttribute("style")
}
break
}
case boolean(element.tagName, name):
element.toggleAttribute(name, Boolean(value))
break
default:
if ((value === undefined) || (value === null)) {
element.removeAttribute(name)
break
}
element.setAttribute(name, `${value}`)
}
}
},
} as Directive<WeakMap<HTMLElement, { class?: string; style?: string }>>
export const _bind_class = _bind as typeof _bind
export const _bind_style = _bind as typeof _bind
export default _bind
|