@htmlplus/element 3.2.6 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { kebabCase, pascalCase } from 'change-case';
2
- import { API_HOST, STATIC_TAG, API_STACKS, API_REQUEST, API_CONNECTED, LIFECYCLE_UPDATE, STATIC_STYLE, API_STYLE, LIFECYCLE_UPDATED, API_RENDER_COMPLETED, METHOD_RENDER, TYPE_BOOLEAN, TYPE_NUMBER, TYPE_NULL, TYPE_DATE, TYPE_ARRAY, TYPE_OBJECT, TYPE_UNDEFINED, KEY, LIFECYCLE_CONNECTED, LIFECYCLE_DISCONNECTED, LIFECYCLE_CONSTRUCTED, LIFECYCLE_ADOPTED, LIFECYCLE_READY } from './constants.js';
2
+ import { API_HOST, STATIC_TAG, API_STACKS, API_REQUEST, API_CONNECTED, LIFECYCLE_UPDATE, STATIC_STYLE, API_STYLE, LIFECYCLE_UPDATED, API_RENDER_COMPLETED, METHOD_RENDER, TYPE_BOOLEAN, TYPE_NUMBER, TYPE_NULL, TYPE_DATE, TYPE_ARRAY, TYPE_OBJECT, TYPE_UNDEFINED, TYPE_STRING, KEY, LIFECYCLE_CONNECTED, LIFECYCLE_DISCONNECTED, LIFECYCLE_CONSTRUCTED, LIFECYCLE_ADOPTED, LIFECYCLE_READY } from './constants.js';
3
3
 
4
4
  /**
5
5
  * Indicates the host of the element.
@@ -27,11 +27,19 @@ const dispatch = (target, type, eventInitDict) => {
27
27
  */
