@oscarpalmer/abydon 0.17.0 → 0.19.0

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.
@@ -1,4 +1,4 @@
1
- //#region node_modules/@oscarpalmer/mora/dist/constants.js
1
+ //#region node_modules/@oscarpalmer/mora/dist/constants.mjs
2
2
  const ACTIVE = {};
3
3
  const BATCH = {
4
4
  depth: 0,
@@ -31,7 +31,7 @@ const NAME_ALL = new Set([
31
31
  NAME_STORE
32
32
  ]);
33
33
  //#endregion
34
- //#region node_modules/@oscarpalmer/mora/dist/effect.js
34
+ //#region node_modules/@oscarpalmer/mora/dist/effect.mjs
35
35
  var Effect = class {
36
36
  constructor(callback) {
37
37
  Object.defineProperty(this, NAME_MORA, { value: NAME_EFFECT });
@@ -39,40 +39,70 @@ var Effect = class {
39
39
  runEffect(this);
40
40
  }
41
41
  };
42
- function runEffect(effect$1) {
42
+ function runEffect(effect) {
43
43
  const previousEffect = ACTIVE.effect;
44
- ACTIVE.effect = effect$1;
44
+ ACTIVE.effect = effect;
45
45
  try {
46
- effect$1.state.callback();
46
+ effect.state.callback();
47
47
  } finally {
48
48
  ACTIVE.effect = previousEffect;
49
49
  }
50
50
  }
51
+ /**
52
+ * Create an effect
53
+ * @param callback Callback for handling signal effects
54
+ * @returns Effect
55
+ */
51
56
  function effect(callback) {
52
57
  return typeof callback === "function" ? new Effect(callback) : void 0;
53
58
  }
54
59
  //#endregion
55
- //#region node_modules/@oscarpalmer/mora/dist/helpers/is.js
60
+ //#region node_modules/@oscarpalmer/mora/dist/helpers/is.mjs
61
+ /**
62
+ * Is the value a reactive array?
63
+ * @param value Value to check
64
+ * @returns True if value is a {@link ReactiveArray}
65
+ */
56
66
  function isArray(value) {
57
67
  return isMora(value, NAME_ARRAY);
58
68
  }
69
+ /**
70
+ * Is the value a computed signal?
71
+ * @param value Value to check
72
+ * @returns True if value is a {@link Computed}
73
+ */
59
74
  function isComputed(value) {
60
75
  return isMora(value, NAME_COMPUTED);
61
76
  }
77
+ /**
78
+ * Is the value an effect?
79
+ * @param value Value to check
80
+ * @returns True if value is an {@link Effect}
81
+ */
62
82
  function isEffect(value) {
63
83
  return isMora(value, NAME_EFFECT);
64
84
  }
65
85
  function isMora(value, name) {
66
86
  return typeof value === "object" && value != null && "$mora" in value && (typeof name === "string" ? value["$mora"] === name : name.has(value["$mora"]));
67
87
  }
88
+ /**
89
+ * Is the value reactive?
90
+ * @param value Value to check
91
+ * @returns True if value is a {@link Reactive}
92
+ */
68
93
  function isReactive(value) {
69
94
  return isMora(value, NAME_ALL);
70
95
  }
96
+ /**
97
+ * Is the value a signal?
98
+ * @param value Value to check
99
+ * @returns True if value is a {@link Signal}
100
+ */
71
101
  function isSignal(value) {
72
102
  return isMora(value, NAME_SIGNAL);
73
103
  }
74
104
  //#endregion
75
- //#region node_modules/@oscarpalmer/mora/dist/batch.js
105
+ //#region node_modules/@oscarpalmer/mora/dist/batch.mjs
76
106
  function flushHandlers() {
77
107
  while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
78
108
  const handlers = [...BATCH.handlers];
@@ -81,15 +111,23 @@ function flushHandlers() {
81
111
  else handler.callback(handler.state.value);
82
112
  }
83
113
  }
114
+ /**
115
+ * Start batching effects
116
+ *
117
+ * _(Use {@link stopBatch} to flush and run batched effects)_
118
+ */
84
119
  function startBatch() {
85
120
  BATCH.depth += 1;
86
121
  }
122
+ /**
123
+ * Stop batching effects and flush _(run)_ them
124
+ */
87
125
  function stopBatch() {
88
126
  if (BATCH.depth > 0) BATCH.depth -= 1;
89
127
  flushHandlers();
90
128
  }
91
129
  //#endregion
92
- //#region node_modules/@oscarpalmer/mora/dist/subscription.js
130
+ //#region node_modules/@oscarpalmer/mora/dist/subscription.mjs
93
131
  var Subscription = class {
94
132
  callback;
95
133
  state;
@@ -116,7 +154,7 @@ function unsubscribe(state, callback) {
116
154
  state.subscriptions.delete(callback);
117
155
  }
118
156
  //#endregion
119
- //#region node_modules/@oscarpalmer/mora/dist/value/reactive.js
157
+ //#region node_modules/@oscarpalmer/mora/dist/value/reactive.mjs
120
158
  var Reactive = class {
121
159
  state = {
122
160
  computeds: /* @__PURE__ */ new Set(),
@@ -130,24 +168,45 @@ var Reactive = class {
130
168
  if (typeof options === "object" && typeof options.equal === "function") this.state.equal = options.equal;
131
169
  Object.defineProperty(this, NAME_MORA, { value: name });
132
170
  }
171
+ /**
172
+ * Get the value _(without reactivity)_
173
+ * @return Current value
174
+ */
133
175
  peek() {
134
176
  return this.state.value;
135
177
  }
178
+ /**
179
+ * Subscribe to changes
180
+ * @param callback Callback for changes
181
+ * @return Unsubscribe callback
182
+ */
136
183
  subscribe(callback) {
137
184
  return subscribe(this.state, callback);
138
185
  }
186
+ /**
187
+ * JSON representation of the value
188
+ * @return JSON value
189
+ */
139
190
  toJSON() {
140
191
  return this.get();
141
192
  }
193
+ /**
194
+ * String representation of the value
195
+ * @return Value as string
196
+ */
142
197
  toString() {
143
198
  return String(this.get());
144
199
  }
200
+ /**
201
+ * Unsubscribe from changes
202
+ * @param callback Callback to unsubscribe
203
+ */
145
204
  unsubscribe(callback) {
146
205
  unsubscribe(this.state, callback);
147
206
  }
148
207
  };
149
208
  //#endregion
150
- //#region node_modules/@oscarpalmer/mora/dist/value/computed.js
209
+ //#region node_modules/@oscarpalmer/mora/dist/value/computed.mjs
151
210
  var Computed = class extends Reactive {
152
211
  effect = {
153
212
  dirty: true,
@@ -163,13 +222,16 @@ var Computed = class extends Reactive {
163
222
  ACTIVE.computed = previousComputed;
164
223
  if (!this.state.equal(this.state.value, value)) {
165
224
  this.state.value = value;
166
- for (const computed$1 of this.state.computeds) computed$1.effect.dirty = true;
167
- for (const effect$1 of this.state.effects) BATCH.handlers.add(effect$1);
225
+ for (const computed of this.state.computeds) computed.effect.dirty = true;
226
+ for (const effect of this.state.effects) BATCH.handlers.add(effect);
168
227
  for (const [, subscription] of this.state.subscriptions) subscription.callback(value);
169
228
  }
170
229
  this.effect.dirty = false;
171
230
  });
172
231
  }
232
+ /**
233
+ * @inheritdoc
234
+ */
173
235
  get() {
174
236
  if (ACTIVE.computed != null && this !== ACTIVE.computed) this.state.computeds.add(ACTIVE.computed);
175
237
  if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) this.state.effects.add(ACTIVE.effect);
@@ -177,11 +239,17 @@ var Computed = class extends Reactive {
177
239
  return this.state.value;
178
240
  }
179
241
  };
242
+ /**
243
+ * Create a computed value
244
+ * @param callback Callback to compute the value
245
+ * @param options Reactivity options
246
+ * @returns Computed value
247
+ */
180
248
  function computed(callback, options) {
181
249
  return new Computed(callback, options);
182
250
  }
183
251
  //#endregion
184
- //#region node_modules/@oscarpalmer/mora/dist/helpers/value.js
252
+ //#region node_modules/@oscarpalmer/mora/dist/helpers/value.mjs
185
253
  function emitValue(state) {
186
254
  for (const computed of state.computeds) computed.effect.dirty = true;
187
255
  for (const effect of state.effects) BATCH.handlers.add(effect);
@@ -207,29 +275,46 @@ function getValue$1(state) {
207
275
  return state.value;
208
276
  }
209
277
  //#endregion
210
- //#region node_modules/@oscarpalmer/mora/dist/value/signal.js
278
+ //#region node_modules/@oscarpalmer/mora/dist/value/signal.mjs
211
279
  var Signal = class extends Reactive {
212
280
  constructor(value, options) {
213
281
  super(NAME_SIGNAL, value, options);
214
282
  }
283
+ /**
284
+ * @inheritdoc
285
+ */
215
286
  get() {
216
287
  return getValue$1(this.state);
217
288
  }
289
+ /**
290
+ * Set the value
291
+ * @param value New value
292
+ */
218
293
  set(value) {
219
294
  if (!this.state.equal(this.state.value, value)) {
220
295
  this.state.value = value;
221
296
  emitValue(this.state);
222
297
  }
223
298
  }
299
+ /**
300
+ * Update the value _(based on the current value)_
301
+ * @param callback Callback to update the value
302
+ */
224
303
  update(callback) {
225
304
  this.set(callback(this.state.value));
226
305
  }
227
306
  };
307
+ /**
308
+ * Create a reactive value
309
+ * @param value Initial value
310
+ * @param options Reactivity options
311
+ * @returns Reactive value
312
+ */
228
313
  function signal(value, options) {
229
314
  return new Signal(value, options);
230
315
  }
231
316
  //#endregion
232
- //#region node_modules/@oscarpalmer/mora/dist/helpers/proxy.js
317
+ //#region node_modules/@oscarpalmer/mora/dist/helpers/proxy.mjs
233
318
  function emityProxyValues(state, mapped) {
234
319
  const values = [...mapped.values()];
235
320
  const { length } = values;
@@ -274,23 +359,29 @@ function setValueInProxy(parameters) {
274
359
  return true;
275
360
  }
276
361
  //#endregion
277
- //#region node_modules/@oscarpalmer/mora/dist/value/array.js
362
+ //#region node_modules/@oscarpalmer/mora/dist/value/array.mjs
278
363
  var ReactiveArray = class extends Reactive {
279
364
  #indiced = /* @__PURE__ */ new Map();
280
365
  #size = signal(0);
366
+ /**
367
+ * The length of the array
368
+ */
281
369
  get length() {
282
370
  return this.#size.get();
283
371
  }
372
+ /**
373
+ * Set the length of the array
374
+ */
284
375
  set length(value) {
285
376
  if (typeof value === "number" && value >= 0 && value !== this.state.value.length) this.get().length = value;
286
377
  }
287
378
  constructor(value, options) {
288
379
  super(NAME_ARRAY, new Proxy(value, {
289
380
  get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, this.state, this.#size) : Reflect.get(target, property),
290
- set: (target, property, value$1) => setValueInProxy({
381
+ set: (target, property, value) => setValueInProxy({
291
382
  property,
292
383
  target,
293
- value: value$1,
384
+ value,
294
385
  isArray: true,
295
386
  state: this.state,
296
387
  length: this.#size
@@ -298,9 +389,17 @@ var ReactiveArray = class extends Reactive {
298
389
  }), options);
299
390
  this.#size.set(value.length);
300
391
  }
392
+ /**
393
+ * Clear the array
394
+ */
301
395
  clear() {
302
396
  this.length = 0;
303
397
  }
398
+ /**
399
+ * Create a computed, filtered array
400
+ * @param callback Callback to evaluate each item
401
+ * @return Computed array of filtered items
402
+ */
304
403
  filter(callback) {
305
404
  return computed(() => this.get().filter(callback));
306
405
  }
@@ -308,9 +407,20 @@ var ReactiveArray = class extends Reactive {
308
407
  if (typeof first === "number") return getReactiveValueInProxy(this, this.#indiced, first, true).get();
309
408
  return first === "length" ? this.length : getValue$1(this.state);
310
409
  }
410
+ /**
411
+ * Create a computed, mapped array
412
+ * @param callback Callback to transform each item
413
+ * @return Computed array of mapped items
414
+ */
311
415
  map(callback) {
312
416
  return computed(() => this.get().map(callback));
313
417
  }
418
+ /**
419
+ * Notify dependents of changes
420
+ *
421
+ * _This bypasses equality checks and will immediately notify dependents.
422
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
423
+ */
314
424
  notify() {
315
425
  emityProxyValues(this.state, this.#indiced);
316
426
  }
@@ -318,9 +428,18 @@ var ReactiveArray = class extends Reactive {
318
428
  if (value === "length") return this.#size.peek();
319
429
  return typeof value === "number" ? this.state.value.at(value) : [...this.state.value];
320
430
  }
431
+ /**
432
+ * Remove and return the last item of the array
433
+ * @returns Removed item, or `undefined` if the array is empty
434
+ */
321
435
  pop() {
322
436
  return this.state.value.pop();
323
437
  }
438
+ /**
439
+ * Add items to the end of the array
440
+ * @param items Items to add
441
+ * @returns New array length
442
+ */
324
443
  push(...items) {
325
444
  return this.state.value.push(...items);
326
445
  }
@@ -329,9 +448,20 @@ var ReactiveArray = class extends Reactive {
329
448
  else if (first === "length") this.length = second;
330
449
  else if (typeof first === "number" && !Number.isNaN(first)) setAtIndex(this.state.value, first, second);
331
450
  }
451
+ /**
452
+ * Remove and return the first item of the array
453
+ * @returns Removed item, or `undefined` if the array is empty
454
+ */
332
455
  shift() {
333
456
  return this.state.value.shift();
334
457
  }
458
+ /**
459
+ * Remove and return items from the array _(and optionally add new items)_
460
+ * @param from Index to start removing items from
461
+ * @param to Index to stop removing items at _(defaults to the end of the array)_
462
+ * @param items Optional items to add
463
+ * @returns Removed items
464
+ */
335
465
  splice(from, to, ...items) {
336
466
  return this.state.value.splice(from, to ?? this.state.value.length, ...items);
337
467
  }
@@ -339,54 +469,105 @@ var ReactiveArray = class extends Reactive {
339
469
  if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(this, this.#indiced, first, true).subscribe(second);
340
470
  return typeof first === "function" ? subscribe(this.state, first) : noop$1;
341
471
  }
472
+ /**
473
+ * Add items to the beginning of the array
474
+ * @param items Items to add
475
+ * @returns New array length
476
+ */
342
477
  unshift(...items) {
343
478
  return this.state.value.unshift(...items);
344
479
  }
480
+ /**
481
+ * Update the value _(based on the current value)_
482
+ * @param callback Callback to update the value
483
+ */
345
484
  update(callback) {
346
485
  const updated = callback(this.state.value);
347
486
  if (updated == null || Array.isArray(updated)) this.set(updated);
348
487
  }
349
488
  };
489
+ /**
490
+ * Create a reactive array
491
+ * @param value Initial array of items
492
+ * @param options Reactivity options
493
+ * @returns Reactive array
494
+ */
350
495
  function array(value, options) {
351
496
  return new ReactiveArray(Array.isArray(value) ? value : [], options);
352
497
  }
353
- function updateArray(type, array$1, state, length) {
498
+ function updateArray(type, array, state, length) {
354
499
  const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
355
- const previousArray = affectsLength ? [] : [...array$1];
356
- const previousLength = array$1.length;
500
+ const previousArray = affectsLength ? [] : [...array];
501
+ const previousLength = array.length;
357
502
  return (...args) => {
358
- const result = array$1[type](...args);
359
- if (affectsLength ? array$1.length !== previousLength : !equalArrays(state, previousArray, array$1)) {
503
+ const result = array[type](...args);
504
+ if (affectsLength ? array.length !== previousLength : !equalArrays(state, previousArray, array)) {
360
505
  emitValue(state);
361
- length.set(array$1.length);
506
+ length.set(array.length);
362
507
  }
363
508
  return result;
364
509
  };
365
510
  }
366
- function setAtIndex(array$1, index, value) {
367
- const actual = index < 0 ? array$1.length + index : index;
368
- if (actual > -1) array$1[actual] = value;
511
+ function setAtIndex(array, index, value) {
512
+ const actual = index < 0 ? array.length + index : index;
513
+ if (actual > -1) array[actual] = value;
369
514
  }
370
515
  //#endregion
371
- //#region node_modules/@oscarpalmer/mora/node_modules/@oscarpalmer/atoms/dist/internal/is.js
516
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/is.mjs
517
+ /**
518
+ * Is the value a key?
519
+ * @param value Value to check
520
+ * @returns `true` if the value is a `Key` _(`number` or `string`)_, otherwise `false`
521
+ */
372
522
  function isKey(value) {
373
523
  return typeof value === "number" || typeof value === "string";
374
524
  }
375
- function isPlainObject$1(value) {
525
+ /**
526
+ * Is the value a plain object?
527
+ * @param value Value to check
528
+ * @returns `true` if the value is a plain object, otherwise `false`
529
+ */
530
+ function isPlainObject(value) {
376
531
  if (value === null || typeof value !== "object") return false;
377
532
  if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
378
533
  const prototype = Object.getPrototypeOf(value);
379
534
  return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
380
535
  }
381
536
  //#endregion
382
- //#region node_modules/@oscarpalmer/mora/dist/value/store.js
537
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/string.mjs
538
+ /**
539
+ * Get the string value from any value
540
+ * @param value Original value
541
+ * @returns String representation of the value
542
+ */
543
+ function getString(value) {
544
+ if (typeof value === "string") return value;
545
+ if (value == null) return "";
546
+ if (typeof value === "function") return getString(value());
547
+ if (typeof value !== "object") return String(value);
548
+ const asString = String(value.valueOf?.() ?? value);
549
+ return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
550
+ }
551
+ //#endregion
552
+ //#region node_modules/@oscarpalmer/atoms/dist/is.mjs
553
+ /**
554
+ * Is the value `undefined`, `null`, or a whitespace-only string?
555
+ * @param value Value to check
556
+ * @returns `true` if the value is nullable or a whitespace-only string, otherwise `false`
557
+ */
558
+ function isNullableOrWhitespace(value) {
559
+ return value == null || EXPRESSION_WHITESPACE$1.test(getString(value));
560
+ }
561
+ const EXPRESSION_WHITESPACE$1 = /^\s*$/;
562
+ //#endregion
563
+ //#region node_modules/@oscarpalmer/mora/dist/value/store.mjs
383
564
  var Store = class extends Reactive {
384
565
  #keyed = /* @__PURE__ */ new Map();
385
566
  constructor(value, options) {
386
- super(NAME_STORE, new Proxy(value, { set: (target, property, value$1) => setValueInProxy({
567
+ super(NAME_STORE, new Proxy(value, { set: (target, property, value) => setValueInProxy({
387
568
  target,
388
569
  property,
389
- value: value$1,
570
+ value,
390
571
  isArray: false,
391
572
  state: this.state
392
573
  }) }), options);
@@ -394,6 +575,12 @@ var Store = class extends Reactive {
394
575
  get(key) {
395
576
  return isKey(key) ? getReactiveValueInProxy(this, this.#keyed, key, false).get() : getValue$1(this.state);
396
577
  }
578
+ /**
579
+ * Notify dependents of changes
580
+ *
581
+ * _This bypasses equality checks and will immediately notify dependents.
582
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
583
+ */
397
584
  notify() {
398
585
  emityProxyValues(this.state, this.#keyed);
399
586
  }
@@ -402,59 +589,30 @@ var Store = class extends Reactive {
402
589
  }
403
590
  set(first, second) {
404
591
  if (isKey(first)) this.state.value[first] = second;
405
- else if (first == null || isPlainObject$1(first)) setProxyValue(this.state.value, first ?? {});
592
+ else if (first == null || isPlainObject(first)) setProxyValue(this.state.value, first ?? {});
406
593
  }
407
594
  subscribe(first, second) {
408
595
  if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(this, this.#keyed, first, false).subscribe(second);
409
596
  return typeof first === "function" ? subscribe(this.state, first) : noop$1;
410
597
  }
598
+ /**
599
+ * Update the value _(based on the current value)_
600
+ * @param callback Callback to update the value
601
+ */
411
602
  update(callback) {
412
603
  const updated = callback({ ...this.state.value });
413
- if (updated == null || isPlainObject$1(updated)) setProxyValue(this.state.value, updated ?? {});
604
+ if (updated == null || isPlainObject(updated)) setProxyValue(this.state.value, updated ?? {});
414
605
  }
415
606
  };
416
- function store(value, options) {
417
- return new Store(isPlainObject$1(value) ? value : {}, options);
418
- }
419
- //#endregion
420
- //#region node_modules/@oscarpalmer/atoms/dist/internal/is.mjs
421
- /**
422
- * Is the value a plain object?
423
- * @param value Value to check
424
- * @returns `true` if the value is a plain object, otherwise `false`
425
- */
426
- function isPlainObject(value) {
427
- if (value === null || typeof value !== "object") return false;
428
- if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
429
- const prototype = Object.getPrototypeOf(value);
430
- return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
431
- }
432
- //#endregion
433
- //#region node_modules/@oscarpalmer/atoms/dist/internal/string.mjs
434
- /**
435
- * Get the string value from any value
436
- * @param value Original value
437
- * @returns String representation of the value
438
- */
439
- function getString(value) {
440
- if (typeof value === "string") return value;
441
- if (value == null) return "";
442
- if (typeof value === "function") return getString(value());
443
- if (typeof value !== "object") return String(value);
444
- const asString = String(value.valueOf?.() ?? value);
445
- return asString.startsWith("[object ") ? JSON.stringify(value) : asString;
446
- }
447
- //#endregion
448
- //#region node_modules/@oscarpalmer/atoms/dist/is.mjs
449
607
  /**
450
- * Is the value `undefined`, `null`, or a whitespace-only string?
451
- * @param value Value to check
452
- * @returns `true` if the value is nullable or a whitespace-only string, otherwise `false`
608
+ * Create a reactive store
609
+ * @param value Initial object value
610
+ * @param options Reactivity options
611
+ * @returns Reactive store
453
612
  */
454
- function isNullableOrWhitespace(value) {
455
- return value == null || EXPRESSION_WHITESPACE$1.test(getString(value));
613
+ function store(value, options) {
614
+ return new Store(isPlainObject(value) ? value : {}, options);
456
615
  }
457
- const EXPRESSION_WHITESPACE$1 = /^\s*$/;
458
616
  //#endregion
459
617
  //#region node_modules/@oscarpalmer/toretto/dist/internal/is.mjs
460
618
  /**
@@ -760,15 +918,24 @@ let parser;
760
918
  let templates = {};
761
919
  //#endregion
762
920
  //#region src/constants.ts
921
+ const ARRAY_COMPARISON_ADDED = "added";
922
+ const ARRAY_COMPARISON_DISSIMILAR = "dissimilar";
923
+ const ARRAY_COMPARISON_REMOVED = "removed";
924
+ const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
925
+ const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
926
+ const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
927
+ const EVENT_CHANGE = "change";
928
+ const EVENT_INPUT = "input";
929
+ const EVENT_SUBMIT = "submit";
763
930
  const EVENT_DEFAULTS = {
764
931
  A: "click",
765
932
  BUTTON: "click",
766
933
  DETAILS: "toggle",
767
- FORM: "submit",
768
- SELECT: "change",
769
- TEXTAREA: "input"
934
+ FORM: EVENT_SUBMIT,
935
+ SELECT: EVENT_CHANGE,
936
+ TEXTAREA: EVENT_INPUT
770
937
  };
771
- const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
938
+ const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?[\w-]+)="<!--abydon.(\d+)-->"/g;
772
939
  const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
773
940
  const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
774
941
  const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
@@ -783,14 +950,31 @@ const EXPRESSION_EVENT_PREFIX = /^@/;
783
950
  const EXPRESSION_PERIOD = /\./;
784
951
  const NAME_FRAGMENT = "$fragment";
785
952
  const NAME_FRAGMENTS = "$fragments";
953
+ const PROPERTY_CHECKED = "checked";
954
+ const PROPERTY_VALUE = "value";
955
+ //#endregion
956
+ //#region node_modules/@oscarpalmer/atoms/dist/string/index.mjs
957
+ /**
958
+ * Parse a JSON string into its proper value _(or `undefined` if it fails)_
959
+ * @param value JSON string to parse
960
+ * @param reviver Reviver function to transform the parsed values
961
+ * @returns Parsed value or `undefined` if parsing fails
962
+ */
963
+ function parse$1(value, reviver) {
964
+ try {
965
+ return JSON.parse(value, reviver);
966
+ } catch {
967
+ return;
968
+ }
969
+ }
786
970
  //#endregion
787
971
  //#region src/helpers/index.ts
788
972
  function compareArrays(first, second) {
789
973
  const firstIsLarger = first.length > second.length;
790
974
  const from = firstIsLarger ? first : second;
791
975
  const to = firstIsLarger ? second : first;
792
- if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return COMPARISON_DISSIMILAR;
793
- return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
976
+ if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return ARRAY_COMPARISON_DISSIMILAR;
977
+ return firstIsLarger ? ARRAY_COMPARISON_REMOVED : ARRAY_COMPARISON_ADDED;
794
978
  }
795
979
  function isFragment(value) {
796
980
  return isNamed(value, NAME_FRAGMENT);
@@ -801,9 +985,6 @@ function isFragments(value) {
801
985
  function isNamed(value, name) {
802
986
  return typeof value === "object" && value != null && name in value && value[name] === true;
803
987
  }
804
- const COMPARISON_ADDED = "added";
805
- const COMPARISON_DISSIMILAR = "dissimilar";
806
- const COMPARISON_REMOVED = "removed";
807
988
  //#endregion
808
989
  //#region src/fragments.ts
809
990
  var Fragments = class {
@@ -839,13 +1020,13 @@ function handleItems(state, items) {
839
1020
  for (let index = 0; index < length; index += 1) {
840
1021
  const item = items[index];
841
1022
  const identifier = state.identify(item);
842
- if (identifier == null) throw new Error("Identifier cannot be null or undefined");
1023
+ if (identifier == null) throw new TypeError(ERROR_IDENTIFIER_TYPE);
843
1024
  const key = getString(identifier);
844
- if (keys.has(key)) throw new Error(`Duplicate identifier found: "${key}"`);
1025
+ if (keys.has(key)) throw new Error(ERROR_IDENTIFIER_DUPLICATE.replace("<>", key));
845
1026
  let instance = state.instances[key];
846
1027
  if (instance == null) {
847
1028
  instance = state.fragment(item);
848
- if (!isFragment(instance)) throw new Error("Fragment function must return a Fragment instance");
1029
+ if (!isFragment(instance)) throw new Error(ERROR_FRAGMENT);
849
1030
  }
850
1031
  instance.identify(key);
851
1032
  state.instances[key] = instance;
@@ -886,6 +1067,9 @@ function createNodes(value) {
886
1067
  if (isChildNode(value)) return [value];
887
1068
  return [new Text(getString(value))];
888
1069
  }
1070
+ function isInputElement(node) {
1071
+ return node instanceof HTMLInputElement || node instanceof HTMLSelectElement || node instanceof HTMLTextAreaElement;
1072
+ }
889
1073
  function removeNodes(nodes) {
890
1074
  const { length } = nodes;
891
1075
  for (let index = 0; index < length; index += 1) nodes[index].remove();
@@ -1023,8 +1207,8 @@ function getOptions(options) {
1023
1207
  function getType(element, type) {
1024
1208
  if (type !== "on") return type;
1025
1209
  if (element instanceof HTMLInputElement) {
1026
- if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) return "change";
1027
- return element.type === "submit" ? "submit" : "input";
1210
+ if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) return EVENT_CHANGE;
1211
+ return element.type === "submit" ? EVENT_SUBMIT : EVENT_INPUT;
1028
1212
  }
1029
1213
  return EVENT_DEFAULTS[element.tagName] ?? type;
1030
1214
  }
@@ -1083,7 +1267,7 @@ function setValue(data, element, name, value) {
1083
1267
  else callback(element, name, value);
1084
1268
  }
1085
1269
  function updateChecked(element, name, value) {
1086
- updateElement("change", "checked", element, name, value, value === true);
1270
+ updateElement(EVENT_CHANGE, PROPERTY_CHECKED, element, name, value, value === true);
1087
1271
  }
1088
1272
  function updateElement(event, property, element, name, value, next) {
1089
1273
  if (!(property in element) || element[property] === next) return;
@@ -1096,7 +1280,7 @@ function updateProperty(element, name, value) {
1096
1280
  setAttribute$1(element, name, value === true);
1097
1281
  }
1098
1282
  function updateValue(element, name, value) {
1099
- updateElement(element instanceof HTMLSelectElement ? "change" : "input", "value", element, name, value, String(value));
1283
+ updateElement(element instanceof HTMLSelectElement ? EVENT_CHANGE : EVENT_INPUT, PROPERTY_VALUE, element, name, value, String(value));
1100
1284
  }
1101
1285
  //#endregion
1102
1286
  //#region src/node/attribute/index.ts
@@ -1128,6 +1312,9 @@ function mapAttributes(data, element) {
1128
1312
  function mapValue$1(data, element, name, value) {
1129
1313
  if (typeof value === "function") setComputedAttribute(data, element, name, value);
1130
1314
  else setAttribute(data, element, name, value);
1315
+ if (name === "value" && isInputElement(element) && isSignal(value)) mapEvent(element, "@on", () => {
1316
+ value.set(parse$1(element.value) ?? element.value);
1317
+ });
1131
1318
  }
1132
1319
  function setComputedAttribute(data, element, name, callback) {
1133
1320
  const value = computed(callback);
@@ -1157,7 +1344,7 @@ function handleArray(identifiers, items, nodes) {
1157
1344
  if (comparison !== "removed") addToArray(identifiers, {
1158
1345
  ...items,
1159
1346
  next
1160
- }, nodes, comparison === "added");
1347
+ }, nodes, comparison === ARRAY_COMPARISON_ADDED);
1161
1348
  const toRemove = items.fragments?.filter((fragment) => !identifiers.next.has(fragment.identifier)) ?? [];
1162
1349
  const { length } = toRemove;
1163
1350
  for (let index = 0; index < length; index += 1) toRemove[index].remove();
@@ -1,6 +1,19 @@
1
1
  //#region src/constants.d.ts
2
+ declare const ARRAY_COMPARISON_ADDED = "added";
3
+ declare const ARRAY_COMPARISON_DISSIMILAR = "dissimilar";
4
+ declare const ARRAY_COMPARISON_REMOVED = "removed";
2
5
  declare const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
6
+ declare const ATTRIBUTE_NAME_DELIMITER = ".";
7
+ declare const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
8
+ declare const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ declare const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
10
+ declare const EVENT_CHANGE = "change";
11
+ declare const EVENT_INPUT = "input";
12
+ declare const EVENT_SUBMIT = "submit";
3
13
  declare const EVENT_DEFAULTS: Record<string, string>;
14
+ declare const EVENT_ON_PREFIXED = "@on";
15
+ declare const EVENT_ON_VALUE = "on";
16
+ declare const EVENT_OPTIONS_DELIMITER = ":";
4
17
  declare const EXPRESSION_ABYDON_ATTRIBUTE_FULL: RegExp;
5
18
  declare const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX: RegExp;
6
19
  declare const EXPRESSION_ABYDON_CONTENT: RegExp;
@@ -16,5 +29,9 @@ declare const EXPRESSION_EVENT_PREFIX: RegExp;
16
29
  declare const EXPRESSION_PERIOD: RegExp;
17
30
  declare const NAME_FRAGMENT = "$fragment";
18
31
  declare const NAME_FRAGMENTS = "$fragments";
32
+ declare const PROPERTY_CHECKED = "checked";
33
+ declare const PROPERTY_IDENTIFIER = "identifier";
34
+ declare const PROPERTY_VALUE = "value";
35
+ declare const TEMPLATE_ITEM = "<>";
19
36
  //#endregion
20
- export { ATTRIBUTE_CLASS_PREFIX_LENGTH, EVENT_DEFAULTS, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, NAME_FRAGMENT, NAME_FRAGMENTS };
37
+ export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_PREFIXED, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM };
@@ -1,14 +1,27 @@
1
1
  //#region src/constants.ts
2
+ const ARRAY_COMPARISON_ADDED = "added";
3
+ const ARRAY_COMPARISON_DISSIMILAR = "dissimilar";
4
+ const ARRAY_COMPARISON_REMOVED = "removed";
2
5
  const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
6
+ const ATTRIBUTE_NAME_DELIMITER = ".";
7
+ const ERROR_FRAGMENT = "Fragment function must return a Fragment instance";
8
+ const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
9
+ const ERROR_IDENTIFIER_TYPE = "Identifier cannot be null or undefined";
10
+ const EVENT_CHANGE = "change";
11
+ const EVENT_INPUT = "input";
12
+ const EVENT_SUBMIT = "submit";
3
13
  const EVENT_DEFAULTS = {
4
14
  A: "click",
5
15
  BUTTON: "click",
6
16
  DETAILS: "toggle",
7
- FORM: "submit",
8
- SELECT: "change",
9
- TEXTAREA: "input"
17
+ FORM: EVENT_SUBMIT,
18
+ SELECT: EVENT_CHANGE,
19
+ TEXTAREA: EVENT_INPUT
10
20
  };
11
- const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
21
+ const EVENT_ON_PREFIXED = "@on";
22
+ const EVENT_ON_VALUE = "on";
23
+ const EVENT_OPTIONS_DELIMITER = ":";
24
+ const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?[\w-]+)="<!--abydon.(\d+)-->"/g;
12
25
  const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
13
26
  const EXPRESSION_ABYDON_CONTENT = /^@?abydon\.(\d+)@?$/;
14
27
  const EXPRESSION_ATTRIBUTE_CLASS = /^class\./;
@@ -23,5 +36,9 @@ const EXPRESSION_EVENT_PREFIX = /^@/;
23
36
  const EXPRESSION_PERIOD = /\./;
24
37
  const NAME_FRAGMENT = "$fragment";
25
38
  const NAME_FRAGMENTS = "$fragments";
39
+ const PROPERTY_CHECKED = "checked";
40
+ const PROPERTY_IDENTIFIER = "identifier";
41
+ const PROPERTY_VALUE = "value";
42
+ const TEMPLATE_ITEM = "<>";
26
43
  //#endregion
27
- export { ATTRIBUTE_CLASS_PREFIX_LENGTH, EVENT_DEFAULTS, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, NAME_FRAGMENT, NAME_FRAGMENTS };
44
+ export { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, ATTRIBUTE_CLASS_PREFIX_LENGTH, ATTRIBUTE_NAME_DELIMITER, ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_ON_PREFIXED, EVENT_ON_VALUE, EVENT_OPTIONS_DELIMITER, EVENT_SUBMIT, EXPRESSION_ABYDON_ATTRIBUTE_FULL, EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD, NAME_FRAGMENT, NAME_FRAGMENTS, PROPERTY_CHECKED, PROPERTY_IDENTIFIER, PROPERTY_VALUE, TEMPLATE_ITEM };
@@ -1,4 +1,4 @@
1
- import { NAME_FRAGMENTS } from "./constants.mjs";
1
+ import { ERROR_FRAGMENT, ERROR_IDENTIFIER_DUPLICATE, ERROR_IDENTIFIER_TYPE, NAME_FRAGMENTS } from "./constants.mjs";
2
2
  import { isFragment, isFragments } from "./helpers/index.mjs";
3
3
  import { getString } from "@oscarpalmer/atoms/string";
4
4
  import { array } from "@oscarpalmer/mora";
@@ -36,13 +36,13 @@ function handleItems(state, items) {
36
36
  for (let index = 0; index < length; index += 1) {
37
37
  const item = items[index];
38
38
  const identifier = state.identify(item);
39
- if (identifier == null) throw new Error("Identifier cannot be null or undefined");
39
+ if (identifier == null) throw new TypeError(ERROR_IDENTIFIER_TYPE);
40
40
  const key = getString(identifier);
41
- if (keys.has(key)) throw new Error(`Duplicate identifier found: "${key}"`);
41
+ if (keys.has(key)) throw new Error(ERROR_IDENTIFIER_DUPLICATE.replace("<>", key));
42
42
  let instance = state.instances[key];
43
43
  if (instance == null) {
44
44
  instance = state.fragment(item);
45
- if (!isFragment(instance)) throw new Error("Fragment function must return a Fragment instance");
45
+ if (!isFragment(instance)) throw new Error(ERROR_FRAGMENT);
46
46
  }
47
47
  instance.identify(key);
48
48
  state.instances[key] = instance;
@@ -1,6 +1,7 @@
1
1
  //#region src/helpers/dom.d.ts
2
2
  declare function createNodes(value: unknown): ChildNode[];
3
+ declare function isInputElement(node: Node): node is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
3
4
  declare function removeNodes(nodes: ChildNode[]): void;
4
5
  declare function replaceNodes(from: ChildNode[], to: ChildNode[]): void;
5
6
  //#endregion
6
- export { createNodes, removeNodes, replaceNodes };
7
+ export { createNodes, isInputElement, removeNodes, replaceNodes };
@@ -7,6 +7,9 @@ function createNodes(value) {
7
7
  if (isChildNode(value)) return [value];
8
8
  return [new Text(getString(value))];
9
9
  }
10
+ function isInputElement(node) {
11
+ return node instanceof HTMLInputElement || node instanceof HTMLSelectElement || node instanceof HTMLTextAreaElement;
12
+ }
10
13
  function removeNodes(nodes) {
11
14
  const { length } = nodes;
12
15
  for (let index = 0; index < length; index += 1) nodes[index].remove();
@@ -17,4 +20,4 @@ function replaceNodes(from, to) {
17
20
  for (let index = 1; index < length; index += 1) from[index].remove();
18
21
  }
19
22
  //#endregion
20
- export { createNodes, removeNodes, replaceNodes };
23
+ export { createNodes, isInputElement, removeNodes, replaceNodes };
@@ -1,8 +1,9 @@
1
+ import { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED } from "../constants.mjs";
1
2
  import { t as Fragment } from "../fragment-9qTBAvtR.mjs";
2
3
  import { Fragments } from "../fragments.mjs";
3
4
 
4
5
  //#region src/helpers/index.d.ts
5
- declare function compareArrays(first: unknown[], second: unknown[]): 'added' | 'dissimilar' | 'removed';
6
+ declare function compareArrays(first: unknown[], second: unknown[]): typeof ARRAY_COMPARISON_ADDED | typeof ARRAY_COMPARISON_DISSIMILAR | typeof ARRAY_COMPARISON_REMOVED;
6
7
  declare function isFragment(value: unknown): value is Fragment;
7
8
  declare function isFragments(value: unknown): value is Fragments;
8
9
  //#endregion
@@ -1,11 +1,11 @@
1
- import { NAME_FRAGMENT, NAME_FRAGMENTS } from "../constants.mjs";
1
+ import { ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_DISSIMILAR, ARRAY_COMPARISON_REMOVED, NAME_FRAGMENT, NAME_FRAGMENTS } from "../constants.mjs";
2
2
  //#region src/helpers/index.ts
3
3
  function compareArrays(first, second) {
4
4
  const firstIsLarger = first.length > second.length;
5
5
  const from = firstIsLarger ? first : second;
6
6
  const to = firstIsLarger ? second : first;
7
- if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return COMPARISON_DISSIMILAR;
8
- return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
7
+ if (!from.filter((key) => to.includes(key)).every((key, index) => to[index] === key)) return ARRAY_COMPARISON_DISSIMILAR;
8
+ return firstIsLarger ? ARRAY_COMPARISON_REMOVED : ARRAY_COMPARISON_ADDED;
9
9
  }
10
10
  function isFragment(value) {
11
11
  return isNamed(value, NAME_FRAGMENT);
@@ -16,8 +16,5 @@ function isFragments(value) {
16
16
  function isNamed(value, name) {
17
17
  return typeof value === "object" && value != null && name in value && value[name] === true;
18
18
  }
19
- const COMPARISON_ADDED = "added";
20
- const COMPARISON_DISSIMILAR = "dissimilar";
21
- const COMPARISON_REMOVED = "removed";
22
19
  //#endregion
23
20
  export { compareArrays, isFragment, isFragments };
@@ -1,7 +1,9 @@
1
1
  import { EXPRESSION_ABYDON_ATTRIBUTE_PREFIX, EXPRESSION_ABYDON_CONTENT, EXPRESSION_EVENT_PREFIX, EXPRESSION_PERIOD } from "../../constants.mjs";
2
+ import { isInputElement } from "../../helpers/dom.mjs";
2
3
  import { mapEvent } from "../event.mjs";
3
4
  import { setAttribute } from "./value.mjs";
4
- import { computed, isReactive } from "@oscarpalmer/mora";
5
+ import { parse } from "@oscarpalmer/atoms/string";
6
+ import { computed, isReactive, isSignal } from "@oscarpalmer/mora";
5
7
  //#region src/node/attribute/index.ts
6
8
  function getValue(data, original) {
7
9
  const matches = EXPRESSION_ABYDON_CONTENT.exec(original ?? "");
@@ -31,6 +33,9 @@ function mapAttributes(data, element) {
31
33
  function mapValue(data, element, name, value) {
32
34
  if (typeof value === "function") setComputedAttribute(data, element, name, value);
33
35
  else setAttribute(data, element, name, value);
36
+ if (name === "value" && isInputElement(element) && isSignal(value)) mapEvent(element, "@on", () => {
37
+ value.set(parse(element.value) ?? element.value);
38
+ });
34
39
  }
35
40
  function setComputedAttribute(data, element, name, callback) {
36
41
  const value = computed(callback);
@@ -1,4 +1,4 @@
1
- import { EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX } from "../../constants.mjs";
1
+ import { EVENT_CHANGE, EVENT_INPUT, EXPRESSION_ATTRIBUTE_CLASS, EXPRESSION_ATTRIBUTE_STYLE_FULL, EXPRESSION_ATTRIBUTE_STYLE_PREFIX, PROPERTY_CHECKED, PROPERTY_VALUE } from "../../constants.mjs";
2
2
  import { isReactive } from "@oscarpalmer/mora";
3
3
  import { isBooleanAttribute, setAttribute as setAttribute$1 } from "@oscarpalmer/toretto/attribute";
4
4
  //#region src/node/attribute/value.ts
@@ -46,7 +46,7 @@ function setValue(data, element, name, value) {
46
46
  else callback(element, name, value);
47
47
  }
48
48
  function updateChecked(element, name, value) {
49
- updateElement("change", "checked", element, name, value, value === true);
49
+ updateElement(EVENT_CHANGE, PROPERTY_CHECKED, element, name, value, value === true);
50
50
  }
51
51
  function updateElement(event, property, element, name, value, next) {
52
52
  if (!(property in element) || element[property] === next) return;
@@ -59,7 +59,7 @@ function updateProperty(element, name, value) {
59
59
  setAttribute$1(element, name, value === true);
60
60
  }
61
61
  function updateValue(element, name, value) {
62
- updateElement(element instanceof HTMLSelectElement ? "change" : "input", "value", element, name, value, String(value));
62
+ updateElement(element instanceof HTMLSelectElement ? EVENT_CHANGE : EVENT_INPUT, PROPERTY_VALUE, element, name, value, String(value));
63
63
  }
64
64
  //#endregion
65
65
  export { setAttribute };
@@ -1,4 +1,4 @@
1
- import { EVENT_DEFAULTS, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE } from "../constants.mjs";
1
+ import { EVENT_CHANGE, EVENT_DEFAULTS, EVENT_INPUT, EVENT_SUBMIT, EXPRESSION_EVENT_CHANGE_TYPES, EXPRESSION_EVENT_NAME, EXPRESSION_EVENT_OPTIONS_ACTIVE, EXPRESSION_EVENT_OPTIONS_CAPTURE, EXPRESSION_EVENT_OPTIONS_ONCE } from "../constants.mjs";
2
2
  import { on } from "@oscarpalmer/toretto/event";
3
3
  //#region src/node/event.ts
4
4
  function getOptions(options) {
@@ -12,8 +12,8 @@ function getOptions(options) {
12
12
  function getType(element, type) {
13
13
  if (type !== "on") return type;
14
14
  if (element instanceof HTMLInputElement) {
15
- if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) return "change";
16
- return element.type === "submit" ? "submit" : "input";
15
+ if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) return EVENT_CHANGE;
16
+ return element.type === "submit" ? EVENT_SUBMIT : EVENT_INPUT;
17
17
  }
18
18
  return EVENT_DEFAULTS[element.tagName] ?? type;
19
19
  }
@@ -1,3 +1,4 @@
1
+ import { ARRAY_COMPARISON_ADDED } from "../constants.mjs";
1
2
  import { compareArrays, isFragment } from "../helpers/index.mjs";
2
3
  import { createNodes, replaceNodes } from "../helpers/dom.mjs";
3
4
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
@@ -25,7 +26,7 @@ function handleArray(identifiers, items, nodes) {
25
26
  if (comparison !== "removed") addToArray(identifiers, {
26
27
  ...items,
27
28
  next
28
- }, nodes, comparison === "added");
29
+ }, nodes, comparison === ARRAY_COMPARISON_ADDED);
29
30
  const toRemove = items.fragments?.filter((fragment) => !identifiers.next.has(fragment.identifier)) ?? [];
30
31
  const { length } = toRemove;
31
32
  for (let index = 0; index < length; index += 1) toRemove[index].remove();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/abydon",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "keywords": [
5
5
  "components",
6
6
  "dom",
@@ -39,12 +39,11 @@
39
39
  "tsdown:build": "npx tsdown -c ./tsdown.config.ts",
40
40
  "tsdown:watch": "npx tsdown -c ./tsdown.config.ts --watch",
41
41
  "test": "npx vp test run --coverage",
42
- "test:leak": "npx vp test run --detect-async-leaks --coverage",
43
- "watch": "npx vite build --watch"
42
+ "test:leak": "npx vp test run --detect-async-leaks --coverage"
44
43
  },
45
44
  "dependencies": {
46
45
  "@oscarpalmer/atoms": "^0.165",
47
- "@oscarpalmer/mora": "^0.26",
46
+ "@oscarpalmer/mora": "^0.27",
48
47
  "@oscarpalmer/toretto": "^0.41"
49
48
  },
50
49
  "devDependencies": {
package/src/constants.ts CHANGED
@@ -1,15 +1,41 @@
1
+ export const ARRAY_COMPARISON_ADDED = 'added';
2
+
3
+ export const ARRAY_COMPARISON_DISSIMILAR = 'dissimilar';
4
+
5
+ export const ARRAY_COMPARISON_REMOVED = 'removed';
6
+
1
7
  export const ATTRIBUTE_CLASS_PREFIX_LENGTH = 6;
2
8
 
9
+ export const ATTRIBUTE_NAME_DELIMITER = '.';
10
+
11
+ export const ERROR_FRAGMENT = 'Fragment function must return a Fragment instance';
12
+
13
+ export const ERROR_IDENTIFIER_DUPLICATE = "Duplicate identifier found: '<id>'";
14
+
15
+ export const ERROR_IDENTIFIER_TYPE = 'Identifier cannot be null or undefined';
16
+
17
+ export const EVENT_CHANGE = 'change';
18
+
19
+ export const EVENT_INPUT = 'input';
20
+
21
+ export const EVENT_SUBMIT = 'submit';
22
+
3
23
  export const EVENT_DEFAULTS: Record<string, string> = {
4
24
  A: 'click',
5
25
  BUTTON: 'click',
6
26
  DETAILS: 'toggle',
7
- FORM: 'submit',
8
- SELECT: 'change',
9
- TEXTAREA: 'input',
27
+ FORM: EVENT_SUBMIT,
28
+ SELECT: EVENT_CHANGE,
29
+ TEXTAREA: EVENT_INPUT,
10
30
  };
11
31
 
12
- export const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?\w+)="<!--abydon.(\d+)-->"/g;
32
+ export const EVENT_ON_PREFIXED = '@on';
33
+
34
+ export const EVENT_ON_VALUE = 'on';
35
+
36
+ export const EVENT_OPTIONS_DELIMITER = ':';
37
+
38
+ export const EXPRESSION_ABYDON_ATTRIBUTE_FULL = /(@?[\w-]+)="<!--abydon.(\d+)-->"/g;
13
39
 
14
40
  export const EXPRESSION_ABYDON_ATTRIBUTE_PREFIX = /^_/;
15
41
 
@@ -38,3 +64,11 @@ export const EXPRESSION_PERIOD = /\./;
38
64
  export const NAME_FRAGMENT = '$fragment';
39
65
 
40
66
  export const NAME_FRAGMENTS = '$fragments';
67
+
68
+ export const PROPERTY_CHECKED = 'checked';
69
+
70
+ export const PROPERTY_IDENTIFIER = 'identifier';
71
+
72
+ export const PROPERTY_VALUE = 'value';
73
+
74
+ export const TEMPLATE_ITEM = '<>';
package/src/fragment.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import {html} from '@oscarpalmer/toretto/html';
3
- import {NAME_FRAGMENT} from './constants';
3
+ import {NAME_FRAGMENT, PROPERTY_IDENTIFIER} from './constants';
4
4
  import {handleFragments} from './fragments';
5
5
  import {isFragments} from './helpers';
6
6
  import {removeNodes} from './helpers/dom';
@@ -56,7 +56,7 @@ export class Fragment {
56
56
  configure(configuration: FragmentConfiguration): Fragment {
57
57
  const actual = isPlainObject(configuration) ? configuration : {};
58
58
 
59
- if ('identifier' in actual) {
59
+ if (PROPERTY_IDENTIFIER in actual) {
60
60
  this.#configuration.identifier = actual.identifier;
61
61
  }
62
62
 
package/src/fragments.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
2
  import {array, type ReactiveArray} from '@oscarpalmer/mora';
3
- import {NAME_FRAGMENTS} from './constants';
3
+ import {
4
+ ERROR_FRAGMENT,
5
+ ERROR_IDENTIFIER_DUPLICATE,
6
+ ERROR_IDENTIFIER_TYPE,
7
+ NAME_FRAGMENTS,
8
+ TEMPLATE_ITEM,
9
+ } from './constants';
4
10
  import type {Fragment} from './fragment';
5
11
  import {isFragment, isFragments} from './helpers';
6
12
  import type {FragmentsState} from './models';
@@ -58,13 +64,13 @@ function handleItems(state: FragmentsState, items: unknown[]): void {
58
64
  const identifier = state.identify(item);
59
65
 
60
66
  if (identifier == null) {
61
- throw new Error('Identifier cannot be null or undefined');
67
+ throw new TypeError(ERROR_IDENTIFIER_TYPE);
62
68
  }
63
69
 
64
70
  const key = getString(identifier);
65
71
 
66
72
  if (keys.has(key)) {
67
- throw new Error(`Duplicate identifier found: "${key}"`);
73
+ throw new Error(ERROR_IDENTIFIER_DUPLICATE.replace(TEMPLATE_ITEM, key));
68
74
  }
69
75
 
70
76
  let instance = state.instances[key];
@@ -73,7 +79,7 @@ function handleItems(state: FragmentsState, items: unknown[]): void {
73
79
  instance = state.fragment(item);
74
80
 
75
81
  if (!isFragment(instance)) {
76
- throw new Error('Fragment function must return a Fragment instance');
82
+ throw new Error(ERROR_FRAGMENT);
77
83
  }
78
84
  }
79
85
 
@@ -14,6 +14,16 @@ export function createNodes(value: unknown): ChildNode[] {
14
14
  return [new Text(getString(value))];
15
15
  }
16
16
 
17
+ export function isInputElement(
18
+ node: Node,
19
+ ): node is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement {
20
+ return (
21
+ node instanceof HTMLInputElement ||
22
+ node instanceof HTMLSelectElement ||
23
+ node instanceof HTMLTextAreaElement
24
+ );
25
+ }
26
+
17
27
  export function removeNodes(nodes: ChildNode[]): void {
18
28
  const {length} = nodes;
19
29
 
@@ -1,21 +1,30 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
- import {NAME_FRAGMENT, NAME_FRAGMENTS} from '../constants';
2
+ import {
3
+ ARRAY_COMPARISON_ADDED,
4
+ ARRAY_COMPARISON_DISSIMILAR,
5
+ ARRAY_COMPARISON_REMOVED,
6
+ NAME_FRAGMENT,
7
+ NAME_FRAGMENTS,
8
+ } from '../constants';
3
9
  import type {Fragment} from '../fragment';
4
10
  import type {Fragments} from '../fragments';
5
11
 
6
12
  export function compareArrays(
7
13
  first: unknown[],
8
14
  second: unknown[],
9
- ): 'added' | 'dissimilar' | 'removed' {
15
+ ):
16
+ | typeof ARRAY_COMPARISON_ADDED
17
+ | typeof ARRAY_COMPARISON_DISSIMILAR
18
+ | typeof ARRAY_COMPARISON_REMOVED {
10
19
  const firstIsLarger = first.length > second.length;
11
20
  const from = firstIsLarger ? first : second;
12
21
  const to = firstIsLarger ? second : first;
13
22
 
14
23
  if (!from.filter(key => to.includes(key)).every((key, index) => to[index] === key)) {
15
- return COMPARISON_DISSIMILAR;
24
+ return ARRAY_COMPARISON_DISSIMILAR;
16
25
  }
17
26
 
18
- return firstIsLarger ? COMPARISON_REMOVED : COMPARISON_ADDED;
27
+ return firstIsLarger ? ARRAY_COMPARISON_REMOVED : ARRAY_COMPARISON_ADDED;
19
28
  }
20
29
 
21
30
  export function isFragment(value: unknown): value is Fragment {
@@ -34,9 +43,3 @@ function isNamed(value: unknown, name: string): boolean {
34
43
  (value as PlainObject)[name] === true
35
44
  );
36
45
  }
37
-
38
- const COMPARISON_ADDED = 'added';
39
-
40
- const COMPARISON_DISSIMILAR = 'dissimilar';
41
-
42
- const COMPARISON_REMOVED = 'removed';
@@ -1,11 +1,15 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
- import {computed, isReactive} from '@oscarpalmer/mora';
2
+ import {parse} from '@oscarpalmer/atoms/string';
3
+ import {computed, isReactive, isSignal} from '@oscarpalmer/mora';
3
4
  import {
5
+ EVENT_ON_PREFIXED,
4
6
  EXPRESSION_ABYDON_ATTRIBUTE_PREFIX,
5
7
  EXPRESSION_ABYDON_CONTENT,
6
8
  EXPRESSION_EVENT_PREFIX,
7
9
  EXPRESSION_PERIOD,
10
+ PROPERTY_VALUE,
8
11
  } from '../../constants';
12
+ import {isInputElement} from '../../helpers/dom';
9
13
  import type {FragmentData} from '../../models';
10
14
  import {mapEvent} from '../event';
11
15
  import {setAttribute} from './value';
@@ -58,6 +62,12 @@ function mapValue(
58
62
  } else {
59
63
  setAttribute(data, element, name, value);
60
64
  }
65
+
66
+ if (name === PROPERTY_VALUE && isInputElement(element) && isSignal(value)) {
67
+ mapEvent(element, EVENT_ON_PREFIXED, () => {
68
+ value.set(parse(element.value) ?? element.value);
69
+ });
70
+ }
61
71
  }
62
72
 
63
73
  function setComputedAttribute(
@@ -6,18 +6,23 @@ import {
6
6
  } from '@oscarpalmer/toretto/attribute';
7
7
  import {
8
8
  ATTRIBUTE_CLASS_PREFIX_LENGTH,
9
+ ATTRIBUTE_NAME_DELIMITER,
10
+ EVENT_CHANGE,
11
+ EVENT_INPUT,
9
12
  EXPRESSION_ATTRIBUTE_CLASS,
10
13
  EXPRESSION_ATTRIBUTE_STYLE_FULL,
11
14
  EXPRESSION_ATTRIBUTE_STYLE_PREFIX,
15
+ PROPERTY_CHECKED,
16
+ PROPERTY_VALUE,
12
17
  } from '../../constants';
13
18
  import type {FragmentData} from '../../models';
14
19
 
15
20
  function getCallback(element: HTMLElement | SVGElement, name: string) {
16
21
  if (isBooleanAttribute(name) && name in element) {
17
- return name === 'checked' ? updateChecked : updateProperty;
22
+ return name === PROPERTY_CHECKED ? updateChecked : updateProperty;
18
23
  }
19
24
 
20
- return name === 'value' ? updateValue : setTorettoAttribute;
25
+ return name === PROPERTY_VALUE ? updateValue : setTorettoAttribute;
21
26
  }
22
27
 
23
28
  export function setAttribute(
@@ -55,7 +60,7 @@ function setClasses(
55
60
  }
56
61
  }
57
62
 
58
- const classes = name.slice(ATTRIBUTE_CLASS_PREFIX_LENGTH).split('.');
63
+ const classes = name.slice(ATTRIBUTE_CLASS_PREFIX_LENGTH).split(ATTRIBUTE_NAME_DELIMITER);
59
64
 
60
65
  if (isReactive(value)) {
61
66
  data.mora.subscribers.add(value.subscribe(update));
@@ -111,7 +116,7 @@ function setValue(
111
116
  }
112
117
 
113
118
  function updateChecked(element: HTMLElement | SVGElement, name: string, value: unknown): void {
114
- updateElement('change', 'checked', element, name, value, value === true);
119
+ updateElement(EVENT_CHANGE, PROPERTY_CHECKED, element, name, value, value === true);
115
120
  }
116
121
 
117
122
  function updateElement(
@@ -143,8 +148,8 @@ function updateProperty(element: HTMLElement | SVGElement, name: string, value:
143
148
 
144
149
  function updateValue(element: HTMLElement | SVGElement, name: string, value: unknown): void {
145
150
  updateElement(
146
- element instanceof HTMLSelectElement ? 'change' : 'input',
147
- 'value',
151
+ element instanceof HTMLSelectElement ? EVENT_CHANGE : EVENT_INPUT,
152
+ PROPERTY_VALUE,
148
153
  element,
149
154
  name,
150
155
  value,
package/src/node/event.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import {on} from '@oscarpalmer/toretto/event';
2
2
  import {
3
+ EVENT_CHANGE,
3
4
  EVENT_DEFAULTS,
5
+ EVENT_INPUT,
6
+ EVENT_ON_VALUE,
7
+ EVENT_OPTIONS_DELIMITER,
8
+ EVENT_SUBMIT,
4
9
  EXPRESSION_EVENT_CHANGE_TYPES,
5
10
  EXPRESSION_EVENT_NAME,
6
11
  EXPRESSION_EVENT_OPTIONS_ACTIVE,
@@ -9,7 +14,7 @@ import {
9
14
  } from '../constants';
10
15
 
11
16
  function getOptions(options: string): AddEventListenerOptions {
12
- const parts = options.split(':');
17
+ const parts = options.split(EVENT_OPTIONS_DELIMITER);
13
18
 
14
19
  return {
15
20
  capture: parts.some(part => EXPRESSION_EVENT_OPTIONS_CAPTURE.test(part)),
@@ -19,16 +24,16 @@ function getOptions(options: string): AddEventListenerOptions {
19
24
  }
20
25
 
21
26
  function getType(element: HTMLElement | SVGElement, type: string): string {
22
- if (type !== 'on') {
27
+ if (type !== EVENT_ON_VALUE) {
23
28
  return type;
24
29
  }
25
30
 
26
31
  if (element instanceof HTMLInputElement) {
27
32
  if (EXPRESSION_EVENT_CHANGE_TYPES.test(element.type)) {
28
- return 'change';
33
+ return EVENT_CHANGE;
29
34
  }
30
35
 
31
- return element.type === 'submit' ? 'submit' : 'input';
36
+ return element.type === EVENT_SUBMIT ? EVENT_SUBMIT : EVENT_INPUT;
32
37
  }
33
38
 
34
39
  return EVENT_DEFAULTS[element.tagName] ?? type;
package/src/node/value.ts CHANGED
@@ -2,6 +2,7 @@ import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
2
  import {getString} from '@oscarpalmer/atoms/string';
3
3
  import type {Reactive} from '@oscarpalmer/mora';
4
4
  import {isChildNode} from '@oscarpalmer/toretto/is';
5
+ import {ARRAY_COMPARISON_ADDED, ARRAY_COMPARISON_REMOVED} from '../constants';
5
6
  import type {Fragment} from '../fragment';
6
7
  import {compareArrays, isFragment} from '../helpers';
7
8
  import {createNodes, replaceNodes} from '../helpers/dom';
@@ -71,8 +72,8 @@ function handleArray(
71
72
 
72
73
  const comparison = compareArrays(items.fragments ?? [], items.templates);
73
74
 
74
- if (comparison !== 'removed') {
75
- addToArray(identifiers, {...items, next}, nodes, comparison === 'added');
75
+ if (comparison !== ARRAY_COMPARISON_REMOVED) {
76
+ addToArray(identifiers, {...items, next}, nodes, comparison === ARRAY_COMPARISON_ADDED);
76
77
  }
77
78
 
78
79
  const toRemove =