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 |
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x76
x76
x76
x8
x8
x94
x95
x95
x94
x411
x94
x94
x97
x97
x97
x504
x97
x98
x98
x97
x94
x223
x349
x460
x237
x237
x711
x258
x267
x258
x280
x280
x237
x237
x237
x463
x240
x240
x720
x264
x277
x299
x299
x299
x299
x299
x304
x304
x277
x264
x284
x284
x240
x240
x241
x241
x240
x240
x223
x307
x307
x223
x237
x239
x239
x239
x249
x223
x223
x94
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 _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 || 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 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
|