@ramstack/alpinegear-main 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.
@@ -37,70 +37,70 @@ 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 is_template = el => el.matches("template");
73
- const is_element = el => el.nodeType === Node.ELEMENT_NODE;
74
- const as_array = value => is_array(value) ? value : [value];
75
- const loose_equal = (a, b) => a == b;
76
- const loose_index_of = (array, value) => array.findIndex(v => v == value);
77
- const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
78
-
79
- function assert(value, message) {
80
- if (!value) {
81
- throw new Error(message);
82
- }
83
- }
84
-
85
- const listen = (target, type, listener, options) => {
86
- target.addEventListener(type, listener, options);
87
- return () => target.removeEventListener(type, listener, options);
88
- };
89
-
90
- const clone = value =>
91
- typeof value === "object"
92
- ? JSON.parse(JSON.stringify(value))
93
- : value;
94
-
95
- const closest = (el, callback) => {
96
- while (el && !callback(el)) {
97
- el = (el._x_teleportBack ?? el).parentElement;
98
- }
99
-
100
- return el;
101
- };
102
-
103
- 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 is_template = el => el.matches("template");
73
+ const is_element = el => el.nodeType === Node.ELEMENT_NODE;
74
+ const as_array = value => is_array(value) ? value : [value];
75
+ const loose_equal = (a, b) => a == b;
76
+ const loose_index_of = (array, value) => array.findIndex(v => v == value);
77
+ const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
78
+
79
+ function assert(value, message) {
80
+ if (!value) {
81
+ throw new Error(message);
82
+ }
83
+ }
84
+
85
+ const listen = (target, type, listener, options) => {
86
+ target.addEventListener(type, listener, options);
87
+ return () => target.removeEventListener(type, listener, options);
88
+ };
89
+
90
+ const clone = value =>
91
+ typeof value === "object"
92
+ ? JSON.parse(JSON.stringify(value))
93
+ : value;
94
+
95
+ const closest = (el, callback) => {
96
+ while (el && !callback(el)) {
97
+ el = (el._x_teleportBack ?? el).parentElement;
98
+ }
99
+
100
+ return el;
101
+ };
102
+
103
+ const create_map = keys => new Map(
104
104
  keys.split(",").map(v => [v.trim().toLowerCase(), v.trim()]));
105
105
 