28
28
  const on = (target, type, handler, options) => {
29
29
  const element = host(target);
30
- if (type != 'outside') {
30
+ if (type !== 'outside') {
31
31
  return element.addEventListener(type, handler, options);
32
32
  }
33
33
  const callback = (event) => {
34
- !event.composedPath().some((item) => item == element) && handler(event);
34
+ const has = event.composedPath().some((item) => item === element);
35
+ if (has)
36
+ return;
37
+ if (typeof handler === 'function') {
38
+ handler(event);
39
+ }
40
+ else {
41
+ handler.handleEvent(event);
42
+ }
35
43
  };
36
44
  type = 'ontouchstart' in window.document.documentElement ? 'touchstart' : 'click';
37
45
  on(document, type, callback, options);
@@ -48,11 +56,11 @@ const on = (target, type, handler, options) => {
48
56
  */
49
57
  const off = (target, type, handler, options) => {
50
58
  const element = host(target);
51
- if (type != 'outside') {
52
- return element.removeEventListener(type, handler, options);
59
+ if (type !== 'outside') {
60
+ return void element.removeEventListener(type, handler, options);
53
61
  }
54
62
  const index = outsides.findIndex((outside) => {
55
- return outside.element == element && outside.handler == handler && outside.options == options;
63
+ return (outside.element === element && outside.handler === handler && outside.options === options);
56
64
  });
57
65
  const outside = outsides[index];
58
66
  if (!outside)
@@ -71,12 +79,10 @@ const toEvent = (input) => {
71
79
 
72
80
  const updateAttribute = (target, key, value) => {
73
81
  const element = host(target);
74
- if ([undefined, null, false].includes(value)) {
75
- element.removeAttribute(key);
76
- }
77
- else {
78
- element.setAttribute(key, value === true ? '' : value);
82
+ if (value === undefined || value === null || value === false) {
83
+ return void element.removeAttribute(key);
79
84
  }
85
+ element.setAttribute(key, value === true ? '' : String(value));
80
86
  };
81
87
 
82
88
  const symbol = Symbol();
@@ -132,9 +138,10 @@ const classes = (input, smart) => {
132
138
  break;
133
139
  }
134
140
  case 'object': {
135
- const keys = Object.keys(input);
141
+ const obj = input;
142
+ const keys = Object.keys(obj);
136
143
  for (const key of keys) {
137
- const value = input[key];
144
+ const value = obj[key];
138
145
  const name = kebabCase(key);
139
146
  const type = typeOf(value);
140
147
  if (!smart) {
@@ -163,18 +170,11 @@ const classes = (input, smart) => {
163
170
  return result.filter((item) => item).join(' ');
164
171
  };
165
172
 
166
- /**
167
- * Indicates whether the current code is running on a server.
168
- */
169
- const isServer = () => {
170
- return !(typeof window != 'undefined' && window.document);
171
- };
172
-
173
173
  const merge = (target, ...sources) => {
174
174
  for (const source of sources) {
175
175
  if (!source)
176
176
  continue;
177
- if (typeOf(source) != 'object') {
177
+ if (typeOf(source) !== 'object') {
178
178
  target = source;
179
179
  continue;
180
180
  }
@@ -192,31 +192,31 @@ const merge = (target, ...sources) => {
192
192
  return target;
193
193
  };
194
194
 
195
- const DEFAULTS = {
196
- element: {}
195
+ /**
196
+ * TODO
197
+ */
198
+ const getConfig = (namespace) => {
199
+ return globalThis[`$htmlplus:${namespace}$`] || {};
197
200
  };
198
201
  /**
199
202
  * TODO
200
203
  */
201
- const getConfig = (...keys) => {
202
- if (isServer())
203
- return;
204
- let config = window[`$htmlplus$`];
205
- for (const key of keys) {
206
- if (!config)
207
- break;
208
- config = config[key];
209
- }
210
- return config;
204
+ const getConfigCreator = (namespace) => () => {
205
+ return getConfig(namespace);
211
206
  };
212
207
  /**
213
208
  * TODO
214
209
  */
215
- const setConfig = (config, options) => {
216
- if (isServer())
217
- return;
218
- const previous = options?.override ? {} : window[`$htmlplus$`];
219
- window[`$htmlplus$`] = merge({}, DEFAULTS, previous, config);
210
+ const setConfig = (namespace, config, options) => {
211
+ const previous = options?.override ? {} : globalThis[`$htmlplus:${namespace}$`];
212
+ const next = merge({}, previous, config);
213
+ globalThis[`$htmlplus:${namespace}$`] = next;
214
+ };
215
+ /**
216
+ * TODO
217
+ */
218
+ const setConfigCreator = (namespace) => (config, options) => {
219
+ return setConfig(namespace, config, options);
220
220
  };
221
221
 
222
222
  const defineProperty = Object.defineProperty;
@@ -253,6 +253,10 @@ const getTag = (target) => {
253
253
  return target.constructor[STATIC_TAG] ?? target[STATIC_TAG];
254
254
  };
255
255
 
256
+ const getNamespace = (target) => {
257
+ return getTag(target)?.split('-')?.at(0);
258
+ };
259
+
256
260
  /**
257
261
  * Determines whether the given input string is a valid
258
262
  * [CSS Color](https://mdn.io/color-value) or not.
@@ -266,7 +270,16 @@ const isCSSColor = (input) => {
266
270
  /**
267
271
  * Indicates whether the direction of the element is `Right-To-Left` or not.
268
272
  */
269
- const isRTL = (target) => direction(target) == 'rtl';
273
+ const isRTL = (target) => {
274
+ return direction(target) === 'rtl';
275
+ };
276
+
277
+ /**
278
+ * Indicates whether the current code is running on a server.
279
+ */
280
+ const isServer = () => {
281
+ return !(typeof window !== 'undefined' && window.document);
282
+ };
270
283
 
271
284
  const shadowRoot = (target) => {
272
285
  return host(target)?.shadowRoot;
@@ -287,7 +300,8 @@ function queryAll(target, selectors) {
287
300
  }
288
301
 
289
302
  const task = (options) => {
290
- let running, promise;
303
+ let running;
304
+ let promise;
291
305
  const run = () => {
292
306
  if (options.canStart && !options.canStart())
293
307
  return Promise.resolve(false);
@@ -307,8 +321,10 @@ const task = (options) => {
307
321
  if (!running)
308
322
  return promise;
309
323
  try {
310
- if (options.canRun && !options.canRun())
311
- return (running = false);
324
+ if (options.canRun && !options.canRun()) {
325
+ running = false;
326
+ return running;
327
+ }
312
328
  options.handler();
313
329
  running = false;
314
330
  return true;
@@ -321,23 +337,25 @@ const task = (options) => {
321
337
  return run;
322
338
  };
323
339
 
340
+ // biome-ignore-all lint: TODO
341
+
324
342
  class MapSet extends Map {
325
- set(key, value) {
326
- super.set(key, value);
327
- return value;
328
- }
343
+ set(key, value) {
344
+ super.set(key, value);
345
+ return value;
346
+ }
329
347
  }
330
348
 
331
349
  class WeakMapSet extends WeakMap {
332
- set(key, value) {
333
- super.set(key, value);
334
- return value;
335
- }
350
+ set(key, value) {
351
+ super.set(key, value);
352
+ return value;
353
+ }
336
354
  }
337
355
 
338
356
  /*! (c) Andrea Giammarchi - ISC */
339
357
  const empty =
340
- /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
358
+ /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
341
359
  const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
342
360
  const attributes$1 = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
343
361
  const holes = /[\x01\x02]/g;
@@ -354,155 +372,155 @@ const holes = /[\x01\x02]/g;
354
372
  * @returns {string} X/HTML with prefixed comments or attributes
355
373
  */
356
374
  var instrument = (template, prefix, svg) => {
357
- let i = 0;
358
- return template
359
- .join('\x01')
360
- .trim()
361
- .replace(elements, (_, name, attrs, selfClosing) => {
362
- let ml = name + attrs.replace(attributes$1, '\x02=$2$1').trimEnd();
363
- if (selfClosing.length) ml += svg || empty.test(name) ? ' /' : '></' + name;
364
- return '<' + ml + '>';
365
- })
366
- .replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
375
+ let i = 0;
376
+ return template
377
+ .join('\x01')
378
+ .trim()
379
+ .replace(elements, (_, name, attrs, selfClosing) => {
380
+ let ml = name + attrs.replace(attributes$1, '\x02=$2$1').trimEnd();
381
+ if (selfClosing.length) ml += svg || empty.test(name) ? ' /' : '></' + name;
382
+ return '<' + ml + '>';
383
+ })
384
+ .replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
367
385
  };
368
386
 
369
387
  const ELEMENT_NODE = 1;
370
388
  const nodeType = 111;
371
389
 
372
390
  const remove = ({ firstChild, lastChild }) => {
373
- const range = document.createRange();
374
- range.setStartAfter(firstChild);
375
- range.setEndAfter(lastChild);
376
- range.deleteContents();
377
- return firstChild;
391
+ const range = document.createRange();
392
+ range.setStartAfter(firstChild);
393
+ range.setEndAfter(lastChild);
394
+ range.deleteContents();
395
+ return firstChild;
378
396
  };
379
397
 
380
398
  const diffable = (node, operation) =>
381
- node.nodeType === nodeType
382
- ? 1 / operation < 0
383
- ? operation
384
- ? remove(node)
385
- : node.lastChild
386
- : operation
387
- ? node.valueOf()
388
- : node.firstChild
389
- : node;
399
+ node.nodeType === nodeType
400
+ ? 1 / operation < 0
401
+ ? operation
402
+ ? remove(node)
403
+ : node.lastChild
404
+ : operation
405
+ ? node.valueOf()
406
+ : node.firstChild
407
+ : node;
390
408
  const persistent = (fragment) => {
391
- const { firstChild, lastChild } = fragment;
392
- if (firstChild === lastChild) return lastChild || fragment;
393
- const { childNodes } = fragment;
394
- const nodes = [...childNodes];
395
- return {
396
- ELEMENT_NODE,
397
- nodeType,
398
- firstChild,
399
- lastChild,
400
- valueOf() {
401
- if (childNodes.length !== nodes.length) fragment.append(...nodes);
402
- return fragment;
403
- }
404
- };
409
+ const { firstChild, lastChild } = fragment;
410
+ if (firstChild === lastChild) return lastChild || fragment;
411
+ const { childNodes } = fragment;
412
+ const nodes = [...childNodes];
413
+ return {
414
+ ELEMENT_NODE,
415
+ nodeType,
416
+ firstChild,
417
+ lastChild,
418
+ valueOf() {
419
+ if (childNodes.length !== nodes.length) fragment.append(...nodes);
420
+ return fragment;
421
+ }
422
+ };
405
423
  };
406
424
 
407
425
  const { isArray: isArray$1 } = Array;
408
426
 
409
427
  const aria = (node) => (values) => {
410
- for (const key in values) {
411
- const name = key === 'role' ? key : `aria-${key}`;
412
- const value = values[key];
413
- if (value == null) node.removeAttribute(name);
414
- else node.setAttribute(name, value);
415
- }
428
+ for (const key in values) {
429
+ const name = key === 'role' ? key : `aria-${key}`;
430
+ const value = values[key];
431
+ if (value == null) node.removeAttribute(name);
432
+ else node.setAttribute(name, value);
433
+ }
416
434
  };
417
435
 
418
436
  const attribute = (node, name) => {
419
- let oldValue,
420
- orphan = true;
421
- const attributeNode = document.createAttributeNS(null, name);
422
- return (newValue) => {
423
- if (oldValue !== newValue) {
424
- oldValue = newValue;
425
- if (oldValue == null) {
426
- if (!orphan) {
427
- node.removeAttributeNode(attributeNode);
428
- orphan = true;
429
- }
430
- } else {
431
- const value = newValue;
432
- if (value == null) {
433
- if (!orphan) node.removeAttributeNode(attributeNode);
434
- orphan = true;
435
- } else {
436
- attributeNode.value = value;
437
- if (orphan) {
438
- node.setAttributeNodeNS(attributeNode);
439
- orphan = false;
440
- }
441
- }
442
- }
443
- }
444
- };
437
+ let oldValue,
438
+ orphan = true;
439
+ const attributeNode = document.createAttributeNS(null, name);
440
+ return (newValue) => {
441
+ if (oldValue !== newValue) {
442
+ oldValue = newValue;
443
+ if (oldValue == null) {
444
+ if (!orphan) {
445
+ node.removeAttributeNode(attributeNode);
446
+ orphan = true;
447
+ }
448
+ } else {
449
+ const value = newValue;
450
+ if (value == null) {
451
+ if (!orphan) node.removeAttributeNode(attributeNode);
452
+ orphan = true;
453
+ } else {
454
+ attributeNode.value = value;
455
+ if (orphan) {
456
+ node.setAttributeNodeNS(attributeNode);
457
+ orphan = false;
458
+ }
459
+ }
460
+ }
461
+ }
462
+ };
445
463
  };
446
464
 
447
465
  const boolean = (node, key, oldValue) => (newValue) => {
448
- if (oldValue !== !!newValue) {
449
- // when IE won't be around anymore ...
450
- // node.toggleAttribute(key, oldValue = !!newValue);
451
- if ((oldValue = !!newValue)) node.setAttribute(key, '');
452
- else node.removeAttribute(key);
453
- }
466
+ if (oldValue !== !!newValue) {
467
+ // when IE won't be around anymore ...
468
+ // node.toggleAttribute(key, oldValue = !!newValue);
469
+ if ((oldValue = !!newValue)) node.setAttribute(key, '');
470
+ else node.removeAttribute(key);
471
+ }
454
472
  };
455
473
 
456
474
  const data =
457
- ({ dataset }) =>
458
- (values) => {
459
- for (const key in values) {
460
- const value = values[key];
461
- if (value == null) delete dataset[key];
462
- else dataset[key] = value;
463
- }
464
- };
475
+ ({ dataset }) =>
476
+ (values) => {
477
+ for (const key in values) {
478
+ const value = values[key];
479
+ if (value == null) delete dataset[key];
480
+ else dataset[key] = value;
481
+ }
482
+ };
465
483
 
466
484
  const event = (node, name) => {
467
- let oldValue,
468
- lower,
469
- type = name.slice(2);
470
- if (!(name in node) && (lower = name.toLowerCase()) in node) type = lower.slice(2);
471
- return (newValue) => {
472
- const info = isArray$1(newValue) ? newValue : [newValue, false];
473
- if (oldValue !== info[0]) {
474
- if (oldValue) node.removeEventListener(type, oldValue, info[1]);
475
- if ((oldValue = info[0])) node.addEventListener(type, oldValue, info[1]);
476
- }
477
- };
485
+ let oldValue,
486
+ lower,
487
+ type = name.slice(2);
488
+ if (!(name in node) && (lower = name.toLowerCase()) in node) type = lower.slice(2);
489
+ return (newValue) => {
490
+ const info = isArray$1(newValue) ? newValue : [newValue, false];
491
+ if (oldValue !== info[0]) {
492
+ if (oldValue) node.removeEventListener(type, oldValue, info[1]);
493
+ if ((oldValue = info[0])) node.addEventListener(type, oldValue, info[1]);
494
+ }
495
+ };
478
496
  };
479
497
 
480
498
  const ref = (node) => {
481
- let oldValue;
482
- return (value) => {
483
- if (oldValue !== value) {
484
- oldValue = value;
485
- if (typeof value === 'function') value(node);
486
- else value.current = node;
487
- }
488
- };
499
+ let oldValue;
500
+ return (value) => {
501
+ if (oldValue !== value) {
502
+ oldValue = value;
503
+ if (typeof value === 'function') value(node);
504
+ else value.current = node;
505
+ }
506
+ };
489
507
  };
490
508
 
491
509
  const setter = (node, key) =>
492
- key === 'dataset'
493
- ? data(node)
494
- : (value) => {
495
- node[key] = value;
496
- };
510
+ key === 'dataset'
511
+ ? data(node)
512
+ : (value) => {
513
+ node[key] = value;
514
+ };
497
515
 
498
516
  const text = (node) => {
499
- let oldValue;
500
- return (newValue) => {
501
- if (oldValue != newValue) {
502
- oldValue = newValue;
503
- node.textContent = newValue == null ? '' : newValue;
504
- }
505
- };
517
+ let oldValue;
518
+ return (newValue) => {
519
+ if (oldValue != newValue) {
520
+ oldValue = newValue;
521
+ node.textContent = newValue == null ? '' : newValue;
522
+ }
523
+ };
506
524
  };
507
525
 
508
526
  /**
@@ -533,148 +551,148 @@ const text = (node) => {
533
551
  * @returns {Node[]} The same list of future children.
534
552
  */
535
553
  var udomdiff = (parentNode, a, b, get, before) => {
536
- const bLength = b.length;
537
- let aEnd = a.length;
538
- let bEnd = bLength;
539
- let aStart = 0;
540
- let bStart = 0;
541
- let map = null;
542
- while (aStart < aEnd || bStart < bEnd) {
543
- // append head, tail, or nodes in between: fast path
544
- if (aEnd === aStart) {
545
- // we could be in a situation where the rest of nodes that
546
- // need to be added are not at the end, and in such case
547
- // the node to `insertBefore`, if the index is more than 0
548
- // must be retrieved, otherwise it's gonna be the first item.
549
- const node =
550
- bEnd < bLength
551
- ? bStart
552
- ? get(b[bStart - 1], -0).nextSibling
553
- : get(b[bEnd - bStart], 0)
554
- : before;
555
- while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node);
556
- }
557
- // remove head or tail: fast path
558
- else if (bEnd === bStart) {
559
- while (aStart < aEnd) {
560
- // remove the node only if it's unknown or not live
561
- if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1));
562
- aStart++;
563
- }
564
- }
565
- // same node: fast path
566
- else if (a[aStart] === b[bStart]) {
567
- aStart++;
568
- bStart++;
569
- }
570
- // same tail: fast path
571
- else if (a[aEnd - 1] === b[bEnd - 1]) {
572
- aEnd--;
573
- bEnd--;
574
- }
575
- // The once here single last swap "fast path" has been removed in v1.1.0
576
- // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
577
- // reverse swap: also fast path
578
- else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
579
- // this is a "shrink" operation that could happen in these cases:
580
- // [1, 2, 3, 4, 5]
581
- // [1, 4, 3, 2, 5]
582
- // or asymmetric too
583
- // [1, 2, 3, 4, 5]
584
- // [1, 2, 3, 5, 6, 4]
585
- const node = get(a[--aEnd], -1).nextSibling;
586
- parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
587
- parentNode.insertBefore(get(b[--bEnd], 1), node);
588
- // mark the future index as identical (yeah, it's dirty, but cheap 👍)
589
- // The main reason to do this, is that when a[aEnd] will be reached,
590
- // the loop will likely be on the fast path, as identical to b[bEnd].
591
- // In the best case scenario, the next loop will skip the tail,
592
- // but in the worst one, this node will be considered as already
593
- // processed, bailing out pretty quickly from the map index check
594
- a[aEnd] = b[bEnd];
595
- }
596
- // map based fallback, "slow" path
597
- else {
598
- // the map requires an O(bEnd - bStart) operation once
599
- // to store all future nodes indexes for later purposes.
600
- // In the worst case scenario, this is a full O(N) cost,
601
- // and such scenario happens at least when all nodes are different,
602
- // but also if both first and last items of the lists are different
603
- if (!map) {
604
- map = new Map();
605
- let i = bStart;
606
- while (i < bEnd) map.set(b[i], i++);
607
- }
608
- // if it's a future node, hence it needs some handling
609
- if (map.has(a[aStart])) {
610
- // grab the index of such node, 'cause it might have been processed
611
- const index = map.get(a[aStart]);
612
- // if it's not already processed, look on demand for the next LCS
613
- if (bStart < index && index < bEnd) {
614
- let i = aStart;
615
- // counts the amount of nodes that are the same in the future
616
- let sequence = 1;
617
- while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence) sequence++;
618
- // effort decision here: if the sequence is longer than replaces
619
- // needed to reach such sequence, which would brings again this loop
620
- // to the fast path, prepend the difference before a sequence,
621
- // and move only the future list index forward, so that aStart
622
- // and bStart will be aligned again, hence on the fast path.
623
- // An example considering aStart and bStart are both 0:
624
- // a: [1, 2, 3, 4]
625
- // b: [7, 1, 2, 3, 6]
626
- // this would place 7 before 1 and, from that time on, 1, 2, and 3
627
- // will be processed at zero cost
628
- if (sequence > index - bStart) {
629
- const node = get(a[aStart], 0);
630
- while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), node);
631
- }
632
- // if the effort wasn't good enough, fallback to a replace,
633
- // moving both source and target indexes forward, hoping that some
634
- // similar node will be found later on, to go back to the fast path
635
- else {
636
- parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
637
- }
638
- }
639
- // otherwise move the source forward, 'cause there's nothing to do
640
- else aStart++;
641
- }
642
- // this node has no meaning in the future list, so it's more than safe
643
- // to remove it, and check the next live node out instead, meaning
644
- // that only the live list index should be forwarded
645
- else parentNode.removeChild(get(a[aStart++], -1));
646
- }
647
- }
648
- return b;
554
+ const bLength = b.length;
555
+ let aEnd = a.length;
556
+ let bEnd = bLength;
557
+ let aStart = 0;
558
+ let bStart = 0;
559
+ let map = null;
560
+ while (aStart < aEnd || bStart < bEnd) {
561
+ // append head, tail, or nodes in between: fast path
562
+ if (aEnd === aStart) {
563
+ // we could be in a situation where the rest of nodes that
564
+ // need to be added are not at the end, and in such case
565
+ // the node to `insertBefore`, if the index is more than 0
566
+ // must be retrieved, otherwise it's gonna be the first item.
567
+ const node =
568
+ bEnd < bLength
569
+ ? bStart
570
+ ? get(b[bStart - 1], -0).nextSibling
571
+ : get(b[bEnd - bStart], 0)
572
+ : before;
573
+ while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node);
574
+ }
575
+ // remove head or tail: fast path
576
+ else if (bEnd === bStart) {
577
+ while (aStart < aEnd) {
578
+ // remove the node only if it's unknown or not live
579
+ if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1));
580
+ aStart++;
581
+ }
582
+ }
583
+ // same node: fast path
584
+ else if (a[aStart] === b[bStart]) {
585
+ aStart++;
586
+ bStart++;
587
+ }
588
+ // same tail: fast path
589
+ else if (a[aEnd - 1] === b[bEnd - 1]) {
590
+ aEnd--;
591
+ bEnd--;
592
+ }
593
+ // The once here single last swap "fast path" has been removed in v1.1.0
594
+ // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
595
+ // reverse swap: also fast path
596
+ else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
597
+ // this is a "shrink" operation that could happen in these cases:
598
+ // [1, 2, 3, 4, 5]
599
+ // [1, 4, 3, 2, 5]
600
+ // or asymmetric too
601
+ // [1, 2, 3, 4, 5]
602
+ // [1, 2, 3, 5, 6, 4]
603
+ const node = get(a[--aEnd], -1).nextSibling;
604
+ parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
605
+ parentNode.insertBefore(get(b[--bEnd], 1), node);
606
+ // mark the future index as identical (yeah, it's dirty, but cheap 👍)
607
+ // The main reason to do this, is that when a[aEnd] will be reached,
608
+ // the loop will likely be on the fast path, as identical to b[bEnd].
609
+ // In the best case scenario, the next loop will skip the tail,
610
+ // but in the worst one, this node will be considered as already
611
+ // processed, bailing out pretty quickly from the map index check
612
+ a[aEnd] = b[bEnd];
613
+ }
614
+ // map based fallback, "slow" path
615
+ else {
616
+ // the map requires an O(bEnd - bStart) operation once
617
+ // to store all future nodes indexes for later purposes.
618
+ // In the worst case scenario, this is a full O(N) cost,
619
+ // and such scenario happens at least when all nodes are different,
620
+ // but also if both first and last items of the lists are different
621
+ if (!map) {
622
+ map = new Map();
623
+ let i = bStart;
624
+ while (i < bEnd) map.set(b[i], i++);
625
+ }
626
+ // if it's a future node, hence it needs some handling
627
+ if (map.has(a[aStart])) {
628
+ // grab the index of such node, 'cause it might have been processed
629
+ const index = map.get(a[aStart]);
630
+ // if it's not already processed, look on demand for the next LCS
631
+ if (bStart < index && index < bEnd) {
632
+ let i = aStart;
633
+ // counts the amount of nodes that are the same in the future
634
+ let sequence = 1;
635
+ while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence) sequence++;
636
+ // effort decision here: if the sequence is longer than replaces
637
+ // needed to reach such sequence, which would brings again this loop
638
+ // to the fast path, prepend the difference before a sequence,
639
+ // and move only the future list index forward, so that aStart
640
+ // and bStart will be aligned again, hence on the fast path.
641
+ // An example considering aStart and bStart are both 0:
642
+ // a: [1, 2, 3, 4]
643
+ // b: [7, 1, 2, 3, 6]
644
+ // this would place 7 before 1 and, from that time on, 1, 2, and 3
645
+ // will be processed at zero cost
646
+ if (sequence > index - bStart) {
647
+ const node = get(a[aStart], 0);
648
+ while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), node);
649
+ }
650
+ // if the effort wasn't good enough, fallback to a replace,
651
+ // moving both source and target indexes forward, hoping that some
652
+ // similar node will be found later on, to go back to the fast path
653
+ else {
654
+ parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
655
+ }
656
+ }
657
+ // otherwise move the source forward, 'cause there's nothing to do
658
+ else aStart++;
659
+ }
660
+ // this node has no meaning in the future list, so it's more than safe
661
+ // to remove it, and check the next live node out instead, meaning
662
+ // that only the live list index should be forwarded
663
+ else parentNode.removeChild(get(a[aStart++], -1));
664
+ }
665
+ }
666
+ return b;
649
667
  };
650
668
 
651
669
  const { isArray, prototype } = Array;
652
670
  const { indexOf } = prototype;
653
671
 
654
672
  const {
655
- createDocumentFragment,
656
- createElement,
657
- createElementNS,
658
- createTextNode,
659
- createTreeWalker,
660
- importNode
673
+ createDocumentFragment,
674
+ createElement,
675
+ createElementNS,
676
+ createTextNode,
677
+ createTreeWalker,
678
+ importNode
661
679
  } = new Proxy(typeof window == 'undefined' ? {} : window.document, {
662
- get: (target, method) => (target[method] || function () {}).bind(target)
680
+ get: (target, method) => (target[method] || function () {}).bind(target)
663
681
  });
664
682
 
665
683
  const createHTML = (html) => {
666
- const template = createElement('template');
667
- template.innerHTML = html;
668
- return template.content;
684
+ const template = createElement('template');
685
+ template.innerHTML = html;
686
+ return template.content;
669
687
  };
670
688
 
671
689
  let xml;
672
690
  const createSVG = (svg) => {
673
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
674
- xml.innerHTML = svg;
675
- const content = createDocumentFragment();
676
- content.append(...xml.childNodes);
677
- return content;
691
+ if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
692
+ xml.innerHTML = svg;
693
+ const content = createDocumentFragment();
694
+ content.append(...xml.childNodes);
695
+ return content;
678
696
  };
679
697
 
680
698
  const createContent = (text, svg) => (svg ? createSVG(text) : createHTML(text));
@@ -684,91 +702,91 @@ const reducePath = ({ childNodes }, i) => childNodes[i];
684
702
 
685
703
  // this helper avoid code bloat around handleAnything() callback
686
704
  const diff = (comment, oldNodes, newNodes) =>
687
- udomdiff(
688
- comment.parentNode,
689
- // TODO: there is a possible edge case where a node has been
690
- // removed manually, or it was a keyed one, attached
691
- // to a shared reference between renders.
692
- // In this case udomdiff might fail at removing such node
693
- // as its parent won't be the expected one.
694
- // The best way to avoid this issue is to filter oldNodes
695
- // in search of those not live, or not in the current parent
696
- // anymore, but this would require both a change to uwire,
697
- // exposing a parentNode from the firstChild, as example,
698
- // but also a filter per each diff that should exclude nodes
699
- // that are not in there, penalizing performance quite a lot.
700
- // As this has been also a potential issue with domdiff,
701
- // and both lighterhtml and hyperHTML might fail with this
702
- // very specific edge case, I might as well document this possible
703
- // "diffing shenanigan" and call it a day.
704
- oldNodes,
705
- newNodes,
706
- diffable,
707
- comment
708
- );
705
+ udomdiff(
706
+ comment.parentNode,
707
+ // TODO: there is a possible edge case where a node has been
708
+ // removed manually, or it was a keyed one, attached
709
+ // to a shared reference between renders.
710
+ // In this case udomdiff might fail at removing such node
711
+ // as its parent won't be the expected one.
712
+ // The best way to avoid this issue is to filter oldNodes
713
+ // in search of those not live, or not in the current parent
714
+ // anymore, but this would require both a change to uwire,
715
+ // exposing a parentNode from the firstChild, as example,
716
+ // but also a filter per each diff that should exclude nodes
717
+ // that are not in there, penalizing performance quite a lot.
718
+ // As this has been also a potential issue with domdiff,
719
+ // and both lighterhtml and hyperHTML might fail with this
720
+ // very specific edge case, I might as well document this possible
721
+ // "diffing shenanigan" and call it a day.
722
+ oldNodes,
723
+ newNodes,
724
+ diffable,
725
+ comment
726
+ );
709
727
 
710
728
  // if an interpolation represents a comment, the whole
711
729
  // diffing will be related to such comment.
712
730
  // This helper is in charge of understanding how the new
713
731
  // content for such interpolation/hole should be updated
714
732
  const handleAnything = (comment) => {
715
- let oldValue,
716
- text,
717
- nodes = [];
718
- const anyContent = (newValue) => {
719
- switch (typeof newValue) {
720
- // primitives are handled as text content
721
- case 'string':
722
- case 'number':
723
- case 'boolean':
724
- if (oldValue !== newValue) {
725
- oldValue = newValue;
726
- if (!text) text = createTextNode('');
727
- text.data = newValue;
728
- nodes = diff(comment, nodes, [text]);
729
- }
730
- break;
731
- // null, and undefined are used to cleanup previous content
732
- case 'object':
733
- case 'undefined':
734
- if (newValue == null) {
735
- if (oldValue != newValue) {
736
- oldValue = newValue;
737
- nodes = diff(comment, nodes, []);
738
- }
739
- break;
740
- }
741
- // arrays and nodes have a special treatment
742
- if (isArray(newValue)) {
743
- oldValue = newValue;
744
- // arrays can be used to cleanup, if empty
745
- if (newValue.length === 0) nodes = diff(comment, nodes, []);
746
- // or diffed, if these contains nodes or "wires"
747
- else if (typeof newValue[0] === 'object') nodes = diff(comment, nodes, newValue);
748
- // in all other cases the content is stringified as is
749
- else anyContent(String(newValue));
750
- break;
751
- }
752
- // if the new value is a DOM node, or a wire, and it's
753
- // different from the one already live, then it's diffed.
754
- // if the node is a fragment, it's appended once via its childNodes
755
- // There is no `else` here, meaning if the content
756
- // is not expected one, nothing happens, as easy as that.
757
- if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
758
- oldValue = newValue;
759
- nodes = diff(
760
- comment,
761
- nodes,
762
- newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]
763
- );
764
- }
765
- break;
766
- case 'function':
767
- anyContent(newValue(comment));
768
- break;
769
- }
770
- };
771
- return anyContent;
733
+ let oldValue,
734
+ text,
735
+ nodes = [];
736
+ const anyContent = (newValue) => {
737
+ switch (typeof newValue) {
738
+ // primitives are handled as text content
739
+ case 'string':
740
+ case 'number':
741
+ case 'boolean':
742
+ if (oldValue !== newValue) {
743
+ oldValue = newValue;
744
+ if (!text) text = createTextNode('');
745
+ text.data = newValue;
746
+ nodes = diff(comment, nodes, [text]);
747
+ }
748
+ break;
749
+ // null, and undefined are used to cleanup previous content
750
+ case 'object':
751
+ case 'undefined':
752
+ if (newValue == null) {
753
+ if (oldValue != newValue) {
754
+ oldValue = newValue;
755
+ nodes = diff(comment, nodes, []);
756
+ }
757
+ break;
758
+ }
759
+ // arrays and nodes have a special treatment
760
+ if (isArray(newValue)) {
761
+ oldValue = newValue;
762
+ // arrays can be used to cleanup, if empty
763
+ if (newValue.length === 0) nodes = diff(comment, nodes, []);
764
+ // or diffed, if these contains nodes or "wires"
765
+ else if (typeof newValue[0] === 'object') nodes = diff(comment, nodes, newValue);
766
+ // in all other cases the content is stringified as is
767
+ else anyContent(String(newValue));
768
+ break;
769
+ }
770
+ // if the new value is a DOM node, or a wire, and it's
771
+ // different from the one already live, then it's diffed.
772
+ // if the node is a fragment, it's appended once via its childNodes
773
+ // There is no `else` here, meaning if the content
774
+ // is not expected one, nothing happens, as easy as that.
775
+ if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
776
+ oldValue = newValue;
777
+ nodes = diff(
778
+ comment,
779
+ nodes,
780
+ newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]
781
+ );
782
+ }
783
+ break;
784
+ case 'function':
785
+ anyContent(newValue(comment));
786
+ break;
787
+ }
788
+ };
789
+ return anyContent;
772
790
  };
773
791
 
774
792
  // attributes can be:
@@ -782,25 +800,25 @@ const handleAnything = (comment) => {
782
800
  // * onevent=${...} to automatically handle event listeners
783
801
  // * generic=${...} to handle an attribute just like an attribute
784
802
  const handleAttribute = (node, name /*, svg*/) => {
785
- switch (name[0]) {
786
- case '?':
787
- return boolean(node, name.slice(1), false);
788
- case '.':
789
- return setter(node, name.slice(1));
790
- case '@':
791
- return event(node, 'on' + name.slice(1));
792
- case 'o':
793
- if (name[1] === 'n') return event(node, name);
794
- }
795
-
796
- switch (name) {
797
- case 'ref':
798
- return ref(node);
799
- case 'aria':
800
- return aria(node);
801
- }
802
-
803
- return attribute(node, name /*, svg*/);
803
+ switch (name[0]) {
804
+ case '?':
805
+ return boolean(node, name.slice(1), false);
806
+ case '.':
807
+ return setter(node, name.slice(1));
808
+ case '@':
809
+ return event(node, 'on' + name.slice(1));
810
+ case 'o':
811
+ if (name[1] === 'n') return event(node, name);
812
+ }
813
+
814
+ switch (name) {
815
+ case 'ref':
816
+ return ref(node);
817
+ case 'aria':
818
+ return aria(node);
819
+ }
820
+
821
+ return attribute(node, name /*, svg*/);
804
822
  };
805
823
 
806
824
  // each mapped update carries the update type and its path
@@ -808,27 +826,27 @@ const handleAttribute = (node, name /*, svg*/) => {
808
826
  // the path is how to retrieve the related node to update.
809
827
  // In the attribute case, the attribute name is also carried along.
810
828
  function handlers(options) {
811
- const { type, path } = options;
812
- const node = path.reduceRight(reducePath, this);
813
- return type === 'node'
814
- ? handleAnything(node)
815
- : type === 'attr'
816
- ? handleAttribute(node, options.name /*, options.svg*/)
817
- : text(node);
829
+ const { type, path } = options;
830
+ const node = path.reduceRight(reducePath, this);
831
+ return type === 'node'
832
+ ? handleAnything(node)
833
+ : type === 'attr'
834
+ ? handleAttribute(node, options.name /*, options.svg*/)
835
+ : text(node);
818
836
  }
819
837
 
820
838
  // from a fragment container, create an array of indexes
821
839
  // related to its child nodes, so that it's possible
822
840
  // to retrieve later on exact node via reducePath
823
841
  const createPath = (node) => {
824
- const path = [];
825
- let { parentNode } = node;
826
- while (parentNode) {
827
- path.push(indexOf.call(parentNode.childNodes, node));
828
- node = parentNode;
829
- ({ parentNode } = node);
830
- }
831
- return path;
842
+ const path = [];
843
+ let { parentNode } = node;
844
+ while (parentNode) {
845
+ path.push(indexOf.call(parentNode.childNodes, node));
846
+ node = parentNode;
847
+ ({ parentNode } = node);
848
+ }
849
+ return path;
832
850
  };
833
851
 
834
852
  // the prefix is used to identify either comments, attributes, or nodes
@@ -850,100 +868,100 @@ const cache$1 = new WeakMapSet();
850
868
  const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
851
869
 
852
870
  const createCache = () => ({
853
- stack: [], // each template gets a stack for each interpolation "hole"
854
-
855
- entry: null, // each entry contains details, such as:
856
- // * the template that is representing
857
- // * the type of node it represents (html or svg)
858
- // * the content fragment with all nodes
859
- // * the list of updates per each node (template holes)
860
- // * the "wired" node or fragment that will get updates
861
- // if the template or type are different from the previous one
862
- // the entry gets re-created each time
863
-
864
- wire: null // each rendered node represent some wired content and
865
- // this reference to the latest one. If different, the node
866
- // will be cleaned up and the new "wire" will be appended
871
+ stack: [], // each template gets a stack for each interpolation "hole"
872
+
873
+ entry: null, // each entry contains details, such as:
874
+ // * the template that is representing
875
+ // * the type of node it represents (html or svg)
876
+ // * the content fragment with all nodes
877
+ // * the list of updates per each node (template holes)
878
+ // * the "wired" node or fragment that will get updates
879
+ // if the template or type are different from the previous one
880
+ // the entry gets re-created each time
881
+
882
+ wire: null // each rendered node represent some wired content and
883
+ // this reference to the latest one. If different, the node
884
+ // will be cleaned up and the new "wire" will be appended
867
885
  });
868
886
 
869
887
  // the entry stored in the rendered node cache, and per each "hole"
870
888
  const createEntry = (type, template) => {
871
- const { content, updates } = mapUpdates(type, template);
872
- return { type, template, content, updates, wire: null };
889
+ const { content, updates } = mapUpdates(type, template);
890
+ return { type, template, content, updates, wire: null };
873
891
  };
874
892
 
875
893
  // a template is instrumented to be able to retrieve where updates are needed.
876
894
  // Each unique template becomes a fragment, cloned once per each other
877
895
  // operation based on the same template, i.e. data => html`<p>${data}</p>`
878
896
  const mapTemplate = (type, template) => {
879
- const svg = type === 'svg';
880
- const text = instrument(template, prefix, svg);
881
- const content = createContent(text, svg);
882
- // once instrumented and reproduced as fragment, it's crawled
883
- // to find out where each update is in the fragment tree
884
- const tw = createTreeWalker(content, 1 | 128);
885
- const nodes = [];
886
- const length = template.length - 1;
887
- let i = 0;
888
- // updates are searched via unique names, linearly increased across the tree
889
- // <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
890
- let search = `${prefix}${i}`;
891
- while (i < length) {
892
- const node = tw.nextNode();
893
- // if not all updates are bound but there's nothing else to crawl
894
- // it means that there is something wrong with the template.
895
- if (!node) throw `bad template: ${text}`;
896
- // if the current node is a comment, and it contains isµX
897
- // it means the update should take care of any content
898
- if (node.nodeType === 8) {
899
- // The only comments to be considered are those
900
- // which content is exactly the same as the searched one.
901
- if (node.data === search) {
902
- nodes.push({ type: 'node', path: createPath(node) });
903
- search = `${prefix}${++i}`;
904
- }
905
- } else {
906
- // if the node is not a comment, loop through all its attributes
907
- // named isµX and relate attribute updates to this node and the
908
- // attribute name, retrieved through node.getAttribute("isµX")
909
- // the isµX attribute will be removed as irrelevant for the layout
910
- // let svg = -1;
911
- while (node.hasAttribute(search)) {
912
- nodes.push({
913
- type: 'attr',
914
- path: createPath(node),
915
- name: node.getAttribute(search)
916
- });
917
- node.removeAttribute(search);
918
- search = `${prefix}${++i}`;
919
- }
920
- // if the node was a style, textarea, or others, check its content
921
- // and if it is <!--isµX--> then update tex-only this node
922
- if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
923
- node.textContent = '';
924
- nodes.push({ type: 'text', path: createPath(node) });
925
- search = `${prefix}${++i}`;
926
- }
927
- }
928
- }
929
- // once all nodes to update, or their attributes, are known, the content
930
- // will be cloned in the future to represent the template, and all updates
931
- // related to such content retrieved right away without needing to re-crawl
932
- // the exact same template, and its content, more than once.
933
- return { content, nodes };
897
+ const svg = type === 'svg';
898
+ const text = instrument(template, prefix, svg);
899
+ const content = createContent(text, svg);
900
+ // once instrumented and reproduced as fragment, it's crawled
901
+ // to find out where each update is in the fragment tree
902
+ const tw = createTreeWalker(content, 1 | 128);
903
+ const nodes = [];
904
+ const length = template.length - 1;
905
+ let i = 0;
906
+ // updates are searched via unique names, linearly increased across the tree
907
+ // <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
908
+ let search = `${prefix}${i}`;
909
+ while (i < length) {
910
+ const node = tw.nextNode();
911
+ // if not all updates are bound but there's nothing else to crawl
912
+ // it means that there is something wrong with the template.
913
+ if (!node) throw `bad template: ${text}`;
914
+ // if the current node is a comment, and it contains isµX
915
+ // it means the update should take care of any content
916
+ if (node.nodeType === 8) {
917
+ // The only comments to be considered are those
918
+ // which content is exactly the same as the searched one.
919
+ if (node.data === search) {
920
+ nodes.push({ type: 'node', path: createPath(node) });
921
+ search = `${prefix}${++i}`;
922
+ }
923
+ } else {
924
+ // if the node is not a comment, loop through all its attributes
925
+ // named isµX and relate attribute updates to this node and the
926
+ // attribute name, retrieved through node.getAttribute("isµX")
927
+ // the isµX attribute will be removed as irrelevant for the layout
928
+ // let svg = -1;
929
+ while (node.hasAttribute(search)) {
930
+ nodes.push({
931
+ type: 'attr',
932
+ path: createPath(node),
933
+ name: node.getAttribute(search)
934
+ });
935
+ node.removeAttribute(search);
936
+ search = `${prefix}${++i}`;
937
+ }
938
+ // if the node was a style, textarea, or others, check its content
939
+ // and if it is <!--isµX--> then update tex-only this node
940
+ if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
941
+ node.textContent = '';
942
+ nodes.push({ type: 'text', path: createPath(node) });
943
+ search = `${prefix}${++i}`;
944
+ }
945
+ }
946
+ }
947
+ // once all nodes to update, or their attributes, are known, the content
948
+ // will be cloned in the future to represent the template, and all updates
949
+ // related to such content retrieved right away without needing to re-crawl
950
+ // the exact same template, and its content, more than once.
951
+ return { content, nodes };
934
952
  };
935
953
 
936
954
  // if a template is unknown, perform the previous mapping, otherwise grab
937
955
  // its details such as the fragment with all nodes, and updates info.
938
956
  const mapUpdates = (type, template) => {
939
- const { content, nodes } =
940
- cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
941
- // clone deeply the fragment
942
- const fragment = importNode(content, true);
943
- // and relate an update handler per each node that needs one
944
- const updates = nodes.map(handlers, fragment);
945
- // return the fragment and all updates to use within its nodes
946
- return { content: fragment, updates };
957
+ const { content, nodes } =
958
+ cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
959
+ // clone deeply the fragment
960
+ const fragment = importNode(content, true);
961
+ // and relate an update handler per each node that needs one
962
+ const updates = nodes.map(handlers, fragment);
963
+ // return the fragment and all updates to use within its nodes
964
+ return { content: fragment, updates };
947
965
  };
948
966
 
949
967
  // as html and svg can be nested calls, but no parent node is known
@@ -951,48 +969,48 @@ const mapUpdates = (type, template) => {
951
969
  // discover what to do with each interpolation, which will result
952
970
  // into an update operation.
953
971
  const unroll = (info, { type, template, values }) => {
954
- // interpolations can contain holes and arrays, so these need
955
- // to be recursively discovered
956
- const length = unrollValues(info, values);
957
- let { entry } = info;
958
- // if the cache entry is either null or different from the template
959
- // and the type this unroll should resolve, create a new entry
960
- // assigning a new content fragment and the list of updates.
961
- if (!entry || entry.template !== template || entry.type !== type)
962
- info.entry = entry = createEntry(type, template);
963
- const { content, updates, wire } = entry;
964
- // even if the fragment and its nodes is not live yet,
965
- // it is already possible to update via interpolations values.
966
- for (let i = 0; i < length; i++) updates[i](values[i]);
967
- // if the entry was new, or representing a different template or type,
968
- // create a new persistent entity to use during diffing.
969
- // This is simply a DOM node, when the template has a single container,
970
- // as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
971
- return wire || (entry.wire = persistent(content));
972
+ // interpolations can contain holes and arrays, so these need
973
+ // to be recursively discovered
974
+ const length = unrollValues(info, values);
975
+ let { entry } = info;
976
+ // if the cache entry is either null or different from the template
977
+ // and the type this unroll should resolve, create a new entry
978
+ // assigning a new content fragment and the list of updates.
979
+ if (!entry || entry.template !== template || entry.type !== type)
980
+ info.entry = entry = createEntry(type, template);
981
+ const { content, updates, wire } = entry;
982
+ // even if the fragment and its nodes is not live yet,
983
+ // it is already possible to update via interpolations values.
984
+ for (let i = 0; i < length; i++) updates[i](values[i]);
985
+ // if the entry was new, or representing a different template or type,
986
+ // create a new persistent entity to use during diffing.
987
+ // This is simply a DOM node, when the template has a single container,
988
+ // as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
989
+ return wire || (entry.wire = persistent(content));
972
990
  };
973
991
 
974
992
  // the stack retains, per each interpolation value, the cache
975
993
  // related to each interpolation value, or null, if the render
976
994
  // was conditional and the value is not special (Array or Hole)
977
995
  const unrollValues = ({ stack }, values) => {
978
- const { length } = values;
979
- for (let i = 0; i < length; i++) {
980
- const hole = values[i];
981
- // each Hole gets unrolled and re-assigned as value
982
- // so that domdiff will deal with a node/wire, not with a hole
983
- if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
984
- // arrays are recursively resolved so that each entry will contain
985
- // also a DOM node or a wire, hence it can be diffed if/when needed
986
- else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole);
987
- // if the value is nothing special, the stack doesn't need to retain data
988
- // this is useful also to cleanup previously retained data, if the value
989
- // was a Hole, or an Array, but not anymore, i.e.:
990
- // const update = content => html`<div>${content}</div>`;
991
- // update(listOfItems); update(null); update(html`hole`)
992
- else stack[i] = null;
993
- }
994
- if (length < stack.length) stack.splice(length);
995
- return length;
996
+ const { length } = values;
997
+ for (let i = 0; i < length; i++) {
998
+ const hole = values[i];
999
+ // each Hole gets unrolled and re-assigned as value
1000
+ // so that domdiff will deal with a node/wire, not with a hole
1001
+ if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
1002
+ // arrays are recursively resolved so that each entry will contain
1003
+ // also a DOM node or a wire, hence it can be diffed if/when needed
1004
+ else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole);
1005
+ // if the value is nothing special, the stack doesn't need to retain data
1006
+ // this is useful also to cleanup previously retained data, if the value
1007
+ // was a Hole, or an Array, but not anymore, i.e.:
1008
+ // const update = content => html`<div>${content}</div>`;
1009
+ // update(listOfItems); update(null); update(html`hole`)
1010
+ else stack[i] = null;
1011
+ }
1012
+ if (length < stack.length) stack.splice(length);
1013
+ return length;
996
1014
  };
997
1015
 
998
1016
  /**
@@ -1003,44 +1021,44 @@ const unrollValues = ({ stack }, values) => {
1003
1021
  * @param {Array} values Zero, one, or more interpolated values to render.
1004
1022
  */
1005
1023
  class Hole {
1006
- constructor(type, template, values) {
1007
- this.type = type;
1008
- this.template = template;
1009
- this.values = values;
1010
- }
1024
+ constructor(type, template, values) {
1025
+ this.type = type;
1026
+ this.template = template;
1027
+ this.values = values;
1028
+ }
1011
1029
  }
1012
1030
 
1013
1031
  // both `html` and `svg` template literal tags are polluted
1014
1032
  // with a `for(ref[, id])` and a `node` tag too
1015
1033
  const tag = (type) => {
1016
- // both `html` and `svg` tags have their own cache
1017
- const keyed = new WeakMapSet();
1018
- // keyed operations always re-use the same cache and unroll
1019
- // the template and its interpolations right away
1020
- const fixed =
1021
- (cache) =>
1022
- (template, ...values) =>
1023
- unroll(cache, { type, template, values });
1024
- return Object.assign(
1025
- // non keyed operations are recognized as instance of Hole
1026
- // during the "unroll", recursively resolved and updated
1027
- (template, ...values) => new Hole(type, template, values),
1028
- {
1029
- // keyed operations need a reference object, usually the parent node
1030
- // which is showing keyed results, and optionally a unique id per each
1031
- // related node, handy with JSON results and mutable list of objects
1032
- // that usually carry a unique identifier
1033
- for(ref, id) {
1034
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
1035
- return memo.get(id) || memo.set(id, fixed(createCache()));
1036
- },
1037
- // it is possible to create one-off content out of the box via node tag
1038
- // this might return the single created node, or a fragment with all
1039
- // nodes present at the root level and, of course, their child nodes
1040
- node: (template, ...values) =>
1041
- unroll(createCache(), new Hole(type, template, values)).valueOf()
1042
- }
1043
- );
1034
+ // both `html` and `svg` tags have their own cache
1035
+ const keyed = new WeakMapSet();
1036
+ // keyed operations always re-use the same cache and unroll
1037
+ // the template and its interpolations right away
1038
+ const fixed =
1039
+ (cache) =>
1040
+ (template, ...values) =>
1041
+ unroll(cache, { type, template, values });
1042
+ return Object.assign(
1043
+ // non keyed operations are recognized as instance of Hole
1044
+ // during the "unroll", recursively resolved and updated
1045
+ (template, ...values) => new Hole(type, template, values),
1046
+ {
1047
+ // keyed operations need a reference object, usually the parent node
1048
+ // which is showing keyed results, and optionally a unique id per each
1049
+ // related node, handy with JSON results and mutable list of objects
1050
+ // that usually carry a unique identifier
1051
+ for(ref, id) {
1052
+ const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
1053
+ return memo.get(id) || memo.set(id, fixed(createCache()));
1054
+ },
1055
+ // it is possible to create one-off content out of the box via node tag
1056
+ // this might return the single created node, or a fragment with all
1057
+ // nodes present at the root level and, of course, their child nodes
1058
+ node: (template, ...values) =>
1059
+ unroll(createCache(), new Hole(type, template, values)).valueOf()
1060
+ }
1061
+ );
1044
1062
  };
1045
1063
 
1046
1064
  // each rendered node gets its own cache
@@ -1052,18 +1070,18 @@ const cache = new WeakMapSet();
1052
1070
  // and the new new content is appended, and if such content is a Hole
1053
1071
  // then it's "unrolled" to resolve all its inner nodes.
1054
1072
  const render = (where, what) => {
1055
- const hole = typeof what === 'function' ? what() : what;
1056
- const info = cache.get(where) || cache.set(where, createCache());
1057
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
1058
- if (wire !== info.wire) {
1059
- info.wire = wire;
1060
- // valueOf() simply returns the node itself, but in case it was a "wire"
1061
- // it will eventually re-append all nodes to its fragment so that such
1062
- // fragment can be re-appended many times in a meaningful way
1063
- // (wires are basically persistent fragments facades with special behavior)
1064
- where.replaceChildren(wire.valueOf());
1065
- }
1066
- return where;
1073
+ const hole = typeof what === 'function' ? what() : what;
1074
+ const info = cache.get(where) || cache.set(where, createCache());
1075
+ const wire = hole instanceof Hole ? unroll(info, hole) : hole;
1076
+ if (wire !== info.wire) {
1077
+ info.wire = wire;
1078
+ // valueOf() simply returns the node itself, but in case it was a "wire"
1079
+ // it will eventually re-append all nodes to its fragment so that such
1080
+ // fragment can be re-appended many times in a meaningful way
1081
+ // (wires are basically persistent fragments facades with special behavior)
1082
+ where.replaceChildren(wire.valueOf());
1083
+ }
1084
+ return where;
1067
1085
  };
1068
1086
 
1069
1087
  const html$1 = tag('html');
@@ -1077,8 +1095,10 @@ tag('svg');
1077
1095
  * @param callback Invoked when the rendering phase is completed.
1078
1096
  */
1079
1097
  const requestUpdate = (target, name, previous, callback) => {
1098
+ // Ensure API_STACKS exists on target
1099
+ target[API_STACKS] ||= new Map();
1080
1100
  // Creates/Gets a stacks.
1081
- const stacks = (target[API_STACKS] ||= new Map());
1101
+ const stacks = target[API_STACKS];
1082
1102
  // Creates/Updates a stack.
1083
1103
  const stack = stacks.get(name) || { callbacks: [], previous };
1084
1104
  // Adds the callback to the stack, if exists.
@@ -1101,7 +1121,7 @@ const requestUpdate = (target, name, previous, callback) => {
1101
1121
  // Invokes requests' callback.
1102
1122
  stacks.forEach((state) => {
1103
1123
  state.callbacks.forEach((callback, index, callbacks) => {
1104
- callback(callbacks.length - 1 != index);
1124
+ callback(callbacks.length - 1 !== index);
1105
1125
  });
1106
1126
  });
1107
1127
  // TODO
@@ -1119,7 +1139,7 @@ const requestUpdate = (target, name, previous, callback) => {
1119
1139
  if (!hasVariable && localSheet)
1120
1140
  return;
1121
1141
  const parsed = raw
1122
- .replace(regex1, (match, key) => {
1142
+ .replace(regex1, (_match, key) => {
1123
1143
  let value = target;
1124
1144
  for (const section of key.split('-')) {
1125
1145
  value = value?.[section];
@@ -1130,7 +1150,7 @@ const requestUpdate = (target, name, previous, callback) => {
1130
1150
  if (!localSheet) {
1131
1151
  localSheet = new CSSStyleSheet();
1132
1152
  target[API_STYLE] = localSheet;
1133
- shadowRoot(target).adoptedStyleSheets.push(localSheet);
1153
+ shadowRoot(target)?.adoptedStyleSheets.push(localSheet);
1134
1154
  }
1135
1155
  const localStyle = parsed.replace(regex3, '');
1136
1156
  localSheet.replaceSync(localStyle);
@@ -1145,7 +1165,7 @@ const requestUpdate = (target, name, previous, callback) => {
1145
1165
  ?.match(regex3)
1146
1166
  ?.join('')
1147
1167
  ?.replaceAll('global', '')
1148
- ?.replaceAll(':host', getTag(target));
1168
+ ?.replaceAll(':host', getTag(target) || '');
1149
1169
  globalSheet.replaceSync(globalStyle);
1150
1170
  })();
1151
1171
  // Calls the lifecycle's callback after the rendering phase.
@@ -1169,9 +1189,15 @@ const slots = (target) => {
1169
1189
  const slots = {};
1170
1190
  const children = Array.from(element.childNodes);
1171
1191
  for (const child of children) {
1172
- if (child.nodeName == '#comment')
1192
+ if (child.nodeType === Node.COMMENT_NODE)
1173
1193
  continue;
1174
- const name = child['slot'] || (child.nodeValue?.trim() && 'default') || ('slot' in child && 'default');
1194
+ let name;
1195
+ if (child instanceof HTMLElement) {
1196
+ name = child.slot || 'default';
1197
+ }
1198
+ else if (child.nodeType === Node.TEXT_NODE && child.nodeValue?.trim()) {
1199
+ name = 'default';
1200
+ }
1175
1201
  if (!name)
1176
1202
  continue;
1177
1203
  slots[name] = true;
@@ -1194,10 +1220,10 @@ const toCSSColor = (input) => {
1194
1220
  };
1195
1221
 
1196
1222
  const toCSSUnit = (input) => {
1197
- if (input == null || input === '') {
1223
+ if (input === undefined || input === null || input === '') {
1198
1224
  return;
1199
1225
  }
1200
- if (typeof input === 'number' || !isNaN(Number(input))) {
1226
+ if (typeof input === 'number' || (typeof input === 'string' && !Number.isNaN(Number(input)))) {
1201
1227
  return `${input}px`;
1202
1228
  }
1203
1229
  if (/^\d+(\.\d+)?(px|pt|cm|mm|in|em|rem|%|vw|vh)$/.test(input)) {
@@ -1206,7 +1232,7 @@ const toCSSUnit = (input) => {
1206
1232
  };
1207
1233
 
1208
1234
  function toDecorator(util, ...args) {
1209
- return function (target, key) {
1235
+ return (target, key) => {
1210
1236
  defineProperty(target, key, {
1211
1237
  get() {
1212
1238
  return util(this, ...args);
@@ -1219,7 +1245,8 @@ const toProperty = (input, type) => {
1219
1245
  if (type === undefined)
1220
1246
  return input;
1221
1247
  const string = `${input}`;
1222
- if (TYPE_BOOLEAN & type || type === Boolean) {
1248
+ const typeNumber = typeof type === 'number' ? type : 0;
1249
+ if (TYPE_BOOLEAN & typeNumber || type === Boolean) {
1223
1250
  if (string === '')
1224
1251
  return true;
1225
1252
  if (string === 'true')
@@ -1227,46 +1254,55 @@ const toProperty = (input, type) => {
1227
1254
  if (string === 'false')
1228
1255
  return false;
1229
1256
  }
1230
- if (TYPE_NUMBER & type || type === Number) {
1231
- if (string != '' && !isNaN(input)) {
1232
- return parseFloat(input);
1257
+ if (TYPE_NUMBER & typeNumber || type === Number) {
1258
+ if (string !== '' && !Number.isNaN(Number(input))) {
1259
+ return parseFloat(string);
1233
1260
  }
1234
1261
  }
1235
- if (TYPE_NULL & type || type === null) {
1262
+ if (TYPE_NULL & typeNumber || type === null) {
1236
1263
  if (string === 'null') {
1237
1264
  return null;
1238
1265
  }
1239
1266
  }
1240
- if (TYPE_DATE & type || type === Date) {
1241
- const value = new Date(input);
1242
- if (value.toString() != 'Invalid Date') {
1267
+ if (TYPE_DATE & typeNumber || type === Date) {
1268
+ const value = new Date(string);
1269
+ if (!Number.isNaN(value.getTime())) {
1243
1270
  return value;
1244
1271
  }
1245
1272
  }
1246
- if (TYPE_ARRAY & type || type === Array) {
1273
+ if (TYPE_ARRAY & typeNumber || type === Array) {
1247
1274
  try {
1248
- const value = JSON.parse(input);
1249
- if (typeOf(value) == 'array') {
1275
+ const value = JSON.parse(string);
1276
+ if (typeOf(value) === 'array')
1250
1277
  return value;
1251
- }
1252
1278
  }
1253
1279
  catch { }
1254
1280
  }
1255
- if (TYPE_OBJECT & type || type === Object) {
1281
+ if (TYPE_OBJECT & typeNumber || type === Object) {
1256
1282
  try {
1257
- const value = JSON.parse(input);
1258
- if (typeOf(value) == 'object') {
1283
+ const value = JSON.parse(string);
1284
+ if (typeOf(value) === 'object') {
1259
1285
  return value;
1260
1286
  }
1261
1287
  }
1262
1288
  catch { }
1263
1289
  }
1264
- if (TYPE_UNDEFINED & type || type === undefined) {
1290
+ if (TYPE_UNDEFINED & typeNumber) {
1265
1291
  if (string === 'undefined') {
1266
1292
  return undefined;
1267
1293
  }
1268
1294
  }
1269
- return input;
1295
+ if (TYPE_STRING & typeNumber || type === String) {
1296
+ return string;
1297
+ }
1298
+ // TODO: BigInt, Enum, Function
1299
+ try {
1300
+ // TODO
1301
+ return JSON.parse(string);
1302
+ }
1303
+ catch {
1304
+ return input;
1305
+ }
1270
1306
  };
1271
1307
 
1272
1308
  /**
@@ -1274,13 +1310,15 @@ const toProperty = (input, type) => {
1274
1310
  */
1275
1311
  const toUnit = (input, unit = 'px') => {
1276
1312
  if (input === null || input === undefined || input === '')
1277
- return input;
1278
- if (isNaN(+input))
1279
1313
  return String(input);
1280
- return Number(input) + unit;
1314
+ if (Number.isNaN(Number(input)))
1315
+ return String(input);
1316
+ return `${Number(input)}${unit}`;
1281
1317
  };
1282
1318
 
1283
- const wrapMethod = (mode, target, key, handler) => {
1319
+ const wrapMethod = (mode, target, key,
1320
+ // biome-ignore lint: TODO
1321
+ handler) => {
1284
1322
  // Gets the original function
1285
1323
  const original = target[key];
1286
1324
  // Validate target property
@@ -1290,13 +1328,13 @@ const wrapMethod = (mode, target, key, handler) => {
1290
1328
  // Creates new function
1291
1329
  function wrapped(...args) {
1292
1330
  // Calls the handler before the original
1293
- if (mode == 'before') {
1331
+ if (mode === 'before') {
1294
1332
  handler.apply(this, args);
1295
1333
  }
1296
1334
  // Calls the original
1297
1335
  const result = original?.apply(this, args);
1298
1336
  // Calls the handler after the original
1299
- if (mode == 'after') {
1337
+ if (mode === 'after') {
1300
1338
  handler.apply(this, args);
1301
1339
  }
1302
1340
  // Returns the result
@@ -1311,7 +1349,7 @@ const wrapMethod = (mode, target, key, handler) => {
1311
1349
  * making it easier to reference `this` within the method.
1312
1350
  */
1313
1351
  function Bind() {
1314
- return function (target, key, descriptor) {
1352
+ return (_target, key, descriptor) => {
1315
1353
  const original = descriptor.value;
1316
1354
  return {
1317
1355
  configurable: true,
@@ -1328,8 +1366,9 @@ function Bind() {
1328
1366
  };
1329
1367
  }
1330
1368
 
1369
+ // biome-ignore-all lint: TODO
1331
1370
  function Provider(namespace) {
1332
- return function (target, key) {
1371
+ return (target, key) => {
1333
1372
  const symbol = Symbol();
1334
1373
  const [MAIN, SUB] = namespace.split('.');
1335
1374
  const prefix = `${KEY}:${MAIN}`;
@@ -1376,12 +1415,14 @@ function Provider(namespace) {
1376
1415
  cleanups(this).set(type, cleanup);
1377
1416
  });
1378
1417
  wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
1379
- cleanups(this).forEach((cleanup) => cleanup());
1418
+ cleanups(this).forEach((cleanup) => {
1419
+ cleanup();
1420
+ });
1380
1421
  });
1381
1422
  };
1382
1423
  }
1383
1424
  function Consumer(namespace) {
1384
- return function (target, key) {
1425
+ return (target, key) => {
1385
1426
  const symbol = Symbol();
1386
1427
  const [MAIN, SUB] = namespace.split('.');
1387
1428
  const prefix = `${KEY}:${MAIN}`;
@@ -1397,7 +1438,7 @@ function Consumer(namespace) {
1397
1438
  if (SUB && this[SUB])
1398
1439
  return;
1399
1440
  // TODO
1400
- let connected;
1441
+ let connected = false;
1401
1442
  const options = {
1402
1443
  bubbles: true
1403
1444
  };
@@ -1443,7 +1484,9 @@ function Consumer(namespace) {
1443
1484
  dispatch(window, `${type}:presence`);
1444
1485
  });
1445
1486
  wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
1446
- cleanups(this).forEach((cleanup) => cleanup());
1487
+ cleanups(this).forEach((cleanup) => {
1488
+ cleanup();
1489
+ });
1447
1490
  });
1448
1491
  };
1449
1492
  }
@@ -1456,7 +1499,7 @@ function Consumer(namespace) {
1456
1499
  * @param {number} delay - The debounce delay in milliseconds.
1457
1500
  */
1458
1501
  function Debounce(delay = 0) {
1459
- return function (target, key, descriptor) {
1502
+ return (target, key, descriptor) => {
1460
1503
  const KEY = Symbol();
1461
1504
  const original = descriptor.value;
1462
1505
  function clear() {
@@ -1491,27 +1534,36 @@ function Direction() {
1491
1534
  * and its name, in kebab-case, serves as the element name.
1492
1535
  */
1493
1536
  function Element() {
1494
- return function (constructor) {
1537
+ // biome-ignore lint: TODO
1538
+ return (constructor) => {
1495
1539
  if (isServer())
1496
1540
  return;
1497
1541
  const tag = getTag(constructor);
1542
+ if (!tag)
1543
+ return;
1498
1544
  if (customElements.get(tag))
1499
1545
  return;
1500
1546
  customElements.define(tag, proxy(constructor));
1501
1547
  };
1502
1548
  }
1549
+ // biome-ignore lint: TODO
1503
1550
  const proxy = (constructor) => {
1504
1551
  return class Plus extends HTMLElement {
1505
1552
  #instance;
1553
+ // biome-ignore lint: TODO
1506
1554
  static formAssociated = constructor['formAssociated'];
1555
+ // biome-ignore lint: TODO
1507
1556
  static observedAttributes = constructor['observedAttributes'];
1508
1557
  constructor() {
1509
1558
  super();
1510
1559
  this.attachShadow({
1511
1560
  mode: 'open',
1561
+ // biome-ignore lint: TODO
1512
1562
  delegatesFocus: constructor['delegatesFocus'],
1563
+ // biome-ignore lint: TODO
1513
1564
  slotAssignment: constructor['slotAssignment']
1514
1565
  });
1566
+ // biome-ignore lint: TODO
1515
1567
  this.#instance = new constructor();
1516
1568
  this.#instance[API_HOST] = () => this;
1517
1569
  call(this.#instance, LIFECYCLE_CONSTRUCTED);
@@ -1520,13 +1572,37 @@ const proxy = (constructor) => {
1520
1572
  call(this.#instance, LIFECYCLE_ADOPTED);
1521
1573
  }
1522
1574
  attributeChangedCallback(key, prev, next) {
1523
- if (prev != next) {
1524
- this.#instance['RAW:' + key] = next;
1575
+ if (prev !== next) {
1576
+ this.#instance[`RAW:${key}`] = next;
1525
1577
  }
1526
1578
  }
1527
1579
  connectedCallback() {
1528
1580
  // TODO: experimental for global config
1529
- Object.assign(this.#instance, getConfig('element', getTag(this.#instance), 'property'));
1581
+ (() => {
1582
+ const namespace = getNamespace(this.#instance) || '';
1583
+ const tag = getTag(this.#instance) || '';
1584
+ const properties = getConfig(namespace).elements?.[tag]?.properties;
1585
+ if (!properties)
1586
+ return;
1587
+ const defaults = Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, value?.default]));
1588
+ Object.assign(this, defaults);
1589
+ })();
1590
+ // TODO
1591
+ (() => {
1592
+ const key = Object.keys(this).find((key) => key.startsWith('__reactProps'));
1593
+ const props = this[key];
1594
+ if (!props)
1595
+ return;
1596
+ for (const [key, value] of Object.entries(props)) {
1597
+ if (this[key] !== undefined)
1598
+ continue;
1599
+ if (key === 'children')
1600
+ continue;
1601
+ if (typeof value !== 'object')
1602
+ continue;
1603
+ this[key] = value;
1604
+ }
1605
+ })();
1530
1606
  this.#instance[API_CONNECTED] = true;
1531
1607
  call(this.#instance, LIFECYCLE_CONNECTED);
1532
1608
  requestUpdate(this.#instance, undefined, undefined, () => {
@@ -1546,7 +1622,7 @@ const proxy = (constructor) => {
1546
1622
  * @param options An object that configures options for the event dispatcher.
1547
1623
  */
1548
1624
  function Event(options = {}) {
1549
- return function (target, key) {
1625
+ return (target, key) => {
1550
1626
  target[key] = function (detail) {
1551
1627
  const element = host(this);
1552
1628
  const framework = getFramework(this);
@@ -1558,6 +1634,7 @@ function Event(options = {}) {
1558
1634
  options.bubbles = true;
1559
1635
  type = pascalCase(type);
1560
1636
  try {
1637
+ // biome-ignore lint: TODO
1561
1638
  window['Blazor'].registerCustomEventType(type, {
1562
1639
  createEventArgs: (event) => ({
1563
1640
  detail: event.detail
@@ -1579,7 +1656,8 @@ function Event(options = {}) {
1579
1656
  break;
1580
1657
  }
1581
1658
  let event;
1582
- event ||= getConfig('event', 'resolver')?.({ detail, element, framework, options, type });
1659
+ const resolver = getConfig(getNamespace(target) || '').event?.resolver;
1660
+ event ||= resolver?.({ detail, element, framework, options, type });
1583
1661
  event && element.dispatchEvent(event);
1584
1662
  event ||= dispatch(this, type, { ...options, detail });
1585
1663
  return event;
@@ -1611,7 +1689,7 @@ function IsRTL() {
1611
1689
  * for the event listener.
1612
1690
  */
1613
1691
  function Listen(type, options) {
1614
- return function (target, key, descriptor) {
1692
+ return (target, key, descriptor) => {
1615
1693
  const element = (instance) => {
1616
1694
  switch (options?.target) {
1617
1695
  case 'body':
@@ -1641,19 +1719,183 @@ function Listen(type, options) {
1641
1719
  * and invoke it as needed, both internally and externally.
1642
1720
  */
1643
1721
  function Method() {
1644
- return function (target, key, descriptor) {
1722
+ return (target, key, _descriptor) => {
1645
1723
  wrapMethod('before', target, LIFECYCLE_CONSTRUCTED, function () {
1646
1724
  host(this)[key] = this[key].bind(this);
1647
1725
  });
1648
1726
  };
1649
1727
  }
1650
1728
 
1729
+ const CONTAINER_DATA = Symbol();
1730
+ const getContainers = (breakpoints) => {
1731
+ return Object.entries(breakpoints || {}).reduce((result, [key, breakpoint]) => {
1732
+ if (breakpoint.type !== 'container')
1733
+ return result;
1734
+ result[key] = {
1735
+ min: breakpoint.min,
1736
+ max: breakpoint.max
1737
+ };
1738
+ return result;
1739
+ }, {});
1740
+ };
1741
+ const getMedias = (breakpoints) => {
1742
+ return Object.entries(breakpoints || {}).reduce((result, [key, breakpoint]) => {
1743
+ if (breakpoint.type !== 'media')
1744
+ return result;
1745
+ const parts = [];
1746
+ const min = 'min' in breakpoint ? breakpoint.min : undefined;
1747
+ const max = 'max' in breakpoint ? breakpoint.max : undefined;
1748
+ if (min !== undefined)
1749
+ parts.push(`(min-width: ${min}px)`);
1750
+ if (max !== undefined)
1751
+ parts.push(`(max-width: ${max}px)`);
1752
+ const query = parts.join(' and ');
1753
+ if (query)
1754
+ result[key] = query;
1755
+ return result;
1756
+ }, {});
1757
+ };
1758
+ const matchContainer = (element, container) => {
1759
+ const getData = () => {
1760
+ if (element[CONTAINER_DATA])
1761
+ return element[CONTAINER_DATA];
1762
+ const listeners = new Set();
1763
+ const observer = new ResizeObserver(() => {
1764
+ listeners.forEach((listener) => {
1765
+ listener();
1766
+ });
1767
+ });
1768
+ observer.observe(element);
1769
+ element[CONTAINER_DATA] = { listeners, observer };
1770
+ return element[CONTAINER_DATA];
1771
+ };
1772
+ const getMatches = () => {
1773
+ const width = element.offsetWidth;
1774
+ const matches = (container.min === undefined || width >= container.min) &&
1775
+ (container.max === undefined || width <= container.max);
1776
+ return matches;
1777
+ };
1778
+ const addEventListener = (_type, listener) => {
1779
+ getData().listeners.add(listener);
1780
+ };
1781
+ const removeEventListener = (_type, listener) => {
1782
+ const data = getData();
1783
+ data.listeners.delete(listener);
1784
+ if (data.listeners.size !== 0)
1785
+ return;
1786
+ data.observer.disconnect();
1787
+ delete element[CONTAINER_DATA];
1788
+ };
1789
+ return {
1790
+ get matches() {
1791
+ return getMatches();
1792
+ },
1793
+ addEventListener,
1794
+ removeEventListener
1795
+ };
1796
+ };
1797
+ function Overrides() {
1798
+ return (target, key) => {
1799
+ const DISPOSERS = Symbol();
1800
+ const breakpoints = getConfig(getNamespace(target) || '').breakpoints || {};
1801
+ const containers = getContainers(breakpoints);
1802
+ const medias = getMedias(breakpoints);
1803
+ wrapMethod('after', target, LIFECYCLE_UPDATE, function (states) {
1804
+ if (!states.has(key))
1805
+ return;
1806
+ this[DISPOSERS] ??= new Map();
1807
+ const disposers = this[DISPOSERS];
1808
+ const overrides = this[key] || {};
1809
+ const activeKeys = new Set(disposers.keys());
1810
+ const overrideKeys = Object.keys(overrides);
1811
+ const containerKeys = overrideKeys.filter((breakpoint) => breakpoint in containers);
1812
+ const mediaKeys = overrideKeys.filter((breakpoint) => breakpoint in medias);
1813
+ let timeout = -1;
1814
+ let next = {};
1815
+ const apply = (key) => {
1816
+ clearTimeout(timeout);
1817
+ Object.assign(next, overrides[key]);
1818
+ timeout = window.setTimeout(() => {
1819
+ Object.assign(host(this), overrides[key]);
1820
+ next = {};
1821
+ }, 0);
1822
+ };
1823
+ for (const overrideKey of overrideKeys) {
1824
+ if (activeKeys.delete(overrideKey))
1825
+ continue;
1826
+ const breakpoint = breakpoints[overrideKey];
1827
+ if (!breakpoint)
1828
+ continue;
1829
+ switch (breakpoint.type) {
1830
+ case 'container': {
1831
+ const container = containers[overrideKey];
1832
+ if (!container)
1833
+ break;
1834
+ const containerQueryList = matchContainer(host(this), container);
1835
+ const change = () => {
1836
+ for (const containerKey of containerKeys) {
1837
+ if (matchContainer(host(this), containers[containerKey]).matches) {
1838
+ apply(containerKey);
1839
+ }
1840
+ }
1841
+ };
1842
+ containerQueryList.addEventListener('change', change);
1843
+ const disposer = () => {
1844
+ containerQueryList.removeEventListener('change', change);
1845
+ };
1846
+ disposers.set(overrideKey, disposer);
1847
+ if (!containerQueryList.matches)
1848
+ break;
1849
+ change();
1850
+ break;
1851
+ }
1852
+ case 'media': {
1853
+ const media = medias[overrideKey];
1854
+ if (!media)
1855
+ break;
1856
+ const mediaQueryList = window.matchMedia(media);
1857
+ const change = () => {
1858
+ for (const mediaKey of mediaKeys) {
1859
+ if (window.matchMedia(medias[mediaKey]).matches) {
1860
+ apply(mediaKey);
1861
+ }
1862
+ }
1863
+ };
1864
+ mediaQueryList.addEventListener('change', change);
1865
+ const disposer = () => {
1866
+ mediaQueryList.removeEventListener('change', change);
1867
+ };
1868
+ disposers.set(overrideKey, disposer);
1869
+ if (!mediaQueryList.matches)
1870
+ break;
1871
+ change();
1872
+ break;
1873
+ }
1874
+ }
1875
+ }
1876
+ for (const activeKey of activeKeys) {
1877
+ const disposer = disposers.get(activeKey);
1878
+ disposer?.();
1879
+ disposers.delete(activeKey);
1880
+ }
1881
+ });
1882
+ wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
1883
+ this[DISPOSERS] ??= new Map();
1884
+ const disposers = this[DISPOSERS];
1885
+ disposers.forEach((disposer) => {
1886
+ disposer();
1887
+ });
1888
+ disposers.clear();
1889
+ });
1890
+ };
1891
+ }
1892
+
1651
1893
  /**
1652
1894
  * Creates a reactive property, reflecting a corresponding attribute value,
1653
1895
  * and updates the element when the property is set.
1654
1896
  */
1655
1897
  function Property(options) {
1656
- return function (target, key, descriptor) {
1898
+ return (target, key, descriptor) => {
1657
1899
  // Unique symbol for property storage to avoid naming conflicts
1658
1900
  const KEY = Symbol();
1659
1901
  // Unique symbol for the lock flag to prevent infinite loops during updates
@@ -1662,8 +1904,12 @@ function Property(options) {
1662
1904
  const attribute = options?.attribute || kebabCase(key);
1663
1905
  // Store the original setter (if it exists) to preserve its behavior
1664
1906
  const originalSetter = descriptor?.set;
1907
+ // Ensure the element class has an observedAttributes array
1908
+ // biome-ignore lint: TODO
1909
+ target.constructor['observedAttributes'] ||= [];
1665
1910
  // Register the attribute in the observedAttributes array for the element
1666
- (target.constructor['observedAttributes'] ||= []).push(attribute);
1911
+ // biome-ignore lint: TODO
1912
+ target.constructor['observedAttributes'].push(attribute);
1667
1913
  // Getter function to retrieve the property value
1668
1914
  function get() {
1669
1915
  return this[KEY];
@@ -1713,7 +1959,7 @@ function Property(options) {
1713
1959
  * Define a raw property setter to handle updates that trigger from the `attributeChangedCallback`,
1714
1960
  * To intercept and process raw attribute values before they are assigned to the property
1715
1961
  */
1716
- defineProperty(target, 'RAW:' + attribute, {
1962
+ defineProperty(target, `RAW:${attribute}`, {
1717
1963
  set(value) {
1718
1964
  if (!this[LOCKED]) {
1719
1965
  // Convert the raw value and set it to the corresponding property
@@ -1800,7 +2046,7 @@ function Slots() {
1800
2046
  * element to re-render upon the desired property changes.
1801
2047
  */
1802
2048
  function State() {
1803
- return function (target, key) {
2049
+ return (target, key) => {
1804
2050
  const KEY = Symbol();
1805
2051
  const name = String(key);
1806
2052
  defineProperty(target, key, {
@@ -1822,7 +2068,7 @@ function State() {
1822
2068
 
1823
2069
  // TODO: check the logic
1824
2070
  function Style() {
1825
- return function (target, key) {
2071
+ return (target, key) => {
1826
2072
  const LAST = Symbol();
1827
2073
  const SHEET = Symbol();
1828
2074
  wrapMethod('before', target, LIFECYCLE_UPDATED, function () {
@@ -1843,6 +2089,7 @@ function Style() {
1843
2089
  value = value.call(this);
1844
2090
  }
1845
2091
  if (value instanceof Promise) {
2092
+ // biome-ignore lint: TODO
1846
2093
  value.then(update((this[LAST] = value))).catch((error) => {
1847
2094
  throw new Error('TODO', { cause: error });
1848
2095
  });
@@ -1854,7 +2101,7 @@ function Style() {
1854
2101
  };
1855
2102
  }
1856
2103
  const toCssString = (input, parent) => {
1857
- if (typeof input == 'string') {
2104
+ if (typeof input === 'string') {
1858
2105
  return input.trim();
1859
2106
  }
1860
2107
  if (Array.isArray(input)) {
@@ -1863,7 +2110,9 @@ const toCssString = (input, parent) => {
1863
2110
  .filter(Boolean)
1864
2111
  .join('\n');
1865
2112
  }
1866
- if (typeof input != 'object')
2113
+ if (input === null)
2114
+ return '';
2115
+ if (typeof input !== 'object')
1867
2116
  return '';
1868
2117
  let result = '';
1869
2118
  for (const key of Object.keys(input)) {
@@ -1892,7 +2141,7 @@ const toCssString = (input, parent) => {
1892
2141
  * @param immediate Triggers the callback immediately after initialization.
1893
2142
  */
1894
2143
  function Watch(keys, immediate) {
1895
- return function (target, key) {
2144
+ return (target, key) => {
1896
2145
  // Gets all keys
1897
2146
  const all = [keys].flat().filter((item) => item);
1898
2147
  // Registers a lifecycle to detect changes.
@@ -1916,4 +2165,4 @@ const attributes = attributes$2;
1916
2165
  const html = html$1;
1917
2166
  const styles = styles$1;
1918
2167
 
1919
- export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Property, Provider, Query, QueryAll, Slots, State, Style, Watch, attributes as a, classes, direction, dispatch, getConfig, html as h, host, isCSSColor, isRTL, off, on, query, queryAll, styles as s, setConfig, slots, toCSSColor, toCSSUnit, toUnit };
2168
+ export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Overrides, Property, Provider, Query, QueryAll, Slots, State, Style, Watch, attributes as a, classes, direction, dispatch, getConfig, getConfigCreator, html as h, host, isCSSColor, isRTL, off, on, query, queryAll, styles as s, setConfig, setConfigCreator, slots, toCSSColor, toCSSUnit, toUnit };