@ramstack/alpinegear-bound 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +382 -401
- package/alpinegear-bound.esm.js +375 -371
- package/alpinegear-bound.esm.min.js +1 -1
- package/alpinegear-bound.js +377 -373
- package/alpinegear-bound.min.js +1 -1
- package/package.json +9 -3
package/alpinegear-bound.esm.js
CHANGED
|
@@ -37,68 +37,68 @@ function has_setter(value) {
|
|
|
37
37
|
return typeof value?.set === "function";
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const key = Symbol();
|
|
41
|
-
let observer;
|
|
42
|
-
|
|
43
|
-
function observe_resize(el, listener) {
|
|
44
|
-
observer ??= new ResizeObserver(entries => {
|
|
45
|
-
for (const e of entries) {
|
|
46
|
-
for (const callback of e.target[key]?.values() ?? []) {
|
|
47
|
-
callback(e);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
el[key] ??= new Set();
|
|
53
|
-
el[key].add(listener);
|
|
54
|
-
|
|
55
|
-
observer.observe(el);
|
|
56
|
-
|
|
57
|
-
return () => {
|
|
58
|
-
el[key].delete(listener);
|
|
59
|
-
|
|
60
|
-
if (!el[key].size) {
|
|
61
|
-
observer.unobserve(el);
|
|
62
|
-
el[key] = null;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
40
|
+
const key = Symbol();
|
|
41
|
+
let observer;
|
|
42
|
+
|
|
43
|
+
function observe_resize(el, listener) {
|
|
44
|
+
observer ??= new ResizeObserver(entries => {
|
|
45
|
+
for (const e of entries) {
|
|
46
|
+
for (const callback of e.target[key]?.values() ?? []) {
|
|
47
|
+
callback(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
el[key] ??= new Set();
|
|
53
|
+
el[key].add(listener);
|
|
54
|
+
|
|
55
|
+
observer.observe(el);
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
el[key].delete(listener);
|
|
59
|
+
|
|
60
|
+
if (!el[key].size) {
|
|
61
|
+
observer.unobserve(el);
|
|
62
|
+
el[key] = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const warn = (...args) => console.warn("alpinegear.js:", ...args);
|
|
68
|
-
const is_array = Array.isArray;
|
|
69
|
-
const is_nullish = value => value === null || value === undefined;
|
|
70
|
-
const is_checkable_input = el => el.type === "checkbox" || el.type === "radio";
|
|
71
|
-
const is_numeric_input = el => el.type === "number" || el.type === "range";
|
|
72
|
-
const as_array = value => is_array(value) ? value : [value];
|
|
73
|
-
const loose_equal = (a, b) => a == b;
|
|
74
|
-
const loose_index_of = (array, value) => array.findIndex(v => v == value);
|
|
75
|
-
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
76
|
-
|
|
77
|
-
function assert(value, message) {
|
|
78
|
-
if (!value) {
|
|
79
|
-
throw new Error(message);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const listen = (target, type, listener, options) => {
|
|
84
|
-
target.addEventListener(type, listener, options);
|
|
85
|
-
return () => target.removeEventListener(type, listener, options);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const clone = value =>
|
|
89
|
-
typeof value === "object"
|
|
90
|
-
? JSON.parse(JSON.stringify(value))
|
|
91
|
-
: value;
|
|
92
|
-
|
|
93
|
-
const closest = (el, callback) => {
|
|
94
|
-
while (el && !callback(el)) {
|
|
95
|
-
el = (el._x_teleportBack ?? el).parentElement;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return el;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const create_map = keys => new Map(
|
|
67
|
+
const warn = (...args) => console.warn("alpinegear.js:", ...args);
|
|
68
|
+
const is_array = Array.isArray;
|
|
69
|
+
const is_nullish = value => value === null || value === undefined;
|
|
70
|
+
const is_checkable_input = el => el.type === "checkbox" || el.type === "radio";
|
|
71
|
+
const is_numeric_input = el => el.type === "number" || el.type === "range";
|
|
72
|
+
const as_array = value => is_array(value) ? value : [value];
|
|
73
|
+
const loose_equal = (a, b) => a == b;
|
|
74
|
+
const loose_index_of = (array, value) => array.findIndex(v => v == value);
|
|
75
|
+
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
76
|
+
|
|
77
|
+
function assert(value, message) {
|
|
78
|
+
if (!value) {
|
|
79
|
+
throw new Error(message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const listen = (target, type, listener, options) => {
|
|
84
|
+
target.addEventListener(type, listener, options);
|
|
85
|
+
return () => target.removeEventListener(type, listener, options);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const clone = value =>
|
|
89
|
+
typeof value === "object"
|
|
90
|
+
? JSON.parse(JSON.stringify(value))
|
|
91
|
+
: value;
|
|
92
|
+
|
|
93
|
+
const closest = (el, callback) => {
|
|
94
|
+
while (el && !callback(el)) {
|
|
95
|
+
el = (el._x_teleportBack ?? el).parentElement;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return el;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const create_map = keys => new Map(
|
|
102
102
|
keys.split(",").map(v => [v.trim().toLowerCase(), v.trim()]));
|
|
103
103
|
|
|
104
104
|
function watch(get_value, callback, options = null) {
|
|
@@ -112,6 +112,7 @@ function watch(get_value, callback, options = null) {
|
|
|
112
112
|
let new_value;
|
|
113
113
|
let old_value;
|
|
114
114
|
let initialized = false;
|
|
115
|
+
let timer_id;
|
|
115
116
|
|
|
116
117
|
const handle = effect(() => {
|
|
117
118
|
new_value = get_value();
|
|
@@ -123,325 +124,328 @@ function watch(get_value, callback, options = null) {
|
|
|
123
124
|
|
|
124
125
|
if (initialized || (options?.immediate ?? true)) {
|
|
125
126
|
// Prevent the watcher from detecting its own dependencies.
|
|
126
|
-
setTimeout(() => {
|
|
127
|
+
timer_id = setTimeout(() => {
|
|
127
128
|
callback(new_value, old_value);
|
|
128
129
|
old_value = new_value;
|
|
129
|
-
}
|
|
130
|
+
});
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
initialized = true;
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
return () =>
|
|
136
|
+
return () => {
|
|
137
|
+
clearTimeout(timer_id);
|
|
138
|
+
release(handle);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const canonical_names = create_map(
|
|
143
|
+
"value,checked,files," +
|
|
144
|
+
"innerHTML,innerText,textContent," +
|
|
145
|
+
"videoHeight,videoWidth," +
|
|
146
|
+
"naturalHeight,naturalWidth," +
|
|
147
|
+
"clientHeight,clientWidth,offsetHeight,offsetWidth," +
|
|
148
|
+
"indeterminate," +
|
|
149
|
+
"open," +
|
|
150
|
+
"group");
|
|
151
|
+
|
|
152
|
+
function plugin({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
|
|
153
|
+
// creating a shortcut for the directive,
|
|
154
|
+
// when an attribute name starting with & will refer to our directive,
|
|
155
|
+
// allowing us to write like this: &value="prop",
|
|
156
|
+
// which is equivalent to x-bound:value="prop"
|
|
157
|
+
mapAttributes(attr => ({
|
|
158
|
+
name: attr.name.replace(/^&/, prefixed("bound:")),
|
|
159
|
+
value: attr.value
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
|
|
163
|
+
if (!value) {
|
|
164
|
+
warn("x-bound directive expects the presence of a bound property name");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const tag_name = el.tagName.toUpperCase();
|
|
169
|
+
|
|
170
|
+
expression = expression?.trim();
|
|
171
|
+
|
|
172
|
+
// since attributes come in a lowercase,
|
|
173
|
+
// we need to convert the bound property name to its canonical form
|
|
174
|
+
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
|
|
175
|
+
|
|
176
|
+
// if the expression is omitted, then we assume it corresponds
|
|
177
|
+
// to the bound property name, allowing us to write expressions more concisely,
|
|
178
|
+
// and write &value instead of &value="value"
|
|
179
|
+
expression ||= property_name;
|
|
180
|
+
|
|
181
|
+
const get_value = create_getter(evaluateLater, el, expression);
|
|
182
|
+
const set_value = create_setter(evaluateLater, el, expression);
|
|
183
|
+
|
|
184
|
+
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
|
|
185
|
+
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
|
|
186
|
+
|
|
187
|
+
let processed;
|
|
188
|
+
|
|
189
|
+
switch (property_name) {
|
|
190
|
+
case "value":
|
|
191
|
+
process_value();
|
|
192
|
+
break;
|
|
193
|
+
|
|
194
|
+
case "checked":
|
|
195
|
+
process_checked();
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case "files":
|
|
199
|
+
process_files();
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case "innerHTML":
|
|
203
|
+
case "innerText":
|
|
204
|
+
case "textContent":
|
|
205
|
+
process_contenteditable();
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case "videoHeight":
|
|
209
|
+
case "videoWidth":
|
|
210
|
+
process_media_resize("VIDEO", "resize");
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case "naturalHeight":
|
|
214
|
+
case "naturalWidth":
|
|
215
|
+
process_media_resize("IMG", "load");
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case "clientHeight":
|
|
219
|
+
case "clientWidth":
|
|
220
|
+
case "offsetHeight":
|
|
221
|
+
case "offsetWidth":
|
|
222
|
+
process_dimensions();
|
|
223
|
+
break;
|
|
224
|
+
|
|
225
|
+
case "indeterminate":
|
|
226
|
+
process_indeterminate();
|
|
227
|
+
break;
|
|
228
|
+
|
|
229
|
+
case "open":
|
|
230
|
+
process_open_attribute();
|
|
231
|
+
break;
|
|
232
|
+
|
|
233
|
+
case "group":
|
|
234
|
+
process_group();
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!processed) {
|
|
239
|
+
const modifier =
|
|
240
|
+
has_modifier(modifiers, "in") ? "in" :
|
|
241
|
+
has_modifier(modifiers, "out") ? "out" : "inout";
|
|
242
|
+
|
|
243
|
+
const source_el = expression === value
|
|
244
|
+
? closest(el.parentNode, node => node._x_dataStack)
|
|
245
|
+
: el;
|
|
246
|
+
|
|
247
|
+
if (!el._x_dataStack) {
|
|
248
|
+
warn("x-bound directive requires the presence of the x-data directive to bind component properties");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!source_el) {
|
|
253
|
+
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const source = {
|
|
258
|
+
get: create_getter(evaluateLater, source_el, expression),
|
|
259
|
+
set: create_setter(evaluateLater, source_el, expression)
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const target = {
|
|
263
|
+
get: create_getter(evaluateLater, el, value),
|
|
264
|
+
set: create_setter(evaluateLater, el, value)
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
switch (modifier) {
|
|
268
|
+
case "in":
|
|
269
|
+
cleanup(watch(() => source.get(), v => target.set(clone(v))));
|
|
270
|
+
break;
|
|
271
|
+
case "out":
|
|
272
|
+
cleanup(watch(() => target.get(), v => source.set(clone(v))));
|
|
273
|
+
break;
|
|
274
|
+
default:
|
|
275
|
+
cleanup(entangle(source, target));
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function process_value() {
|
|
281
|
+
switch (tag_name) {
|
|
282
|
+
case "INPUT":
|
|
283
|
+
case "TEXTAREA":
|
|
284
|
+
// if the value of the bound property is "null" or "undefined",
|
|
285
|
+
// we initialize it with the value from the element.
|
|
286
|
+
is_nullish(get_value()) && update_variable();
|
|
287
|
+
|
|
288
|
+
effect(update_property);
|
|
289
|
+
cleanup(listen(el, "input", update_variable));
|
|
290
|
+
|
|
291
|
+
processed = true;
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case "SELECT":
|
|
295
|
+
// WORKAROUND:
|
|
296
|
+
// For the "select" element, there might be a situation
|
|
297
|
+
// where options are generated dynamically using the "x-for" directive,
|
|
298
|
+
// and in this case, attempting to set the "value" property
|
|
299
|
+
// will have no effect since there are no options yet.
|
|
300
|
+
// Therefore, we use a small trick to set the value a bit later
|
|
301
|
+
// when the "x-for" directive has finished its work.
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
// if the value of the bound property is "null" or "undefined",
|
|
304
|
+
// we initialize it with the value from the element.
|
|
305
|
+
is_nullish(get_value()) && update_variable();
|
|
306
|
+
|
|
307
|
+
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
|
|
308
|
+
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
processed = true;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function process_checked() {
|
|
317
|
+
if (is_checkable_input(el)) {
|
|
318
|
+
effect(update_property);
|
|
319
|
+
cleanup(listen(el, "change", update_variable));
|
|
320
|
+
processed = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function process_indeterminate() {
|
|
325
|
+
if (el.type === "checkbox") {
|
|
326
|
+
is_nullish(get_value()) && update_variable();
|
|
327
|
+
effect(update_property);
|
|
328
|
+
cleanup(listen(el, "change", update_variable));
|
|
329
|
+
processed = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function process_files() {
|
|
334
|
+
if (el.type === "file") {
|
|
335
|
+
get_value() instanceof FileList || update_variable();
|
|
336
|
+
|
|
337
|
+
effect(update_property);
|
|
338
|
+
cleanup(listen(el, "input", update_variable));
|
|
339
|
+
processed = true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function process_contenteditable() {
|
|
344
|
+
if (el.isContentEditable) {
|
|
345
|
+
is_nullish(get_value()) && update_variable();
|
|
346
|
+
|
|
347
|
+
effect(update_property);
|
|
348
|
+
cleanup(listen(el, "input", update_variable));
|
|
349
|
+
processed = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function process_media_resize(name, event_name) {
|
|
354
|
+
if (tag_name === name) {
|
|
355
|
+
update_variable();
|
|
356
|
+
cleanup(listen(el, event_name, update_variable));
|
|
357
|
+
processed = true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function process_dimensions() {
|
|
362
|
+
cleanup(observe_resize(el, update_variable));
|
|
363
|
+
processed = true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function process_open_attribute() {
|
|
367
|
+
const [is_details, is_dialog] = [tag_name === "DETAILS", tag_name === "DIALOG"];
|
|
368
|
+
|
|
369
|
+
if (is_details || is_dialog) {
|
|
370
|
+
//
|
|
371
|
+
// <details>:
|
|
372
|
+
// Supports safe two-way binding via the "open" attribute,
|
|
373
|
+
// so we initialize from the element only if the bound value
|
|
374
|
+
// is null or undefined.
|
|
375
|
+
//
|
|
376
|
+
// <dialog>:
|
|
377
|
+
// Directly setting element.open is discouraged by the spec,
|
|
378
|
+
// as it breaks native dialog behavior and the "close" event.
|
|
379
|
+
// Therefore, we always initialize state from the element
|
|
380
|
+
// and treat it as a one-way source of truth.
|
|
381
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value
|
|
382
|
+
//
|
|
383
|
+
(is_dialog || is_nullish(get_value())) && update_variable();
|
|
384
|
+
|
|
385
|
+
//
|
|
386
|
+
// Enable two-way binding only for "<details>"
|
|
387
|
+
//
|
|
388
|
+
is_details && effect(update_property);
|
|
389
|
+
cleanup(listen(el, "toggle", update_variable));
|
|
390
|
+
processed = true;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function process_group() {
|
|
395
|
+
if (is_checkable_input(el)) {
|
|
396
|
+
el.name || mutateDom(() => el.name = expression);
|
|
397
|
+
|
|
398
|
+
effect(() =>
|
|
399
|
+
mutateDom(() =>
|
|
400
|
+
apply_group_values(el, get_value() ?? [])));
|
|
401
|
+
|
|
402
|
+
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
|
|
403
|
+
processed = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function to_number(value) {
|
|
410
|
+
return value === "" ? null : +value;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function apply_select_values(el, values) {
|
|
414
|
+
for (const option of el.options) {
|
|
415
|
+
option.selected = loose_index_of(values, option.value) >= 0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function collect_selected_values(el) {
|
|
420
|
+
if (el.multiple) {
|
|
421
|
+
return [...el.selectedOptions].map(o => o.value);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return el.value;
|
|
136
425
|
}
|
|
137
426
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
warn("x-bound directive expects the presence of a bound property name");
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const tag_name = el.tagName.toUpperCase();
|
|
165
|
-
|
|
166
|
-
expression = expression?.trim();
|
|
167
|
-
|
|
168
|
-
// since attributes come in a lowercase,
|
|
169
|
-
// we need to convert the bound property name to its canonical form
|
|
170
|
-
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
|
|
171
|
-
|
|
172
|
-
// if the expression is omitted, then we assume it corresponds
|
|
173
|
-
// to the bound property name, allowing us to write expressions more concisely,
|
|
174
|
-
// and write &value instead of &value="value"
|
|
175
|
-
expression ||= property_name;
|
|
176
|
-
|
|
177
|
-
const get_value = create_getter(evaluateLater, el, expression);
|
|
178
|
-
const set_value = create_setter(evaluateLater, el, expression);
|
|
179
|
-
|
|
180
|
-
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
|
|
181
|
-
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
|
|
182
|
-
|
|
183
|
-
let processed;
|
|
184
|
-
|
|
185
|
-
switch (property_name) {
|
|
186
|
-
case "value":
|
|
187
|
-
process_value();
|
|
188
|
-
break;
|
|
189
|
-
|
|
190
|
-
case "checked":
|
|
191
|
-
process_checked();
|
|
192
|
-
break;
|
|
193
|
-
|
|
194
|
-
case "files":
|
|
195
|
-
process_files();
|
|
196
|
-
break;
|
|
197
|
-
|
|
198
|
-
case "innerHTML":
|
|
199
|
-
case "innerText":
|
|
200
|
-
case "textContent":
|
|
201
|
-
process_contenteditable();
|
|
202
|
-
break;
|
|
203
|
-
|
|
204
|
-
case "videoHeight":
|
|
205
|
-
case "videoWidth":
|
|
206
|
-
process_media_resize("VIDEO", "resize");
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case "naturalHeight":
|
|
210
|
-
case "naturalWidth":
|
|
211
|
-
process_media_resize("IMG", "load");
|
|
212
|
-
break;
|
|
213
|
-
|
|
214
|
-
case "clientHeight":
|
|
215
|
-
case "clientWidth":
|
|
216
|
-
case "offsetHeight":
|
|
217
|
-
case "offsetWidth":
|
|
218
|
-
process_dimensions();
|
|
219
|
-
break;
|
|
220
|
-
|
|
221
|
-
case "indeterminate":
|
|
222
|
-
process_indeterminate();
|
|
223
|
-
break;
|
|
224
|
-
|
|
225
|
-
case "open":
|
|
226
|
-
process_open_attribute();
|
|
227
|
-
break;
|
|
228
|
-
|
|
229
|
-
case "group":
|
|
230
|
-
process_group();
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!processed) {
|
|
235
|
-
const modifier =
|
|
236
|
-
has_modifier(modifiers, "in") ? "in" :
|
|
237
|
-
has_modifier(modifiers, "out") ? "out" : "inout";
|
|
238
|
-
|
|
239
|
-
const source_el = expression === value
|
|
240
|
-
? closest(el.parentNode, node => node._x_dataStack)
|
|
241
|
-
: el;
|
|
242
|
-
|
|
243
|
-
if (!el._x_dataStack) {
|
|
244
|
-
warn("x-bound directive requires the presence of the x-data directive to bind component properties");
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!source_el) {
|
|
249
|
-
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const source = {
|
|
254
|
-
get: create_getter(evaluateLater, source_el, expression),
|
|
255
|
-
set: create_setter(evaluateLater, source_el, expression)
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const target = {
|
|
259
|
-
get: create_getter(evaluateLater, el, value),
|
|
260
|
-
set: create_setter(evaluateLater, el, value)
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
switch (modifier) {
|
|
264
|
-
case "in":
|
|
265
|
-
cleanup(watch(() => source.get(), v => target.set(clone(v))));
|
|
266
|
-
break;
|
|
267
|
-
case "out":
|
|
268
|
-
cleanup(watch(() => target.get(), v => source.set(clone(v))));
|
|
269
|
-
break;
|
|
270
|
-
default:
|
|
271
|
-
cleanup(entangle(source, target));
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function process_value() {
|
|
277
|
-
switch (tag_name) {
|
|
278
|
-
case "INPUT":
|
|
279
|
-
case "TEXTAREA":
|
|
280
|
-
// if the value of the bound property is "null" or "undefined",
|
|
281
|
-
// we initialize it with the value from the element.
|
|
282
|
-
is_nullish(get_value()) && update_variable();
|
|
283
|
-
|
|
284
|
-
effect(update_property);
|
|
285
|
-
cleanup(listen(el, "input", update_variable));
|
|
286
|
-
|
|
287
|
-
processed = true;
|
|
288
|
-
break;
|
|
289
|
-
|
|
290
|
-
case "SELECT":
|
|
291
|
-
// WORKAROUND:
|
|
292
|
-
// For the "select" element, there might be a situation
|
|
293
|
-
// where options are generated dynamically using the "x-for" directive,
|
|
294
|
-
// and in this case, attempting to set the "value" property
|
|
295
|
-
// will have no effect since there are no options yet.
|
|
296
|
-
// Therefore, we use a small trick to set the value a bit later
|
|
297
|
-
// when the "x-for" directive has finished its work.
|
|
298
|
-
setTimeout(() => {
|
|
299
|
-
// if the value of the bound property is "null" or "undefined",
|
|
300
|
-
// we initialize it with the value from the element.
|
|
301
|
-
is_nullish(get_value()) && update_variable();
|
|
302
|
-
|
|
303
|
-
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
|
|
304
|
-
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
|
|
305
|
-
}, 0);
|
|
306
|
-
|
|
307
|
-
processed = true;
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function process_checked() {
|
|
313
|
-
if (is_checkable_input(el)) {
|
|
314
|
-
effect(update_property);
|
|
315
|
-
cleanup(listen(el, "change", update_variable));
|
|
316
|
-
processed = true;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function process_indeterminate() {
|
|
321
|
-
if (el.type === "checkbox") {
|
|
322
|
-
is_nullish(get_value()) && update_variable();
|
|
323
|
-
effect(update_property);
|
|
324
|
-
cleanup(listen(el, "change", update_variable));
|
|
325
|
-
processed = true;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function process_files() {
|
|
330
|
-
if (el.type === "file") {
|
|
331
|
-
get_value() instanceof FileList || update_variable();
|
|
332
|
-
|
|
333
|
-
effect(update_property);
|
|
334
|
-
cleanup(listen(el, "input", update_variable));
|
|
335
|
-
processed = true;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function process_contenteditable() {
|
|
340
|
-
if (el.isContentEditable) {
|
|
341
|
-
is_nullish(get_value()) && update_variable();
|
|
342
|
-
|
|
343
|
-
effect(update_property);
|
|
344
|
-
cleanup(listen(el, "input", update_variable));
|
|
345
|
-
processed = true;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function process_media_resize(name, event_name) {
|
|
350
|
-
if (tag_name === name) {
|
|
351
|
-
update_variable();
|
|
352
|
-
cleanup(listen(el, event_name, update_variable));
|
|
353
|
-
processed = true;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function process_dimensions() {
|
|
358
|
-
cleanup(observe_resize(el, update_variable));
|
|
359
|
-
processed = true;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function process_open_attribute() {
|
|
363
|
-
const [is_details, is_dialog] = [tag_name === "DETAILS", tag_name === "DIALOG"];
|
|
364
|
-
|
|
365
|
-
if (is_details || is_dialog) {
|
|
366
|
-
//
|
|
367
|
-
// <details>:
|
|
368
|
-
// Supports safe two-way binding via the "open" attribute,
|
|
369
|
-
// so we initialize from the element only if the bound value
|
|
370
|
-
// is null or undefined.
|
|
371
|
-
//
|
|
372
|
-
// <dialog>:
|
|
373
|
-
// Directly setting element.open is discouraged by the spec,
|
|
374
|
-
// as it breaks native dialog behavior and the "close" event.
|
|
375
|
-
// Therefore, we always initialize state from the element
|
|
376
|
-
// and treat it as a one-way source of truth.
|
|
377
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value
|
|
378
|
-
//
|
|
379
|
-
(is_dialog || is_nullish(get_value())) && update_variable();
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
// Enable two-way binding only for "<details>"
|
|
383
|
-
//
|
|
384
|
-
is_details && effect(update_property);
|
|
385
|
-
cleanup(listen(el, "toggle", update_variable));
|
|
386
|
-
processed = true;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function process_group() {
|
|
391
|
-
if (is_checkable_input(el)) {
|
|
392
|
-
el.name || mutateDom(() => el.name = expression);
|
|
393
|
-
|
|
394
|
-
effect(() =>
|
|
395
|
-
mutateDom(() =>
|
|
396
|
-
apply_group_values(el, get_value() ?? [])));
|
|
397
|
-
|
|
398
|
-
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
|
|
399
|
-
processed = true;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function to_number(value) {
|
|
406
|
-
return value === "" ? null : +value;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function apply_select_values(el, values) {
|
|
410
|
-
for (const option of el.options) {
|
|
411
|
-
option.selected = loose_index_of(values, option.value) >= 0;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function collect_selected_values(el) {
|
|
416
|
-
if (el.multiple) {
|
|
417
|
-
return [...el.selectedOptions].map(o => o.value);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return el.value;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function apply_group_values(el, values) {
|
|
424
|
-
el.checked = is_array(values)
|
|
425
|
-
? loose_index_of(values, el.value) >= 0
|
|
426
|
-
: loose_equal(el.value, values);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function collect_group_values(el, values) {
|
|
430
|
-
if (el.type === "radio") {
|
|
431
|
-
return el.value;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
values = as_array(values);
|
|
435
|
-
const index = loose_index_of(values, el.value);
|
|
436
|
-
|
|
437
|
-
if (el.checked) {
|
|
438
|
-
index >= 0 || values.push(el.value);
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
index >= 0 && values.splice(index, 1);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return values;
|
|
427
|
+
function apply_group_values(el, values) {
|
|
428
|
+
el.checked = is_array(values)
|
|
429
|
+
? loose_index_of(values, el.value) >= 0
|
|
430
|
+
: loose_equal(el.value, values);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function collect_group_values(el, values) {
|
|
434
|
+
if (el.type === "radio") {
|
|
435
|
+
return el.value;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
values = as_array(values);
|
|
439
|
+
const index = loose_index_of(values, el.value);
|
|
440
|
+
|
|
441
|
+
if (el.checked) {
|
|
442
|
+
index >= 0 || values.push(el.value);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
index >= 0 && values.splice(index, 1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return values;
|
|
445
449
|
}
|
|
446
450
|
|
|
447
|
-
export { plugin as bound };
|
|
451
|
+
export { plugin as bound, plugin as default };
|