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 |
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x66
x66
x66
x8
x8
x84
x1
x1
x84
x127
x84
x84
x3
x3
x3
x3
x3
x1
x1
x3
x84
x127
x124
x127
x14
x14
x14
x21
x9
x21
x10
x10
x14
x14
x14
x127
x15
x15
x15
x17
x10
x20
x20
x20
x20
x20
x5
x5
x10
x17
x6
x6
x15
x15
x1
x1
x15
x15
x127
x84
x84
x127
x14
x2
x2
x2
x12
x127
x127
x84
x8
x8
x8
x8 |
|
import { type Cache, type Directive, Phase } from "@mizu/internal/engine"
import { toCamelCase } from "@std/text"
import { boolean } from "./boolean.ts"
export type * from "@mizu/internal/engine"
export const _bind = {
name: /^:(?!:)(?<attribute>)/,
prefix: ":",
phase: Phase.ATTRIBUTE,
multiple: true,
default: "$<attribute>",
init(renderer) {
if (!renderer.cache(this.name)) {
renderer.cache<Cache<typeof this>>(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, {}, { 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 || toCamelCase(name), 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 const satisfies Directive<{
Name: RegExp
Cache: WeakMap<HTMLElement, { class?: string; style?: string }>
Default: true
}>
export const _bind_class = _bind as typeof _bind
export const _bind_style = _bind as typeof _bind
export default _bind
|