106
106
  function watch(get_value, callback, options = null) {
@@ -114,6 +114,7 @@ function watch(get_value, callback, options = null) {
114
114
  let new_value;
115
115
  let old_value;
116
116
  let initialized = false;
117
+ let timer_id;
117
118
 
118
119
  const handle = effect(() => {
119
120
  new_value = get_value();
@@ -125,581 +126,600 @@ function watch(get_value, callback, options = null) {
125
126
 
126
127
  if (initialized || (options?.immediate ?? true)) {
127
128
  // Prevent the watcher from detecting its own dependencies.
128
- setTimeout(() => {
129
+ timer_id = setTimeout(() => {
129
130
  callback(new_value, old_value);
130
131
  old_value = new_value;
131
- }, 0);
132
+ });
132
133
  }
133
134
 
134
135
  initialized = true;
135
136
  });
136
137
 
137
- return () => release(handle);
138
+ return () => {
139
+ clearTimeout(timer_id);
140
+ release(handle);
141
+ }
142
+ }
143
+
144
+ const canonical_names = create_map(
145
+ "value,checked,files," +
146
+ "innerHTML,innerText,textContent," +
147
+ "videoHeight,videoWidth," +
148
+ "naturalHeight,naturalWidth," +
149
+ "clientHeight,clientWidth,offsetHeight,offsetWidth," +
150
+ "indeterminate," +
151
+ "open," +
152
+ "group");
153
+
154
+ function plugin$6({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
155
+ // creating a shortcut for the directive,
156
+ // when an attribute name starting with & will refer to our directive,
157
+ // allowing us to write like this: &value="prop",
158
+ // which is equivalent to x-bound:value="prop"
159
+ mapAttributes(attr => ({
160
+ name: attr.name.replace(/^&/, prefixed("bound:")),
161
+ value: attr.value
162
+ }));
163
+
164
+ directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
165
+ if (!value) {
166
+ warn("x-bound directive expects the presence of a bound property name");
167
+ return;
168
+ }
169
+
170
+ const tag_name = el.tagName.toUpperCase();
171
+
172
+ expression = expression?.trim();
173
+
174
+ // since attributes come in a lowercase,
175
+ // we need to convert the bound property name to its canonical form
176
+ const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
177
+
178
+ // if the expression is omitted, then we assume it corresponds
179
+ // to the bound property name, allowing us to write expressions more concisely,
180
+ // and write &value instead of &value="value"
181
+ expression ||= property_name;
182
+
183
+ const get_value = create_getter(evaluateLater, el, expression);
184
+ const set_value = create_setter(evaluateLater, el, expression);
185
+
186
+ const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
187
+ const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
188
+
189
+ let processed;
190
+
191
+ switch (property_name) {
192
+ case "value":
193
+ process_value();
194
+ break;
195
+
196
+ case "checked":
197
+ process_checked();
198
+ break;
199
+
200
+ case "files":
201
+ process_files();
202
+ break;
203
+
204
+ case "innerHTML":
205
+ case "innerText":
206
+ case "textContent":
207
+ process_contenteditable();
208
+ break;
209
+
210
+ case "videoHeight":
211
+ case "videoWidth":
212
+ process_media_resize("VIDEO", "resize");
213
+ break;
214
+
215
+ case "naturalHeight":
216
+ case "naturalWidth":
217
+ process_media_resize("IMG", "load");
218
+ break;
219
+
220
+ case "clientHeight":
221
+ case "clientWidth":
222
+ case "offsetHeight":
223
+ case "offsetWidth":
224
+ process_dimensions();
225
+ break;
226
+
227
+ case "indeterminate":
228
+ process_indeterminate();
229
+ break;
230
+
231
+ case "open":
232
+ process_open_attribute();
233
+ break;
234
+
235
+ case "group":
236
+ process_group();
237
+ break;
238
+ }
239
+
240
+ if (!processed) {
241
+ const modifier =
242
+ has_modifier(modifiers, "in") ? "in" :
243
+ has_modifier(modifiers, "out") ? "out" : "inout";
244
+
245
+ const source_el = expression === value
246
+ ? closest(el.parentNode, node => node._x_dataStack)
247
+ : el;
248
+
249
+ if (!el._x_dataStack) {
250
+ warn("x-bound directive requires the presence of the x-data directive to bind component properties");
251
+ return;
252
+ }
253
+
254
+ if (!source_el) {
255
+ warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
256
+ return;
257
+ }
258
+
259
+ const source = {
260
+ get: create_getter(evaluateLater, source_el, expression),
261
+ set: create_setter(evaluateLater, source_el, expression)
262
+ };
263
+
264
+ const target = {
265
+ get: create_getter(evaluateLater, el, value),
266
+ set: create_setter(evaluateLater, el, value)
267
+ };
268
+
269
+ switch (modifier) {
270
+ case "in":
271
+ cleanup(watch(() => source.get(), v => target.set(clone(v))));
272
+ break;
273
+ case "out":
274
+ cleanup(watch(() => target.get(), v => source.set(clone(v))));
275
+ break;
276
+ default:
277
+ cleanup(entangle(source, target));
278
+ break;
279
+ }
280
+ }
281
+
282
+ function process_value() {
283
+ switch (tag_name) {
284
+ case "INPUT":
285
+ case "TEXTAREA":
286
+ // if the value of the bound property is "null" or "undefined",
287
+ // we initialize it with the value from the element.
288
+ is_nullish(get_value()) && update_variable();
289
+
290
+ effect(update_property);
291
+ cleanup(listen(el, "input", update_variable));
292
+
293
+ processed = true;
294
+ break;
295
+
296
+ case "SELECT":
297
+ // WORKAROUND:
298
+ // For the "select" element, there might be a situation
299
+ // where options are generated dynamically using the "x-for" directive,
300
+ // and in this case, attempting to set the "value" property
301
+ // will have no effect since there are no options yet.
302
+ // Therefore, we use a small trick to set the value a bit later
303
+ // when the "x-for" directive has finished its work.
304
+ setTimeout(() => {
305
+ // if the value of the bound property is "null" or "undefined",
306
+ // we initialize it with the value from the element.
307
+ is_nullish(get_value()) && update_variable();
308
+
309
+ effect(() => apply_select_values(el, as_array(get_value() ?? [])));
310
+ cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
311
+ });
312
+
313
+ processed = true;
314
+ break;
315
+ }
316
+ }
317
+
318
+ function process_checked() {
319
+ if (is_checkable_input(el)) {
320
+ effect(update_property);
321
+ cleanup(listen(el, "change", update_variable));
322
+ processed = true;
323
+ }
324
+ }
325
+
326
+ function process_indeterminate() {
327
+ if (el.type === "checkbox") {
328
+ is_nullish(get_value()) && update_variable();
329
+ effect(update_property);
330
+ cleanup(listen(el, "change", update_variable));
331
+ processed = true;
332
+ }
333
+ }
334
+
335
+ function process_files() {
336
+ if (el.type === "file") {
337
+ get_value() instanceof FileList || update_variable();
338
+
339
+ effect(update_property);
340
+ cleanup(listen(el, "input", update_variable));
341
+ processed = true;
342
+ }
343
+ }
344
+
345
+ function process_contenteditable() {
346
+ if (el.isContentEditable) {
347
+ is_nullish(get_value()) && update_variable();
348
+
349
+ effect(update_property);
350
+ cleanup(listen(el, "input", update_variable));
351
+ processed = true;
352
+ }
353
+ }
354
+
355
+ function process_media_resize(name, event_name) {
356
+ if (tag_name === name) {
357
+ update_variable();
358
+ cleanup(listen(el, event_name, update_variable));
359
+ processed = true;
360
+ }
361
+ }
362
+
363
+ function process_dimensions() {
364
+ cleanup(observe_resize(el, update_variable));
365
+ processed = true;
366
+ }
367
+
368
+ function process_open_attribute() {
369
+ const [is_details, is_dialog] = [tag_name === "DETAILS", tag_name === "DIALOG"];
370
+
371
+ if (is_details || is_dialog) {
372
+ //
373
+ // <details>:
374
+ // Supports safe two-way binding via the "open" attribute,
375
+ // so we initialize from the element only if the bound value
376
+ // is null or undefined.
377
+ //
378
+ // <dialog>:
379
+ // Directly setting element.open is discouraged by the spec,
380
+ // as it breaks native dialog behavior and the "close" event.
381
+ // Therefore, we always initialize state from the element
382
+ // and treat it as a one-way source of truth.
383
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value
384
+ //
385
+ (is_dialog || is_nullish(get_value())) && update_variable();
386
+
387
+ //
388
+ // Enable two-way binding only for "<details>"
389
+ //
390
+ is_details && effect(update_property);
391
+ cleanup(listen(el, "toggle", update_variable));
392
+ processed = true;
393
+ }
394
+ }
395
+
396
+ function process_group() {
397
+ if (is_checkable_input(el)) {
398
+ el.name || mutateDom(() => el.name = expression);
399
+
400
+ effect(() =>
401
+ mutateDom(() =>
402
+ apply_group_values(el, get_value() ?? [])));
403
+
404
+ cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
405
+ processed = true;
406
+ }
407
+ }
408
+ });
409
+ }
410
+
411
+ function to_number(value) {
412
+ return value === "" ? null : +value;
413
+ }
414
+
415
+ function apply_select_values(el, values) {
416
+ for (const option of el.options) {
417
+ option.selected = loose_index_of(values, option.value) >= 0;
418
+ }
419
+ }
420
+
421
+ function collect_selected_values(el) {
422
+ if (el.multiple) {
423
+ return [...el.selectedOptions].map(o => o.value);
424
+ }
425
+
426
+ return el.value;
427
+ }
428
+
429
+ function apply_group_values(el, values) {
430
+ el.checked = is_array(values)
431
+ ? loose_index_of(values, el.value) >= 0
432
+ : loose_equal(el.value, values);
433
+ }
434
+
435
+ function collect_group_values(el, values) {
436
+ if (el.type === "radio") {
437
+ return el.value;
438
+ }
439
+
440
+ values = as_array(values);
441
+ const index = loose_index_of(values, el.value);
442
+
443
+ if (el.checked) {
444
+ index >= 0 || values.push(el.value);
445
+ }
446
+ else {
447
+ index >= 0 && values.splice(index, 1);
448
+ }
449
+
450
+ return values;
451
+ }
452
+
453
+ function plugin$5({ directive, evaluateLater, mutateDom }) {
454
+ directive("format", (el, { modifiers }, { effect }) => {
455
+ const is_once = has_modifier(modifiers, "once");
456
+ const has_format_attr = el => el.hasAttribute("x-format");
457
+
458
+ process(el);
459
+
460
+ function update(callback) {
461
+ if (is_once) {
462
+ mutateDom(() => callback());
463
+ }
464
+ else {
465
+ effect(() => mutateDom(() => callback()));
466
+ }
467
+ }
468
+
469
+ function process(node) {
470
+ switch (node.nodeType) {
471
+ case Node.TEXT_NODE:
472
+ process_text_node(node);
473
+ break;
474
+
475
+ case Node.ELEMENT_NODE:
476
+ if (node !== el) {
477
+ //
478
+ // When we encounter an element with the "x-data" attribute, its properties
479
+ // are not yet initialized, and the Alpine context is unavailable.
480
+ // Attempting to use these properties will result in
481
+ // an "Alpine Expression Error: [expression] is not defined".
482
+ //
483
+ // Workaround:
484
+ // To avoid this, we manually add our "x-format" directive to the element.
485
+ // Alpine evaluates "x-format" directive once the context is initialized.
486
+ // In the current loop, we skip these elements to defer their processing.
487
+ //
488
+ // This also handles cases where the user manually adds the "x-format" attribute.
489
+ //
490
+ if (node.hasAttribute("x-data") && !has_format_attr(node)) {
491
+ node.setAttribute("x-format", "");
492
+ }
493
+
494
+ if (has_format_attr(node)) {
495
+ break;
496
+ }
497
+ }
498
+
499
+ process_nodes(node);
500
+ process_attributes(node);
501
+ break;
502
+ }
503
+ }
504
+
505
+ function process_text_node(node) {
506
+ const tokens = node.textContent.split(/{{(?<expr>.+?)}}/g);
507
+
508
+ if (tokens.length > 1) {
509
+ const fragment = new DocumentFragment();
510
+
511
+ for (let i = 0; i < tokens.length; i++) {
512
+ if ((i % 2) === 0) {
513
+ fragment.appendChild(document.createTextNode(tokens[i]));
514
+ }
515
+ else {
516
+ const get_value = create_getter(evaluateLater, node.parentNode, tokens[i]);
517
+ const text = document.createTextNode("");
518
+
519
+ fragment.append(text);
520
+ update(() => text.textContent = get_value());
521
+ }
522
+ }
523
+
524
+ mutateDom(() =>
525
+ node.parentElement.replaceChild(fragment, node));
526
+ }
527
+ }
528
+
529
+ function process_attributes(node) {
530
+ for (let attr of node.attributes) {
531
+ const matches = [...attr.value.matchAll(/{{(?<expr>.+?)}}/g)];
532
+ if (matches.length) {
533
+ const getters = new Map(
534
+ matches.map(m => [
535
+ m.groups.expr,
536
+ create_getter(
537
+ evaluateLater,
538
+ node,
539
+ m.groups.expr)]));
540
+
541
+ const template = attr.value;
542
+
543
+ update(() => attr.value = template.replace(/{{(?<expr>.+?)}}/g, (_, expr) => getters.get(expr)()));
544
+ }
545
+ }
546
+ }
547
+
548
+ function process_nodes(node) {
549
+ for (let child of node.childNodes) {
550
+ process(child);
551
+ }
552
+ }
553
+ });
138
554
  }
139
555
 
140
- const canonical_names = create_map(
141
- "value,checked,files," +
142
- "innerHTML,innerText,textContent," +
143
- "videoHeight,videoWidth," +
144
- "naturalHeight,naturalWidth," +
145
- "clientHeight,clientWidth,offsetHeight,offsetWidth," +
146
- "indeterminate," +
147
- "open," +
148
- "group");
149
-
150
- function plugin$5({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
151
- // creating a shortcut for the directive,
152
- // when an attribute name starting with & will refer to our directive,
153
- // allowing us to write like this: &value="prop",
154
- // which is equivalent to x-bound:value="prop"
155
- mapAttributes(attr => ({
156
- name: attr.name.replace(/^&/, prefixed("bound:")),
157
- value: attr.value
158
- }));
159
-
160
- directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
161
- if (!value) {
162
- warn("x-bound directive expects the presence of a bound property name");
163
- return;
164
- }
165
-
166
- const tag_name = el.tagName.toUpperCase();
167
-
168
- expression = expression?.trim();
169
-
170
- // since attributes come in a lowercase,
171
- // we need to convert the bound property name to its canonical form
172
- const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
173
-
174
- // if the expression is omitted, then we assume it corresponds
175
- // to the bound property name, allowing us to write expressions more concisely,
176
- // and write &value instead of &value="value"
177
- expression ||= property_name;
178
-
179
- const get_value = create_getter(evaluateLater, el, expression);
180
- const set_value = create_setter(evaluateLater, el, expression);
181
-
182
- const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
183
- const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
184
-
185
- let processed;
186
-
187
- switch (property_name) {
188
- case "value":
189
- process_value();
190
- break;
191
-
192
- case "checked":
193
- process_checked();
194
- break;
195
-
196
- case "files":
197
- process_files();
198
- break;
199
-
200
- case "innerHTML":
201
- case "innerText":
202
- case "textContent":
203
- process_contenteditable();
204
- break;
205
-
206
- case "videoHeight":
207
- case "videoWidth":
208
- process_media_resize("VIDEO", "resize");
209
- break;
210
-
211
- case "naturalHeight":
212
- case "naturalWidth":
213
- process_media_resize("IMG", "load");
214
- break;
215
-
216
- case "clientHeight":
217
- case "clientWidth":
218
- case "offsetHeight":
219
- case "offsetWidth":
220
- process_dimensions();
221
- break;
222
-
223
- case "indeterminate":
224
- process_indeterminate();
225
- break;
226
-
227
- case "open":
228
- process_open_attribute();
229
- break;
230
-
231
- case "group":
232
- process_group();
233
- break;
234
- }
235
-
236
- if (!processed) {
237
- const modifier =
238
- has_modifier(modifiers, "in") ? "in" :
239
- has_modifier(modifiers, "out") ? "out" : "inout";
240
-
241
- const source_el = expression === value
242
- ? closest(el.parentNode, node => node._x_dataStack)
243
- : el;
244
-
245
- if (!el._x_dataStack) {
246
- warn("x-bound directive requires the presence of the x-data directive to bind component properties");
247
- return;
248
- }
249
-
250
- if (!source_el) {
251
- warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
252
- return;
253
- }
254
-
255
- const source = {
256
- get: create_getter(evaluateLater, source_el, expression),
257
- set: create_setter(evaluateLater, source_el, expression)
258
- };
259
-
260
- const target = {
261
- get: create_getter(evaluateLater, el, value),
262
- set: create_setter(evaluateLater, el, value)
263
- };
264
-
265
- switch (modifier) {
266
- case "in":
267
- cleanup(watch(() => source.get(), v => target.set(clone(v))));
268
- break;
269
- case "out":
270
- cleanup(watch(() => target.get(), v => source.set(clone(v))));
271
- break;
272
- default:
273
- cleanup(entangle(source, target));
274
- break;
275
- }
276
- }
277
-
278
- function process_value() {
279
- switch (tag_name) {
280
- case "INPUT":
281
- case "TEXTAREA":
282
- // if the value of the bound property is "null" or "undefined",
283
- // we initialize it with the value from the element.
284
- is_nullish(get_value()) && update_variable();
285
-
286
- effect(update_property);
287
- cleanup(listen(el, "input", update_variable));
288
-
289
- processed = true;
290
- break;
291
-
292
- case "SELECT":
293
- // WORKAROUND:
294
- // For the "select" element, there might be a situation
295
- // where options are generated dynamically using the "x-for" directive,
296
- // and in this case, attempting to set the "value" property
297
- // will have no effect since there are no options yet.
298
- // Therefore, we use a small trick to set the value a bit later
299
- // when the "x-for" directive has finished its work.
300
- setTimeout(() => {
301
- // if the value of the bound property is "null" or "undefined",
302
- // we initialize it with the value from the element.
303
- is_nullish(get_value()) && update_variable();
304
-
305
- effect(() => apply_select_values(el, as_array(get_value() ?? [])));
306
- cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
307
- }, 0);
308
-
309
- processed = true;
310
- break;
311
- }
312
- }
313
-
314
- function process_checked() {
315
- if (is_checkable_input(el)) {
316
- effect(update_property);
317
- cleanup(listen(el, "change", update_variable));
318
- processed = true;
319
- }
320
- }
321
-
322
- function process_indeterminate() {
323
- if (el.type === "checkbox") {
324
- is_nullish(get_value()) && update_variable();
325
- effect(update_property);
326
- cleanup(listen(el, "change", update_variable));
327
- processed = true;
328
- }
329
- }
330
-
331
- function process_files() {
332
- if (el.type === "file") {
333
- get_value() instanceof FileList || update_variable();
334
-
335
- effect(update_property);
336
- cleanup(listen(el, "input", update_variable));
337
- processed = true;
338
- }
339
- }
340
-
341
- function process_contenteditable() {
342
- if (el.isContentEditable) {
343
- is_nullish(get_value()) && update_variable();
344
-
345
- effect(update_property);
346
- cleanup(listen(el, "input", update_variable));
347
- processed = true;
348
- }
349
- }
350
-
351
- function process_media_resize(name, event_name) {
352
- if (tag_name === name) {
353
- update_variable();
354
- cleanup(listen(el, event_name, update_variable));
355
- processed = true;
356
- }
357
- }
358
-
359
- function process_dimensions() {
360
- cleanup(observe_resize(el, update_variable));
361
- processed = true;
362
- }
363
-
364
- function process_open_attribute() {
365
- const [is_details, is_dialog] = [tag_name === "DETAILS", tag_name === "DIALOG"];
366
-
367
- if (is_details || is_dialog) {
368
- //
369
- // <details>:
370
- // Supports safe two-way binding via the "open" attribute,
371
- // so we initialize from the element only if the bound value
372
- // is null or undefined.
373
- //
374
- // <dialog>:
375
- // Directly setting element.open is discouraged by the spec,
376
- // as it breaks native dialog behavior and the "close" event.
377
- // Therefore, we always initialize state from the element
378
- // and treat it as a one-way source of truth.
379
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value
380
- //
381
- (is_dialog || is_nullish(get_value())) && update_variable();
382
-
383
- //
384
- // Enable two-way binding only for "<details>"
385
- //
386
- is_details && effect(update_property);
387
- cleanup(listen(el, "toggle", update_variable));
388
- processed = true;
389
- }
390
- }
391
-
392
- function process_group() {
393
- if (is_checkable_input(el)) {
394
- el.name || mutateDom(() => el.name = expression);
395
-
396
- effect(() =>
397
- mutateDom(() =>
398
- apply_group_values(el, get_value() ?? [])));
399
-
400
- cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
401
- processed = true;
402
- }
403
- }
404
- });
405
- }
406
-
407
- function to_number(value) {
408
- return value === "" ? null : +value;
409
- }
410
-
411
- function apply_select_values(el, values) {
412
- for (const option of el.options) {
413
- option.selected = loose_index_of(values, option.value) >= 0;
414
- }
415
- }
416
-
417
- function collect_selected_values(el) {
418
- if (el.multiple) {
419
- return [...el.selectedOptions].map(o => o.value);
420
- }
421
-
422
- return el.value;
423
- }
424
-
425
- function apply_group_values(el, values) {
426
- el.checked = is_array(values)
427
- ? loose_index_of(values, el.value) >= 0
428
- : loose_equal(el.value, values);
429
- }
430
-
431
- function collect_group_values(el, values) {
432
- if (el.type === "radio") {
433
- return el.value;
434
- }
435
-
436
- values = as_array(values);
437
- const index = loose_index_of(values, el.value);
438
-
439
- if (el.checked) {
440
- index >= 0 || values.push(el.value);
441
- }
442
- else {
443
- index >= 0 && values.splice(index, 1);
444
- }
445
-
446
- return values;
556
+ function anchor_block(el, template, { addScopeToNode, cleanup, initTree, mutateDom, scope = {} }) {
557
+ if (el._r_block) {
558
+ return;
559
+ }
560
+
561
+ initialize();
562
+
563
+ let nodes = is_template(template)
564
+ ? [...template.content.cloneNode(true).childNodes]
565
+ : [template.cloneNode(true)];
566
+
567
+ mutateDom(() => {
568
+ for (let node of nodes) {
569
+ is_element(node) && addScopeToNode(node, scope, el);
570
+ el.parentElement.insertBefore(node, el);
571
+ is_element(node) && initTree(node);
572
+ }
573
+ });
574
+
575
+ el._r_block = {
576
+ template,
577
+ update() {
578
+ mutateDom(() => {
579
+ for (let node of nodes ?? []) {
580
+ el.parentElement.insertBefore(node, el);
581
+ }
582
+ });
583
+ },
584
+ delete() {
585
+ el._r_block = null;
586
+ for (let node of nodes ?? []) {
587
+ node.remove();
588
+ }
589
+ nodes = null;
590
+ }
591
+ };
592
+
593
+ cleanup(() => el._r_block?.delete());
447
594
  }
448
595
 
449
- function plugin$4({ directive, evaluateLater, mutateDom }) {
450
- directive("format", (el, { modifiers }, { effect }) => {
451
- const placeholder_regex = /{{(?<expr>.+?)}}/g;
452
- const is_once = has_modifier(modifiers, "once");
453
- const has_format_attr = el => el.hasAttribute("x-format");
454
-
455
- process(el);
456
-
457
- function update(callback) {
458
- if (is_once) {
459
- mutateDom(() => callback());
460
- }
461
- else {
462
- effect(() => mutateDom(() => callback()));
463
- }
464
- }
465
-
466
- function process(node) {
467
- switch (node.nodeType) {
468
- case Node.TEXT_NODE:
469
- process_text_node(node);
470
- break;
471
-
472
- case Node.ELEMENT_NODE:
473
- if (node !== el) {
474
- //
475
- // When we encounter an element with the "x-data" attribute, its properties
476
- // are not yet initialized, and the Alpine context is unavailable.
477
- // Attempting to use these properties will result in
478
- // an "Alpine Expression Error: [expression] is not defined".
479
- //
480
- // Workaround:
481
- // To avoid this, we manually add our "x-format" directive to the element.
482
- // Alpine evaluates "x-format" directive once the context is initialized.
483
- // In the current loop, we skip these elements to defer their processing.
484
- //
485
- // This also handles cases where the user manually adds the "x-format" attribute.
486
- //
487
- if (node.hasAttribute("x-data") && !has_format_attr(node)) {
488
- node.setAttribute("x-format", "");
489
- }
490
-
491
- if (has_format_attr(node)) {
492
- break;
493
- }
494
- }
495
-
496
-
497
- process_nodes(node);
498
- process_attributes(node);
499
- break;
500
- }
501
- }
502
-
503
- function process_text_node(node) {
504
- const tokens = node.textContent.split(placeholder_regex);
505
-
506
- if (tokens.length > 1) {
507
- const fragment = new DocumentFragment();
508
-
509
- for (let i = 0; i < tokens.length; i++) {
510
- if ((i % 2) === 0) {
511
- fragment.appendChild(document.createTextNode(tokens[i]));
512
- }
513
- else {
514
- const get_value = create_getter(evaluateLater, node.parentNode, tokens[i]);
515
- const text = document.createTextNode("");
516
-
517
- fragment.append(text);
518
- update(() => text.textContent = get_value());
519
- }
520
- }
521
-
522
- mutateDom(() =>
523
- node.parentElement.replaceChild(fragment, node));
524
- }
525
- }
526
-
527
- function process_attributes(node) {
528
- for (let attr of node.attributes) {
529
- const matches = [...attr.value.matchAll(placeholder_regex)];
530
- if (matches.length) {
531
- const template = attr.value;
532
- update(() => attr.value = template.replace(placeholder_regex, (_, expr) => create_getter(evaluateLater, node, expr)()));
533
- }
534
- }
535
- }
536
-
537
- function process_nodes(node) {
538
- for (let child of node.childNodes) {
539
- process(child);
540
- }
541
- }
542
- });
596
+ function initialize() {
597
+ document.body._r_block ??= (() => {
598
+ const observer = new MutationObserver(mutations => {
599
+ for (let mutation of mutations) {
600
+ for (let node of mutation.addedNodes) {
601
+ node._r_block?.update();
602
+ }
603
+ }
604
+ });
605
+
606
+ observer.observe(document.body, { childList: true, subtree: true });
607
+ return observer;
608
+ })();
543
609
  }
544
610
 
545
- function anchor_block(el, template, { addScopeToNode, cleanup, initTree, mutateDom, scope = {} }) {
546
- if (el._r_block) {
547
- return;
548
- }
549
-
550
- initialize();
551
-
552
- let nodes = is_template(template)
553
- ? [...template.content.cloneNode(true).childNodes]
554
- : [template.cloneNode(true)];
555
-
556
- mutateDom(() => {
557
- for (let node of nodes) {
558
- is_element(node) && addScopeToNode(node, scope, el);
559
- el.parentElement.insertBefore(node, el);
560
- is_element(node) && initTree(node);
561
- }
562
- });
563
-
564
- el._r_block = {
565
- template,
566
- update() {
567
- mutateDom(() => {
568
- for (let node of nodes ?? []) {
569
- el.parentElement.insertBefore(node, el);
570
- }
571
- });
572
- },
573
- delete() {
574
- el._r_block = null;
575
- for (let node of nodes ?? []) {
576
- node.remove();
577
- }
578
- nodes = null;
579
- }
580
- };
581
-
582
- cleanup(() => el._r_block?.delete());
583
- }
584
-
585
- function initialize() {
586
- document.body._r_block ??= (() => {
587
- const observer = new MutationObserver(mutations => {
588
- for (let mutation of mutations) {
589
- for (let node of mutation.addedNodes) {
590
- node._r_block?.update();
591
- }
592
- }
593
- });
594
-
595
- observer.observe(document.body, { childList: true, subtree: true });
596
- return observer;
597
- })();
611
+ function plugin$4({ addScopeToNode, directive, initTree, mutateDom }) {
612
+ directive("fragment", (el, {}, { cleanup }) => {
613
+ if (!is_template(el)) {
614
+ warn("x-fragment can only be used on a 'template' tag");
615
+ return;
616
+ }
617
+
618
+ anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
619
+ });
598
620
  }
599
621
 
600
- function plugin$3({ addScopeToNode, directive, initTree, mutateDom }) {
601
- directive("fragment", (el, {}, { cleanup }) => {
602
- if (!is_template(el)) {
603
- warn("x-fragment can only be used on a 'template' tag");
604
- return;
605
- }
606
-
607
- anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
608
- });
622
+ function plugin$3({ addScopeToNode, directive, initTree, mutateDom }) {
623
+ directive("match", (el, { }, { cleanup, effect, evaluateLater }) => {
624
+ if (!is_template(el)) {
625
+ warn("x-match can only be used on a 'template' tag");
626
+ return;
627
+ }
628
+
629
+ const branches = [];
630
+ const has_default_case = () => branches.some(b => b.default);
631
+
632
+ for (let node of el.content.children) {
633
+ const expr = node.getAttribute("x-case");
634
+ if (expr !== null) {
635
+ has_default_case() && warn("The x-case directive cannot be appear after x-default");
636
+ branches.push({ el: node, get_value: create_getter(evaluateLater, expr) });
637
+ }
638
+ else if (node.hasAttribute("x-default")) {
639
+ has_default_case() && warn("Only one x-default directive is allowed");
640
+ branches.push({ el: node, get_value: () => true, default: true });
641
+ }
642
+ else {
643
+ warn("Element has no x-case or x-default directive and will be ignored", node);
644
+ }
645
+ }
646
+
647
+ const activate = branch => {
648
+ if (el._r_block?.template !== branch.el) {
649
+ clear();
650
+ anchor_block(el, branch.el, {
651
+ addScopeToNode,
652
+ cleanup,
653
+ initTree,
654
+ mutateDom
655
+ });
656
+ }
657
+ };
658
+
659
+ const clear = () => el._r_block?.delete();
660
+
661
+ effect(() => {
662
+ let active;
663
+
664
+ for (let branch of branches) {
665
+ if (branch.get_value() && !active) {
666
+ active = branch;
667
+ }
668
+ }
669
+
670
+ active ? activate(active) : clear();
671
+ });
672
+ });
609
673
  }
610
674
 
611
- function plugin$2({ addScopeToNode, directive, initTree, mutateDom }) {
612
- directive("match", (el, { }, { cleanup, effect, evaluateLater }) => {
613
- if (!is_template(el)) {
614
- warn("x-match can only be used on a 'template' tag");
615
- return;
616
- }
617
-
618
- const branches = [];
619
- const has_default_case = () => branches.some(b => b.default);
620
-
621
- for (let node of el.content.children) {
622
- const expr = node.getAttribute("x-case");
623
- if (expr !== null) {
624
- has_default_case() && warn("The x-case directive cannot be appear after x-default");
625
- branches.push({ el: node, get_value: create_getter(evaluateLater, expr) });
626
- }
627
- else if (node.hasAttribute("x-default")) {
628
- has_default_case() && warn("Only one x-default directive is allowed");
629
- branches.push({ el: node, get_value: () => true, default: true });
630
- }
631
- else {
632
- warn("Element has no x-case or x-default directive and will be ignored", node);
633
- }
634
- }
635
-
636
- const activate = branch => {
637
- if (el._r_block?.template !== branch.el) {
638
- clear();
639
- anchor_block(el, branch.el, {
640
- addScopeToNode,
641
- cleanup,
642
- initTree,
643
- mutateDom
644
- });
645
- }
646
- };
647
-
648
- const clear = () => el._r_block?.delete();
649
-
650
- effect(() => {
651
- let active;
652
-
653
- for (let branch of branches) {
654
- if (branch.get_value() && !active) {
655
- active = branch;
656
- }
657
- }
658
-
659
- active ? activate(active) : clear();
660
- });
661
- });
675
+ function plugin$2(alpine) {
676
+ alpine.directive("template", (el, { expression }) => {
677
+ if (is_template(el)) {
678
+ warn("x-template cannot be used on a 'template' tag");
679
+ return;
680
+ }
681
+
682
+ const tpl = document.getElementById(expression);
683
+
684
+ if (!tpl || !is_template(tpl)) {
685
+ warn("x-template directive can only reference the template tag");
686
+ return;
687
+ }
688
+
689
+ // Adding a queued task ensures asynchronous content update, allowing Alpine.js
690
+ // to handle context propagation for cloned elements properly.
691
+ // This is important because manipulation can occur within the mutateDom function
692
+ // when mutation observing is disabled, preventing proper context propagation
693
+ // for cloned elements.
694
+ queueMicrotask(() => {
695
+ el.innerHTML = "";
696
+ el.append(tpl.content.cloneNode(true));
697
+ });
698
+ });
662
699
  }
663
700
 
664
- function plugin$1(alpine) {
665
- alpine.directive("template", (el, { expression }) => {
666
- if (is_template(el)) {
667
- warn("x-template cannot be used on a 'template' tag");
668
- return;
669
- }
670
-
671
- const tpl = document.getElementById(expression);
672
-
673
- if (!is_template(tpl)) {
674
- warn("x-template directive can only reference the template tag");
675
- return;
676
- }
677
-
678
- // Adding a queued task ensures asynchronous content update, allowing Alpine.js
679
- // to handle context propagation for cloned elements properly.
680
- // This is important because manipulation can occur within the mutateDom function
681
- // when mutation observing is disabled, preventing proper context propagation
682
- // for cloned elements.
683
- queueMicrotask(() => {
684
- el.innerHTML = "";
685
- el.append(tpl.content.cloneNode(true));
686
- });
687
- });
701
+ function plugin$1({ addScopeToNode, directive, initTree, mutateDom }) {
702
+ directive("when", (el, { expression }, { cleanup, effect, evaluateLater }) => {
703
+ if (!is_template(el)) {
704
+ warn("x-when can only be used on a 'template' tag");
705
+ return;
706
+ }
707
+
708
+ const activate = () => anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
709
+ const clear = () => el._r_block?.delete();
710
+
711
+ const get = create_getter(evaluateLater, expression);
712
+ effect(() => get() ? activate() : clear());
713
+ });
688
714
  }
689
715
 
690
- function plugin({ addScopeToNode, directive, initTree, mutateDom }) {
691
- directive("when", (el, { expression }, { cleanup, effect, evaluateLater }) => {
692
- if (!is_template(el)) {
693
- warn("x-when can only be used on a 'template' tag");
694
- return;
695
- }
696
-
697
- const activate = () => anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
698
- const clear = () => el._r_block?.delete();
699
-
700
- const get = create_getter(evaluateLater, expression);
701
- effect(() => get() ? activate() : clear());
702
- });
716
+ function plugin(alpine) {
717
+ plugin$6(alpine);
718
+ plugin$5(alpine);
719
+ plugin$4(alpine);
720
+ plugin$3(alpine);
721
+ plugin$2(alpine);
722
+ plugin$1(alpine);
703
723
  }
704
724
 
705
- export { plugin$5 as bound, plugin$4 as format, plugin$3 as fragment, plugin$2 as match, plugin$1 as template, plugin as when };
725
+ export { plugin$6 as bound, plugin as default, plugin$5 as format, plugin$4 as fragment, plugin$3 as match, plugin$2 as template, plugin$1 as when };