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