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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 |
x4
x4
x4
x4
x4
x20
x12
x12
x16
x16
x12
x12
x12
x12
x12
x4
x4
x4
x4
x4
x4
x4
x4
x4
x42
x42
x42
x4
x4
x64
x65
x65
x492
x1058
x64
x64
x64
x64
x93
x152
x152
x152
x152
x158
x158
x152
x185
x185
x309
x157
x157
x160
x168
x160
x160
x160
x157
x152
x169
x185
x185
x169
x152
x93
x93
x64
x93
x123
x123
x123
x258
x135
x135
x137
x140
x142
x142
x140
x135
x145
x145
x135
x146
x146
x136
x136
x136
x123
x125
x126
x126
x125
x123
x127
x123
x861
x426
x93
x93
x64
x93
x93
x95
x95
x93
x564
x94
x744
x93
x123
x64
x4
x12
x4
x94
x94
x199
x105
x105
x105
x109
x109
x105
x105
x94
x173
x173
x94
x94
x94
x4
x309
x203
x207
x207
x203
x213
x213
x203
x211
x211
x203
x206
x206
x278
x103
x103
x103 |
I
|
import { type Arg, type Arrayable, type Cache, type callback, type Directive, type Modifiers, type Nullable, Phase } from "@mizu/internal/engine"
import { equal } from "@std/assert"
import { _event } from "@mizu/event"
export type * from "@mizu/internal/engine"
export const typings = {
modifiers: {
event: { type: String, default: "input", enforce: true },
name: { type: Boolean },
value: { type: Boolean },
throttle: { type: Date, default: 250 },
debounce: { type: Date, default: 250 },
keys: { type: String },
nullish: { type: Boolean },
boolean: { type: Boolean },
number: { type: Boolean },
string: { type: Boolean },
},
} as const
export const _model = {
name: /^::(?<value>)$/,
prefix: "::",
phase: Phase.ATTRIBUTE_MODEL_VALUE,
typings,
default: "value",
init(renderer) {
if (!renderer.cache(this.name)) {
renderer.cache<Cache<typeof _model>>(this.name, new WeakMap())
}
},
async execute(renderer, element, { attributes: [attribute], cache, ...options }) {
if (!renderer.isHtmlElement(element)) {
return
}
const parsed = renderer.parseAttribute(attribute, this.typings, { prefix: this.prefix, modifiers: true })
const cached = cache.get(element)?.get(element) || cache.set(element, new WeakMap([[element, { model: { read: null, sync: null }, event: null, init: false }]])).get(element)!.get(element)!
const input = element as HTMLInputElement
const type = input.tagName === "INPUT" ? input.getAttribute("type") : input.tagName === "SELECT" ? input.tagName : null
const defaults = this.default
if (!cached.model.sync) {
cached.model.sync = async function () {
const model = await renderer.evaluate(input, attribute.value || defaults, options)
const value = parse(read(input), parsed.modifiers)
switch (type) {
case "radio":
input.checked = equal(model, value)
break
case "checkbox":
input.checked = Array.isArray(model) ? model.includes(value) : false
break
case "SELECT": {
const select = element as HTMLSelectElement
if (select.multiple) {
Array.from(select.options).forEach((option) => {
option.selected = Array.isArray(model) ? model.includes(parse(option.value, parsed.modifiers)) : false
})
break
}
}
default:
if (!equal(model, value)) {
input.value = `${model ?? ""}`
}
break
}
}
}
if (!cached.model.read) {
cached.model.read = async function () {
const model = await renderer.evaluate(input, attribute.value || defaults, options)
let value = parse(read(input), parsed.modifiers)
switch (type) {
case "checkbox": {
const array = Array.isArray(model) ? model : []
if (!input.checked) {
for (let i = array.length - 1; i >= 0; i--) {
if (array[i] === value) {
array.splice(i, 1)
}
}
} else if (!array.includes(value)) {
array.push(value)
}
if (array === model) {
return
}
value = array
break
}
case "radio":
if ((equal(model, value)) && (!input.checked)) {
value = undefined as unknown as typeof value
}
break
case "number":
value = parse(Number(value) as unknown as Arg<typeof parse>, parsed.modifiers)
}
await renderer.evaluate(input, `${attribute.value || defaults}=${renderer.internal("value")}`, { ...options, state: { ...options.state, [renderer.internal("value")]: value } })
input.dispatchEvent(new renderer.window.Event("::", { bubbles: true }))
}
}
if (!cached.init) {
cached.init = true
if (parsed.modifiers.name && (!input.hasAttribute("name"))) {
input.setAttribute("name", attribute.value || defaults)
}
if (parsed.modifiers.value && (input.getAttribute("value"))) {
await renderer.evaluate(input, `${attribute.value || defaults}??=${renderer.internal("value")}`, { ...options, state: { ...options.state, [renderer.internal("value")]: parse(read(input), parsed.modifiers) } })
}
await _event.execute.call(this, renderer, element, { ...arguments[2], attributes: [attribute], _event: parsed.modifiers.event, _callback: cached.model.read })
}
await cached.model.sync()
},
} as Directive<WeakMap<HTMLElement, WeakMap<HTMLElement, { model: Record<"read" | "sync", Nullable<callback>>; event: Nullable<string>; init: boolean }>>, typeof typings> & { default: string }
export default [_model]
function read(input: HTMLElement) {
let value = null as Nullable<Arrayable<string>>
switch (input.tagName) {
case "SELECT": {
const select = input as HTMLSelectElement
value = Array.from(select.selectedOptions).map((option) => option.value)
if (!select.multiple) {
value = (value as string[])[0]
}
break
}
default:
value = (input as HTMLInputElement).value
break
}
return value
}
function parse(value: ReturnType<typeof read>, modifiers: Modifiers<typeof _model>) {
const parsed = [value].flat().map((value) => {
if ((modifiers.nullish) && (!value)) {
return null
}
if (modifiers.number) {
return Number(value)
}
if (modifiers.boolean) {
return Boolean(value) && !(value.length && /^(?:[Ff]alse|FALSE|[Nn]|[Nn]o|NO|[Oo]ff|OFF)$/.test(value))
}
if (modifiers.string) {
return String(value)
}
return value
})
return Array.isArray(value) ? parsed : parsed[0]
}
|