@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.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");
|
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
let new_value;
|
|
116
116
|
let old_value;
|
|
117
117
|
let initialized = false;
|
|
118
|
+
let timer_id;
|
|
118
119
|
|
|
119
120
|
const handle = effect(() => {
|
|
120
121
|
new_value = get_value();
|
|
@@ -126,327 +127,330 @@
|
|
|
126
127
|
|
|
127
128
|
if (initialized || (options?.immediate ?? true)) {
|
|
128
129
|
// Prevent the watcher from detecting its own dependencies.
|
|
129
|
-
setTimeout(() => {
|
|
130
|
+
timer_id = setTimeout(() => {
|
|
130
131
|
callback(new_value, old_value);
|
|
131
132
|
old_value = new_value;
|
|
132
|
-
}
|
|
133
|
+
});
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
initialized = true;
|
|
136
137
|
});
|
|
137
138
|
|
|
138
|
-
return () =>
|
|
139
|
+
return () => {
|
|
140
|
+
clearTimeout(timer_id);
|
|
141
|
+
release(handle);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const canonical_names = create_map(
|
|
146
|
+
"value,checked,files," +
|
|
147
|
+
"innerHTML,innerText,textContent," +
|
|
148
|
+
"videoHeight,videoWidth," +
|
|
149
|
+
"naturalHeight,naturalWidth," +
|
|
150
|
+
"clientHeight,clientWidth,offsetHeight,offsetWidth," +
|
|
151
|
+
"indeterminate," +
|
|
152
|
+
"open," +
|
|
153
|
+
"group");
|
|
154
|
+
|
|
155
|
+
function plugin({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
|
|
156
|
+
// creating a shortcut for the directive,
|
|
157
|
+
// when an attribute name starting with & will refer to our directive,
|
|
158
|
+
// allowing us to write like this: &value="prop",
|
|
159
|
+
// which is equivalent to x-bound:value="prop"
|
|
160
|
+
mapAttributes(attr => ({
|
|
161
|
+
name: attr.name.replace(/^&/, prefixed("bound:")),
|
|
162
|
+
value: attr.value
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
|
|
166
|
+
if (!value) {
|
|
167
|
+
warn("x-bound directive expects the presence of a bound property name");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const tag_name = el.tagName.toUpperCase();
|
|
172
|
+
|
|
173
|
+
expression = expression?.trim();
|
|
174
|
+
|
|
175
|
+
// since attributes come in a lowercase,
|
|
176
|
+
// we need to convert the bound property name to its canonical form
|
|
177
|
+
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
|
|
178
|
+
|
|
179
|
+
// if the expression is omitted, then we assume it corresponds
|
|
180
|
+
// to the bound property name, allowing us to write expressions more concisely,
|
|
181
|
+
// and write &value instead of &value="value"
|
|
182
|
+
expression ||= property_name;
|
|
183
|
+
|
|
184
|
+
const get_value = create_getter(evaluateLater, el, expression);
|
|
185
|
+
const set_value = create_setter(evaluateLater, el, expression);
|
|
186
|
+
|
|
187
|
+
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
|
|
188
|
+
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
|
|
189
|
+
|
|
190
|
+
let processed;
|
|
191
|
+
|
|
192
|
+
switch (property_name) {
|
|
193
|
+
case "value":
|
|
194
|
+
process_value();
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case "checked":
|
|
198
|
+
process_checked();
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case "files":
|
|
202
|
+
process_files();
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case "innerHTML":
|
|
206
|
+
case "innerText":
|
|
207
|
+
case "textContent":
|
|
208
|
+
process_contenteditable();
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "videoHeight":
|
|
212
|
+
case "videoWidth":
|
|
213
|
+
process_media_resize("VIDEO", "resize");
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case "naturalHeight":
|
|
217
|
+
case "naturalWidth":
|
|
218
|
+
process_media_resize("IMG", "load");
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case "clientHeight":
|
|
222
|
+
case "clientWidth":
|
|
223
|
+
case "offsetHeight":
|
|
224
|
+
case "offsetWidth":
|
|
225
|
+
process_dimensions();
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case "indeterminate":
|
|
229
|
+
process_indeterminate();
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case "open":
|
|
233
|
+
process_open_attribute();
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case "group":
|
|
237
|
+
process_group();
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!processed) {
|
|
242
|
+
const modifier =
|
|
243
|
+
has_modifier(modifiers, "in") ? "in" :
|
|
244
|
+
has_modifier(modifiers, "out") ? "out" : "inout";
|
|
245
|
+
|
|
246
|
+
const source_el = expression === value
|
|
247
|
+
? closest(el.parentNode, node => node._x_dataStack)
|
|
248
|
+
: el;
|
|
249
|
+
|
|
250
|
+
if (!el._x_dataStack) {
|
|
251
|
+
warn("x-bound directive requires the presence of the x-data directive to bind component properties");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!source_el) {
|
|
256
|
+
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const source = {
|
|
261
|
+
get: create_getter(evaluateLater, source_el, expression),
|
|
262
|
+
set: create_setter(evaluateLater, source_el, expression)
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const target = {
|
|
266
|
+
get: create_getter(evaluateLater, el, value),
|
|
267
|
+
set: create_setter(evaluateLater, el, value)
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
switch (modifier) {
|
|
271
|
+
case "in":
|
|
272
|
+
cleanup(watch(() => source.get(), v => target.set(clone(v))));
|
|
273
|
+
break;
|
|
274
|
+
case "out":
|
|
275
|
+
cleanup(watch(() => target.get(), v => source.set(clone(v))));
|
|
276
|
+
break;
|
|
277
|
+
default:
|
|
278
|
+
cleanup(entangle(source, target));
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function process_value() {
|
|
284
|
+
switch (tag_name) {
|
|
285
|
+
case "INPUT":
|
|
286
|
+
case "TEXTAREA":
|
|
287
|
+
// if the value of the bound property is "null" or "undefined",
|
|
288
|
+
// we initialize it with the value from the element.
|
|
289
|
+
is_nullish(get_value()) && update_variable();
|
|
290
|
+
|
|
291
|
+
effect(update_property);
|
|
292
|
+
cleanup(listen(el, "input", update_variable));
|
|
293
|
+
|
|
294
|
+
processed = true;
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case "SELECT":
|
|
298
|
+
// WORKAROUND:
|
|
299
|
+
// For the "select" element, there might be a situation
|
|
300
|
+
// where options are generated dynamically using the "x-for" directive,
|
|
301
|
+
// and in this case, attempting to set the "value" property
|
|
302
|
+
// will have no effect since there are no options yet.
|
|
303
|
+
// Therefore, we use a small trick to set the value a bit later
|
|
304
|
+
// when the "x-for" directive has finished its work.
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
// if the value of the bound property is "null" or "undefined",
|
|
307
|
+
// we initialize it with the value from the element.
|
|
308
|
+
is_nullish(get_value()) && update_variable();
|
|
309
|
+
|
|
310
|
+
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
|
|
311
|
+
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
processed = true;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function process_checked() {
|
|
320
|
+
if (is_checkable_input(el)) {
|
|
321
|
+
effect(update_property);
|
|
322
|
+
cleanup(listen(el, "change", update_variable));
|
|
323
|
+
processed = true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function process_indeterminate() {
|
|
328
|
+
if (el.type === "checkbox") {
|
|
329
|
+
is_nullish(get_value()) && update_variable();
|
|
330
|
+
effect(update_property);
|
|
331
|
+
cleanup(listen(el, "change", update_variable));
|
|
332
|
+
processed = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function process_files() {
|
|
337
|
+
if (el.type === "file") {
|
|
338
|
+
get_value() instanceof FileList || update_variable();
|
|
339
|
+
|
|
340
|
+
effect(update_property);
|
|
341
|
+
cleanup(listen(el, "input", update_variable));
|
|
342
|
+
processed = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function process_contenteditable() {
|
|
347
|
+
if (el.isContentEditable) {
|
|
348
|
+
is_nullish(get_value()) && update_variable();
|
|
349
|
+
|
|
350
|
+
effect(update_property);
|
|
351
|
+
cleanup(listen(el, "input", update_variable));
|
|
352
|
+
processed = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function process_media_resize(name, event_name) {
|
|
357
|
+
if (tag_name === name) {
|
|
358
|
+
update_variable();
|
|
359
|
+
cleanup(listen(el, event_name, update_variable));
|
|
360
|
+
processed = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function process_dimensions() {
|
|
365
|
+
cleanup(observe_resize(el, update_variable));
|
|
366
|
+
processed = true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function process_open_attribute() {
|
|
370
|
+
const [is_details, is_dialog] = [tag_name === "DETAILS", tag_name === "DIALOG"];
|
|
371
|
+
|
|
372
|
+
if (is_details || is_dialog) {
|
|
373
|
+
//
|
|
374
|
+
// <details>:
|
|
375
|
+
// Supports safe two-way binding via the "open" attribute,
|
|
376
|
+
// so we initialize from the element only if the bound value
|
|
377
|
+
// is null or undefined.
|
|
378
|
+
//
|
|
379
|
+
// <dialog>:
|
|
380
|
+
// Directly setting element.open is discouraged by the spec,
|
|
381
|
+
// as it breaks native dialog behavior and the "close" event.
|
|
382
|
+
// Therefore, we always initialize state from the element
|
|
383
|
+
// and treat it as a one-way source of truth.
|
|
384
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value
|
|
385
|
+
//
|
|
386
|
+
(is_dialog || is_nullish(get_value())) && update_variable();
|
|
387
|
+
|
|
388
|
+
//
|
|
389
|
+
// Enable two-way binding only for "<details>"
|
|
390
|
+
//
|
|
391
|
+
is_details && effect(update_property);
|
|
392
|
+
cleanup(listen(el, "toggle", update_variable));
|
|
393
|
+
processed = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function process_group() {
|
|
398
|
+
if (is_checkable_input(el)) {
|
|
399
|
+
el.name || mutateDom(() => el.name = expression);
|
|
400
|
+
|
|
401
|
+
effect(() =>
|
|
402
|
+
mutateDom(() =>
|
|
403
|
+
apply_group_values(el, get_value() ?? [])));
|
|
404
|
+
|
|
405
|
+
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
|
|
406
|
+
processed = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function to_number(value) {
|
|
413
|
+
return value === "" ? null : +value;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function apply_select_values(el, values) {
|
|
417
|
+
for (const option of el.options) {
|
|
418
|
+
option.selected = loose_index_of(values, option.value) >= 0;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function collect_selected_values(el) {
|
|
423
|
+
if (el.multiple) {
|
|
424
|
+
return [...el.selectedOptions].map(o => o.value);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return el.value;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function apply_group_values(el, values) {
|
|
431
|
+
el.checked = is_array(values)
|
|
432
|
+
? loose_index_of(values, el.value) >= 0
|
|
433
|
+
: loose_equal(el.value, values);
|
|
139
434
|
}
|
|
140
435
|
|
|
141
|
-
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
}, 0);
|
|
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;
|
|
436
|
+
function collect_group_values(el, values) {
|
|
437
|
+
if (el.type === "radio") {
|
|
438
|
+
return el.value;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
values = as_array(values);
|
|
442
|
+
const index = loose_index_of(values, el.value);
|
|
443
|
+
|
|
444
|
+
if (el.checked) {
|
|
445
|
+
index >= 0 || values.push(el.value);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
index >= 0 && values.splice(index, 1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return values;
|
|
448
452
|
}
|
|
449
453
|
|
|
450
|
-
document
|
|
454
|
+
listen(document, "alpine:init", () => Alpine.plugin(plugin));
|
|
451
455
|
|
|
452
456
|
})();
|