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 |
x4
x4
x4
x4
x4
x20
x12
x12
x16
x16
x12
x12
x12
x12
x12
x4
x4
x4
x4
x4
x4
x4
x4
x40
x40
x40
x4
x4
x62
x63
x63
x476
x1014
x62
x62
x62
x89
x146
x146
x146
x146
x152
x152
x146
x179
x179
x297
x151
x151
x154
x162
x154
x154
x154
x151
x146
x161
x175
x175
x161
x146
x89
x89
x62
x89
x118
x118
x118
x248
x130
x130
x132
x135
x137
x137
x135
x130
x140
x140
x130
x141
x141
x131
x131
x131
x118
x120
x121
x121
x120
x118
x122
x118
x952
x408
x89
x89
x62
x89
x89
x90
x90
x89
x630
x90
x712
x89
x119
x62
x4
x12
x4
x91
x91
x193
x102
x102
x102
x106
x106
x102
x102
x91
x167
x167
x91
x91
x91
x4
x300
x197
x201
x201
x197
x207
x207
x197
x205
x205
x197
x200
x200
x269
x100
x100
x100 |
|
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_value = {
name: /^::(?<value>)$/,
prefix: "::",
phase: Phase.ATTRIBUTE_MODEL_VALUE,
typings,
init(renderer) {
if (!renderer.cache(this.name)) {
renderer.cache<Cache<typeof _model_value>>(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
if (!cached.model.sync) {
cached.model.sync = async function () {
const model = await renderer.evaluate(input, attribute.value, 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, 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}=${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)
}
if (parsed.modifiers.value && (input.getAttribute("value"))) {
await renderer.evaluate(input, `${attribute.value}??=${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>
export default [_model_value]
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_value>) {
const parsed = [value].flat().map((value) => {
if ((modifiers.nullish) && (!value)) {
return null
}
if (modifiers.number) {
return Number(value)
}
if (modifiers.boolean) {
return !(value.length && /^(?:[Ff]alse|FALSE|[Nn]o|NO|[Oo]ff|OFF)$/.test(value))
}
if (modifiers.string) {
return `${value}`
}
return value
})
return Array.isArray(value) ? parsed : parsed[0]
}
|