@htmlplus/element 3.4.1 → 3.4.3

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
@@ -86,7 +86,7 @@ const updateAttribute = (target, key, value) => {
86
86
  };
87
87
 
88
88
  const symbol = Symbol();
89
- const attributes$2 = (target, attributes) => {
89
+ const attributes$1 = (target, attributes) => {
90
90
  const element = host(target);
91
91
  const prev = element[symbol] || {};
92
92
  const next = Object.assign({}, ...attributes);
@@ -342,31 +342,25 @@ const task = (options) => {
342
342
  };
343
343
 
344
344
  // biome-ignore-all lint: TODO
345
-
346
345
  class MapSet extends Map {
347
- set(key, value) {
348
- super.set(key, value);
349
- return value;
350
- }
346
+ set(key, value) {
347
+ super.set(key, value);
348
+ return value;
349
+ }
351
350
  }
352
-
353
351
  class WeakMapSet extends WeakMap {
354
- set(key, value) {
355
- super.set(key, value);
356
- return value;
357
- }
352
+ set(key, value) {
353
+ super.set(key, value);
354
+ return value;
355
+ }
358
356
  }
359
-
360
357
  /*! (c) Andrea Giammarchi - ISC */
361
- const empty =
362
- /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
358
+ const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
363
359
  const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
364
- const attributes$1 = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
360
+ const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
365
361
  const holes = /[\x01\x02]/g;
366
-
367
362
  // \x01 Node.ELEMENT_NODE
368
363
  // \x02 Node.ATTRIBUTE_NODE
369
-
370
364
  /**
371
365
  * Given a template, find holes as both nodes and attributes and
372
366
  * return a string with holes as either comment nodes or named attributes.
@@ -376,157 +370,154 @@ const holes = /[\x01\x02]/g;
376
370
  * @returns {string} X/HTML with prefixed comments or attributes
377
371
  */
378
372
  var instrument = (template, prefix, svg) => {
379
- let i = 0;
380
- return template
381
- .join('\x01')
382
- .trim()
383
- .replace(elements, (_, name, attrs, selfClosing) => {
384
- let ml = name + attrs.replace(attributes$1, '\x02=$2$1').trimEnd();
385
- if (selfClosing.length) ml += svg || empty.test(name) ? ' /' : '></' + name;
386
- return '<' + ml + '>';
387
- })
388
- .replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
373
+ let i = 0;
374
+ return template
375
+ .join('\x01')
376
+ .trim()
377
+ .replace(elements, (_, name, attrs, selfClosing) => {
378
+ let ml = name + attrs.replace(attributes, '\x02=$2$1').trimEnd();
379
+ if (selfClosing.length)
380
+ ml += svg || empty.test(name) ? ' /' : '></' + name;
381
+ return '<' + ml + '>';
382
+ })
383
+ .replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
389
384
  };
390
-
391
385
  const ELEMENT_NODE = 1;
392
386
  const nodeType = 111;
393
-
394
387
  const remove = ({ firstChild, lastChild }) => {
395
- const range = document.createRange();
396
- range.setStartAfter(firstChild);
397
- range.setEndAfter(lastChild);
398
- range.deleteContents();
399
- return firstChild;
388
+ const range = document.createRange();
389
+ range.setStartAfter(firstChild);
390
+ range.setEndAfter(lastChild);
391
+ range.deleteContents();
392
+ return firstChild;
400
393
  };
401
-
402
- const diffable = (node, operation) =>
403
- node.nodeType === nodeType
404
- ? 1 / operation < 0
405
- ? operation
406
- ? remove(node)
407
- : node.lastChild
408
- : operation
409
- ? node.valueOf()
410
- : node.firstChild
411
- : node;
394
+ const diffable = (node, operation) => node.nodeType === nodeType
395
+ ? 1 / operation < 0
396
+ ? operation
397
+ ? remove(node)
398
+ : node.lastChild
399
+ : operation
400
+ ? node.valueOf()
401
+ : node.firstChild
402
+ : node;
412
403
  const persistent = (fragment) => {
413
- const { firstChild, lastChild } = fragment;
414
- if (firstChild === lastChild) return lastChild || fragment;
415
- const { childNodes } = fragment;
416
- const nodes = [...childNodes];
417
- return {
418
- ELEMENT_NODE,
419
- nodeType,
420
- firstChild,
421
- lastChild,
422
- valueOf() {
423
- if (childNodes.length !== nodes.length) fragment.append(...nodes);
424
- return fragment;
425
- }
426
- };
404
+ const { firstChild, lastChild } = fragment;
405
+ if (firstChild === lastChild)
406
+ return lastChild || fragment;
407
+ const { childNodes } = fragment;
408
+ const nodes = [...childNodes];
409
+ return {
410
+ ELEMENT_NODE,
411
+ nodeType,
412
+ firstChild,
413
+ lastChild,
414
+ valueOf() {
415
+ if (childNodes.length !== nodes.length)
416
+ fragment.append(...nodes);
417
+ return fragment;
418
+ }
419
+ };
427
420
  };
428
-
429
421
  const { isArray: isArray$1 } = Array;
430
-
431
422
  const aria = (node) => (values) => {
432
- for (const key in values) {
433
- const name = key === 'role' ? key : `aria-${key}`;
434
- const value = values[key];
435
- if (value == null) node.removeAttribute(name);
436
- else node.setAttribute(name, value);
437
- }
423
+ for (const key in values) {
424
+ const name = key === 'role' ? key : `aria-${key}`;
425
+ const value = values[key];
426
+ if (value == null)
427
+ node.removeAttribute(name);
428
+ else
429
+ node.setAttribute(name, value);
430
+ }
438
431
  };
439
-
440
432
  const attribute = (node, name) => {
441
- let oldValue,
442
- orphan = true;
443
- const attributeNode = document.createAttributeNS(null, name);
444
- return (newValue) => {
445
- if (oldValue !== newValue) {
446
- oldValue = newValue;
447
- if (oldValue == null) {
448
- if (!orphan) {
449
- node.removeAttributeNode(attributeNode);
450
- orphan = true;
451
- }
452
- } else {
453
- const value = newValue;
454
- if (value == null) {
455
- if (!orphan) node.removeAttributeNode(attributeNode);
456
- orphan = true;
457
- } else {
458
- attributeNode.value = value;
459
- if (orphan) {
460
- node.setAttributeNodeNS(attributeNode);
461
- orphan = false;
462
- }
463
- }
464
- }
465
- }
466
- };
433
+ let oldValue, orphan = true;
434
+ const attributeNode = document.createAttributeNS(null, name);
435
+ return (newValue) => {
436
+ if (oldValue !== newValue) {
437
+ oldValue = newValue;
438
+ if (oldValue == null) {
439
+ if (!orphan) {
440
+ node.removeAttributeNode(attributeNode);
441
+ orphan = true;
442
+ }
443
+ }
444
+ else {
445
+ const value = newValue;
446
+ if (value == null) {
447
+ if (!orphan)
448
+ node.removeAttributeNode(attributeNode);
449
+ orphan = true;
450
+ }
451
+ else {
452
+ attributeNode.value = value;
453
+ if (orphan) {
454
+ node.setAttributeNodeNS(attributeNode);
455
+ orphan = false;
456
+ }
457
+ }
458
+ }
459
+ }
460
+ };
467
461
  };
468
-
469
462
  const boolean = (node, key, oldValue) => (newValue) => {
470
- if (oldValue !== !!newValue) {
471
- // when IE won't be around anymore ...
472
- // node.toggleAttribute(key, oldValue = !!newValue);
473
- if ((oldValue = !!newValue)) node.setAttribute(key, '');
474
- else node.removeAttribute(key);
475
- }
463
+ if (oldValue !== !!newValue) {
464
+ // when IE won't be around anymore ...
465
+ // node.toggleAttribute(key, oldValue = !!newValue);
466
+ if ((oldValue = !!newValue))
467
+ node.setAttribute(key, '');
468
+ else
469
+ node.removeAttribute(key);
470
+ }
471
+ };
472
+ const data = ({ dataset }) => (values) => {
473
+ for (const key in values) {
474
+ const value = values[key];
475
+ if (value == null)
476
+ delete dataset[key];
477
+ else
478
+ dataset[key] = value;
479
+ }
476
480
  };
477
-
478
- const data =
479
- ({ dataset }) =>
480
- (values) => {
481
- for (const key in values) {
482
- const value = values[key];
483
- if (value == null) delete dataset[key];
484
- else dataset[key] = value;
485
- }
486
- };
487
-
488
481
  const event = (node, name) => {
489
- let oldValue,
490
- lower,
491
- type = name.slice(2);
492
- if (!(name in node) && (lower = name.toLowerCase()) in node) type = lower.slice(2);
493
- return (newValue) => {
494
- const info = isArray$1(newValue) ? newValue : [newValue, false];
495
- if (oldValue !== info[0]) {
496
- if (oldValue) node.removeEventListener(type, oldValue, info[1]);
497
- if ((oldValue = info[0])) node.addEventListener(type, oldValue, info[1]);
498
- }
499
- };
482
+ let oldValue, lower, type = name.slice(2);
483
+ if (!(name in node) && (lower = name.toLowerCase()) in node)
484
+ type = lower.slice(2);
485
+ return (newValue) => {
486
+ const info = isArray$1(newValue) ? newValue : [newValue, false];
487
+ if (oldValue !== info[0]) {
488
+ if (oldValue)
489
+ node.removeEventListener(type, oldValue, info[1]);
490
+ if ((oldValue = info[0]))
491
+ node.addEventListener(type, oldValue, info[1]);
492
+ }
493
+ };
500
494
  };
501
-
502
495
  const ref = (node) => {
503
- let oldValue;
504
- return (value) => {
505
- if (oldValue !== value) {
506
- oldValue = value;
507
- if (typeof value === 'function') value(node);
508
- else value.current = node;
509
- }
510
- };
496
+ let oldValue;
497
+ return (value) => {
498
+ if (oldValue !== value) {
499
+ oldValue = value;
500
+ if (typeof value === 'function')
501
+ value(node);
502
+ else
503
+ value.current = node;
504
+ }
505
+ };
511
506
  };
512
-
513
- const setter = (node, key) =>
514
- key === 'dataset'
515
- ? data(node)
516
- : (value) => {
517
- node[key] = value;
518
- };
519
-
507
+ const setter = (node, key) => key === 'dataset'
508
+ ? data(node)
509
+ : (value) => {
510
+ node[key] = value;
511
+ };
520
512
  const text = (node) => {
521
- let oldValue;
522
- return (newValue) => {
523
- if (oldValue != newValue) {
524
- oldValue = newValue;
525
- node.textContent = newValue == null ? '' : newValue;
526
- }
527
- };
513
+ let oldValue;
514
+ return (newValue) => {
515
+ if (oldValue != newValue) {
516
+ oldValue = newValue;
517
+ node.textContent = newValue == null ? '' : newValue;
518
+ }
519
+ };
528
520
  };
529
-
530
521
  /**
531
522
  * ISC License
532
523
  *
@@ -544,7 +535,6 @@ const text = (node) => {
544
535
  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
545
536
  * PERFORMANCE OF THIS SOFTWARE.
546
537
  */
547
-
548
538
  /**
549
539
  * @param {Node} parentNode The container where children live
550
540
  * @param {Node[]} a The list of current/live children
@@ -555,244 +545,227 @@ const text = (node) => {
555
545
  * @returns {Node[]} The same list of future children.
556
546
  */
557
547
  var udomdiff = (parentNode, a, b, get, before) => {
558
- const bLength = b.length;
559
- let aEnd = a.length;
560
- let bEnd = bLength;
561
- let aStart = 0;
562
- let bStart = 0;
563
- let map = null;
564
- while (aStart < aEnd || bStart < bEnd) {
565
- // append head, tail, or nodes in between: fast path
566
- if (aEnd === aStart) {
567
- // we could be in a situation where the rest of nodes that
568
- // need to be added are not at the end, and in such case
569
- // the node to `insertBefore`, if the index is more than 0
570
- // must be retrieved, otherwise it's gonna be the first item.
571
- const node =
572
- bEnd < bLength
573
- ? bStart
574
- ? get(b[bStart - 1], -0).nextSibling
575
- : get(b[bEnd - bStart], 0)
576
- : before;
577
- while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node);
578
- }
579
- // remove head or tail: fast path
580
- else if (bEnd === bStart) {
581
- while (aStart < aEnd) {
582
- // remove the node only if it's unknown or not live
583
- if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1));
584
- aStart++;
585
- }
586
- }
587
- // same node: fast path
588
- else if (a[aStart] === b[bStart]) {
589
- aStart++;
590
- bStart++;
591
- }
592
- // same tail: fast path
593
- else if (a[aEnd - 1] === b[bEnd - 1]) {
594
- aEnd--;
595
- bEnd--;
596
- }
597
- // The once here single last swap "fast path" has been removed in v1.1.0
598
- // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
599
- // reverse swap: also fast path
600
- else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
601
- // this is a "shrink" operation that could happen in these cases:
602
- // [1, 2, 3, 4, 5]
603
- // [1, 4, 3, 2, 5]
604
- // or asymmetric too
605
- // [1, 2, 3, 4, 5]
606
- // [1, 2, 3, 5, 6, 4]
607
- const node = get(a[--aEnd], -1).nextSibling;
608
- parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
609
- parentNode.insertBefore(get(b[--bEnd], 1), node);
610
- // mark the future index as identical (yeah, it's dirty, but cheap 👍)
611
- // The main reason to do this, is that when a[aEnd] will be reached,
612
- // the loop will likely be on the fast path, as identical to b[bEnd].
613
- // In the best case scenario, the next loop will skip the tail,
614
- // but in the worst one, this node will be considered as already
615
- // processed, bailing out pretty quickly from the map index check
616
- a[aEnd] = b[bEnd];
617
- }
618
- // map based fallback, "slow" path
619
- else {
620
- // the map requires an O(bEnd - bStart) operation once
621
- // to store all future nodes indexes for later purposes.
622
- // In the worst case scenario, this is a full O(N) cost,
623
- // and such scenario happens at least when all nodes are different,
624
- // but also if both first and last items of the lists are different
625
- if (!map) {
626
- map = new Map();
627
- let i = bStart;
628
- while (i < bEnd) map.set(b[i], i++);
629
- }
630
- // if it's a future node, hence it needs some handling
631
- if (map.has(a[aStart])) {
632
- // grab the index of such node, 'cause it might have been processed
633
- const index = map.get(a[aStart]);
634
- // if it's not already processed, look on demand for the next LCS
635
- if (bStart < index && index < bEnd) {
636
- let i = aStart;
637
- // counts the amount of nodes that are the same in the future
638
- let sequence = 1;
639
- while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence) sequence++;
640
- // effort decision here: if the sequence is longer than replaces
641
- // needed to reach such sequence, which would brings again this loop
642
- // to the fast path, prepend the difference before a sequence,
643
- // and move only the future list index forward, so that aStart
644
- // and bStart will be aligned again, hence on the fast path.
645
- // An example considering aStart and bStart are both 0:
646
- // a: [1, 2, 3, 4]
647
- // b: [7, 1, 2, 3, 6]
648
- // this would place 7 before 1 and, from that time on, 1, 2, and 3
649
- // will be processed at zero cost
650
- if (sequence > index - bStart) {
651
- const node = get(a[aStart], 0);
652
- while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), node);
653
- }
654
- // if the effort wasn't good enough, fallback to a replace,
655
- // moving both source and target indexes forward, hoping that some
656
- // similar node will be found later on, to go back to the fast path
657
- else {
658
- parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
659
- }
660
- }
661
- // otherwise move the source forward, 'cause there's nothing to do
662
- else aStart++;
663
- }
664
- // this node has no meaning in the future list, so it's more than safe
665
- // to remove it, and check the next live node out instead, meaning
666
- // that only the live list index should be forwarded
667
- else parentNode.removeChild(get(a[aStart++], -1));
668
- }
669
- }
670
- return b;
548
+ const bLength = b.length;
549
+ let aEnd = a.length;
550
+ let bEnd = bLength;
551
+ let aStart = 0;
552
+ let bStart = 0;
553
+ let map = null;
554
+ while (aStart < aEnd || bStart < bEnd) {
555
+ // append head, tail, or nodes in between: fast path
556
+ if (aEnd === aStart) {
557
+ // we could be in a situation where the rest of nodes that
558
+ // need to be added are not at the end, and in such case
559
+ // the node to `insertBefore`, if the index is more than 0
560
+ // must be retrieved, otherwise it's gonna be the first item.
561
+ const node = bEnd < bLength
562
+ ? bStart
563
+ ? get(b[bStart - 1], -0).nextSibling
564
+ : get(b[bEnd - bStart], 0)
565
+ : before;
566
+ while (bStart < bEnd)
567
+ parentNode.insertBefore(get(b[bStart++], 1), node);
568
+ }
569
+ // remove head or tail: fast path
570
+ else if (bEnd === bStart) {
571
+ while (aStart < aEnd) {
572
+ // remove the node only if it's unknown or not live
573
+ if (!map || !map.has(a[aStart]))
574
+ parentNode.removeChild(get(a[aStart], -1));
575
+ aStart++;
576
+ }
577
+ }
578
+ // same node: fast path
579
+ else if (a[aStart] === b[bStart]) {
580
+ aStart++;
581
+ bStart++;
582
+ }
583
+ // same tail: fast path
584
+ else if (a[aEnd - 1] === b[bEnd - 1]) {
585
+ aEnd--;
586
+ bEnd--;
587
+ }
588
+ // The once here single last swap "fast path" has been removed in v1.1.0
589
+ // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
590
+ // reverse swap: also fast path
591
+ else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
592
+ // this is a "shrink" operation that could happen in these cases:
593
+ // [1, 2, 3, 4, 5]
594
+ // [1, 4, 3, 2, 5]
595
+ // or asymmetric too
596
+ // [1, 2, 3, 4, 5]
597
+ // [1, 2, 3, 5, 6, 4]
598
+ const node = get(a[--aEnd], -1).nextSibling;
599
+ parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
600
+ parentNode.insertBefore(get(b[--bEnd], 1), node);
601
+ // mark the future index as identical (yeah, it's dirty, but cheap 👍)
602
+ // The main reason to do this, is that when a[aEnd] will be reached,
603
+ // the loop will likely be on the fast path, as identical to b[bEnd].
604
+ // In the best case scenario, the next loop will skip the tail,
605
+ // but in the worst one, this node will be considered as already
606
+ // processed, bailing out pretty quickly from the map index check
607
+ a[aEnd] = b[bEnd];
608
+ }
609
+ // map based fallback, "slow" path
610
+ else {
611
+ // the map requires an O(bEnd - bStart) operation once
612
+ // to store all future nodes indexes for later purposes.
613
+ // In the worst case scenario, this is a full O(N) cost,
614
+ // and such scenario happens at least when all nodes are different,
615
+ // but also if both first and last items of the lists are different
616
+ if (!map) {
617
+ map = new Map();
618
+ let i = bStart;
619
+ while (i < bEnd)
620
+ map.set(b[i], i++);
621
+ }
622
+ // if it's a future node, hence it needs some handling
623
+ if (map.has(a[aStart])) {
624
+ // grab the index of such node, 'cause it might have been processed
625
+ const index = map.get(a[aStart]);
626
+ // if it's not already processed, look on demand for the next LCS
627
+ if (bStart < index && index < bEnd) {
628
+ let i = aStart;
629
+ // counts the amount of nodes that are the same in the future
630
+ let sequence = 1;
631
+ while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence)
632
+ sequence++;
633
+ // effort decision here: if the sequence is longer than replaces
634
+ // needed to reach such sequence, which would brings again this loop
635
+ // to the fast path, prepend the difference before a sequence,
636
+ // and move only the future list index forward, so that aStart
637
+ // and bStart will be aligned again, hence on the fast path.
638
+ // An example considering aStart and bStart are both 0:
639
+ // a: [1, 2, 3, 4]
640
+ // b: [7, 1, 2, 3, 6]
641
+ // this would place 7 before 1 and, from that time on, 1, 2, and 3
642
+ // will be processed at zero cost
643
+ if (sequence > index - bStart) {
644
+ const node = get(a[aStart], 0);
645
+ while (bStart < index)
646
+ parentNode.insertBefore(get(b[bStart++], 1), node);
647
+ }
648
+ // if the effort wasn't good enough, fallback to a replace,
649
+ // moving both source and target indexes forward, hoping that some
650
+ // similar node will be found later on, to go back to the fast path
651
+ else {
652
+ parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
653
+ }
654
+ }
655
+ // otherwise move the source forward, 'cause there's nothing to do
656
+ else
657
+ aStart++;
658
+ }
659
+ // this node has no meaning in the future list, so it's more than safe
660
+ // to remove it, and check the next live node out instead, meaning
661
+ // that only the live list index should be forwarded
662
+ else
663
+ parentNode.removeChild(get(a[aStart++], -1));
664
+ }
665
+ }
666
+ return b;
671
667
  };
672
-
673
668
  const { isArray, prototype } = Array;
674
669
  const { indexOf } = prototype;
675
-
676
- const {
677
- createDocumentFragment,
678
- createElement,
679
- createElementNS,
680
- createTextNode,
681
- createTreeWalker,
682
- importNode
683
- } = new Proxy(typeof window == 'undefined' ? {} : window.document, {
684
- get: (target, method) => (target[method] || function () {}).bind(target)
670
+ const { createDocumentFragment, createElement, createElementNS, createTextNode, createTreeWalker, importNode } = new Proxy(typeof window == 'undefined' ? {} : window.document, {
671
+ get: (target, method) => (target[method] || function () { }).bind(target)
685
672
  });
686
-
687
673
  const createHTML = (html) => {
688
- const template = createElement('template');
689
- template.innerHTML = html;
690
- return template.content;
674
+ const template = createElement('template');
675
+ template.innerHTML = html;
676
+ return template.content;
691
677
  };
692
-
693
678
  let xml;
694
679
  const createSVG = (svg) => {
695
- if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
696
- xml.innerHTML = svg;
697
- const content = createDocumentFragment();
698
- content.append(...xml.childNodes);
699
- return content;
680
+ if (!xml)
681
+ xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
682
+ xml.innerHTML = svg;
683
+ const content = createDocumentFragment();
684
+ content.append(...xml.childNodes);
685
+ return content;
700
686
  };
701
-
702
687
  const createContent = (text, svg) => (svg ? createSVG(text) : createHTML(text));
703
-
704
688
  // from a generic path, retrieves the exact targeted node
705
689
  const reducePath = ({ childNodes }, i) => childNodes[i];
706
-
707
690
  // this helper avoid code bloat around handleAnything() callback
708
- const diff = (comment, oldNodes, newNodes) =>
709
- udomdiff(
710
- comment.parentNode,
711
- // TODO: there is a possible edge case where a node has been
712
- // removed manually, or it was a keyed one, attached
713
- // to a shared reference between renders.
714
- // In this case udomdiff might fail at removing such node
715
- // as its parent won't be the expected one.
716
- // The best way to avoid this issue is to filter oldNodes
717
- // in search of those not live, or not in the current parent
718
- // anymore, but this would require both a change to uwire,
719
- // exposing a parentNode from the firstChild, as example,
720
- // but also a filter per each diff that should exclude nodes
721
- // that are not in there, penalizing performance quite a lot.
722
- // As this has been also a potential issue with domdiff,
723
- // and both lighterhtml and hyperHTML might fail with this
724
- // very specific edge case, I might as well document this possible
725
- // "diffing shenanigan" and call it a day.
726
- oldNodes,
727
- newNodes,
728
- diffable,
729
- comment
730
- );
731
-
691
+ const diff = (comment, oldNodes, newNodes) => udomdiff(comment.parentNode,
692
+ // TODO: there is a possible edge case where a node has been
693
+ // removed manually, or it was a keyed one, attached
694
+ // to a shared reference between renders.
695
+ // In this case udomdiff might fail at removing such node
696
+ // as its parent won't be the expected one.
697
+ // The best way to avoid this issue is to filter oldNodes
698
+ // in search of those not live, or not in the current parent
699
+ // anymore, but this would require both a change to uwire,
700
+ // exposing a parentNode from the firstChild, as example,
701
+ // but also a filter per each diff that should exclude nodes
702
+ // that are not in there, penalizing performance quite a lot.
703
+ // As this has been also a potential issue with domdiff,
704
+ // and both lighterhtml and hyperHTML might fail with this
705
+ // very specific edge case, I might as well document this possible
706
+ // "diffing shenanigan" and call it a day.
707
+ oldNodes, newNodes, diffable, comment);
732
708
  // if an interpolation represents a comment, the whole
733
709
  // diffing will be related to such comment.
734
710
  // This helper is in charge of understanding how the new
735
711
  // content for such interpolation/hole should be updated
736
712
  const handleAnything = (comment) => {
737
- let oldValue,
738
- text,
739
- nodes = [];
740
- const anyContent = (newValue) => {
741
- switch (typeof newValue) {
742
- // primitives are handled as text content
743
- case 'string':
744
- case 'number':
745
- case 'boolean':
746
- if (oldValue !== newValue) {
747
- oldValue = newValue;
748
- if (!text) text = createTextNode('');
749
- text.data = newValue;
750
- nodes = diff(comment, nodes, [text]);
751
- }
752
- break;
753
- // null, and undefined are used to cleanup previous content
754
- case 'object':
755
- case 'undefined':
756
- if (newValue == null) {
757
- if (oldValue != newValue) {
758
- oldValue = newValue;
759
- nodes = diff(comment, nodes, []);
760
- }
761
- break;
762
- }
763
- // arrays and nodes have a special treatment
764
- if (isArray(newValue)) {
765
- oldValue = newValue;
766
- // arrays can be used to cleanup, if empty
767
- if (newValue.length === 0) nodes = diff(comment, nodes, []);
768
- // or diffed, if these contains nodes or "wires"
769
- else if (typeof newValue[0] === 'object') nodes = diff(comment, nodes, newValue);
770
- // in all other cases the content is stringified as is
771
- else anyContent(String(newValue));
772
- break;
773
- }
774
- // if the new value is a DOM node, or a wire, and it's
775
- // different from the one already live, then it's diffed.
776
- // if the node is a fragment, it's appended once via its childNodes
777
- // There is no `else` here, meaning if the content
778
- // is not expected one, nothing happens, as easy as that.
779
- if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
780
- oldValue = newValue;
781
- nodes = diff(
782
- comment,
783
- nodes,
784
- newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]
785
- );
786
- }
787
- break;
788
- case 'function':
789
- anyContent(newValue(comment));
790
- break;
791
- }
792
- };
793
- return anyContent;
713
+ let oldValue, text, nodes = [];
714
+ const anyContent = (newValue) => {
715
+ switch (typeof newValue) {
716
+ // primitives are handled as text content
717
+ case 'string':
718
+ case 'number':
719
+ case 'boolean':
720
+ if (oldValue !== newValue) {
721
+ oldValue = newValue;
722
+ if (!text)
723
+ text = createTextNode('');
724
+ text.data = newValue;
725
+ nodes = diff(comment, nodes, [text]);
726
+ }
727
+ break;
728
+ // null, and undefined are used to cleanup previous content
729
+ case 'object':
730
+ case 'undefined':
731
+ if (newValue == null) {
732
+ if (oldValue != newValue) {
733
+ oldValue = newValue;
734
+ nodes = diff(comment, nodes, []);
735
+ }
736
+ break;
737
+ }
738
+ // arrays and nodes have a special treatment
739
+ if (isArray(newValue)) {
740
+ oldValue = newValue;
741
+ // arrays can be used to cleanup, if empty
742
+ if (newValue.length === 0)
743
+ nodes = diff(comment, nodes, []);
744
+ // or diffed, if these contains nodes or "wires"
745
+ else if (typeof newValue[0] === 'object')
746
+ nodes = diff(comment, nodes, newValue);
747
+ // in all other cases the content is stringified as is
748
+ else
749
+ 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(comment, nodes, newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]);
760
+ }
761
+ break;
762
+ case 'function':
763
+ anyContent(newValue(comment));
764
+ break;
765
+ }
766
+ };
767
+ return anyContent;
794
768
  };
795
-
796
769
  // attributes can be:
797
770
  // * ref=${...} for hooks and other purposes
798
771
  // * aria=${...} for aria attributes
@@ -804,55 +777,51 @@ const handleAnything = (comment) => {
804
777
  // * onevent=${...} to automatically handle event listeners
805
778
  // * generic=${...} to handle an attribute just like an attribute
806
779
  const handleAttribute = (node, name /*, svg*/) => {
807
- switch (name[0]) {
808
- case '?':
809
- return boolean(node, name.slice(1), false);
810
- case '.':
811
- return setter(node, name.slice(1));
812
- case '@':
813
- return event(node, 'on' + name.slice(1));
814
- case 'o':
815
- if (name[1] === 'n') return event(node, name);
816
- }
817
-
818
- switch (name) {
819
- case 'ref':
820
- return ref(node);
821
- case 'aria':
822
- return aria(node);
823
- }
824
-
825
- return attribute(node, name /*, svg*/);
780
+ switch (name[0]) {
781
+ case '?':
782
+ return boolean(node, name.slice(1), false);
783
+ case '.':
784
+ return setter(node, name.slice(1));
785
+ case '@':
786
+ return event(node, 'on' + name.slice(1));
787
+ case 'o':
788
+ if (name[1] === 'n')
789
+ return event(node, name);
790
+ }
791
+ switch (name) {
792
+ case 'ref':
793
+ return ref(node);
794
+ case 'aria':
795
+ return aria(node);
796
+ }
797
+ return attribute(node, name /*, svg*/);
826
798
  };
827
-
828
799
  // each mapped update carries the update type and its path
829
800
  // the type is either node, attribute, or text, while
830
801
  // the path is how to retrieve the related node to update.
831
802
  // In the attribute case, the attribute name is also carried along.
832
803
  function handlers(options) {
833
- const { type, path } = options;
834
- const node = path.reduceRight(reducePath, this);
835
- return type === 'node'
836
- ? handleAnything(node)
837
- : type === 'attr'
838
- ? handleAttribute(node, options.name /*, options.svg*/)
839
- : text(node);
804
+ const { type, path } = options;
805
+ const node = path.reduceRight(reducePath, this);
806
+ return type === 'node'
807
+ ? handleAnything(node)
808
+ : type === 'attr'
809
+ ? handleAttribute(node, options.name /*, options.svg*/)
810
+ : text(node);
840
811
  }
841
-
842
812
  // from a fragment container, create an array of indexes
843
813
  // related to its child nodes, so that it's possible
844
814
  // to retrieve later on exact node via reducePath
845
815
  const createPath = (node) => {
846
- const path = [];
847
- let { parentNode } = node;
848
- while (parentNode) {
849
- path.push(indexOf.call(parentNode.childNodes, node));
850
- node = parentNode;
851
- ({ parentNode } = node);
852
- }
853
- return path;
816
+ const path = [];
817
+ let { parentNode } = node;
818
+ while (parentNode) {
819
+ path.push(indexOf.call(parentNode.childNodes, node));
820
+ node = parentNode;
821
+ ({ parentNode } = node);
822
+ }
823
+ return path;
854
824
  };
855
-
856
825
  // the prefix is used to identify either comments, attributes, or nodes
857
826
  // that contain the related unique id. In the attribute cases
858
827
  // isµX="attribute-name" will be used to map current X update to that
@@ -861,162 +830,157 @@ const createPath = (node) => {
861
830
  // style and textarea will have <!--isµX--> text content, and are handled
862
831
  // directly through text-only updates.
863
832
  const prefix = 'isµ';
864
-
865
833
  // Template Literals are unique per scope and static, meaning a template
866
834
  // should be parsed once, and once only, as it will always represent the same
867
835
  // content, within the exact same amount of updates each time.
868
836
  // This cache relates each template to its unique content and updates.
869
837
  const cache$1 = new WeakMapSet();
870
-
871
838
  // a RegExp that helps checking nodes that cannot contain comments
872
839
  const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
873
-
874
840
  const createCache = () => ({
875
- stack: [], // each template gets a stack for each interpolation "hole"
876
-
877
- entry: null, // each entry contains details, such as:
878
- // * the template that is representing
879
- // * the type of node it represents (html or svg)
880
- // * the content fragment with all nodes
881
- // * the list of updates per each node (template holes)
882
- // * the "wired" node or fragment that will get updates
883
- // if the template or type are different from the previous one
884
- // the entry gets re-created each time
885
-
886
- wire: null // each rendered node represent some wired content and
887
- // this reference to the latest one. If different, the node
888
- // will be cleaned up and the new "wire" will be appended
841
+ stack: [], // each template gets a stack for each interpolation "hole"
842
+ entry: null, // each entry contains details, such as:
843
+ // * the template that is representing
844
+ // * the type of node it represents (html or svg)
845
+ // * the content fragment with all nodes
846
+ // * the list of updates per each node (template holes)
847
+ // * the "wired" node or fragment that will get updates
848
+ // if the template or type are different from the previous one
849
+ // the entry gets re-created each time
850
+ wire: null // each rendered node represent some wired content and
851
+ // this reference to the latest one. If different, the node
852
+ // will be cleaned up and the new "wire" will be appended
889
853
  });
890
-
891
854
  // the entry stored in the rendered node cache, and per each "hole"
892
855
  const createEntry = (type, template) => {
893
- const { content, updates } = mapUpdates(type, template);
894
- return { type, template, content, updates, wire: null };
856
+ const { content, updates } = mapUpdates(type, template);
857
+ return { type, template, content, updates, wire: null };
895
858
  };
896
-
897
859
  // a template is instrumented to be able to retrieve where updates are needed.
898
860
  // Each unique template becomes a fragment, cloned once per each other
899
861
  // operation based on the same template, i.e. data => html`<p>${data}</p>`
900
862
  const mapTemplate = (type, template) => {
901
- const svg = type === 'svg';
902
- const text = instrument(template, prefix, svg);
903
- const content = createContent(text, svg);
904
- // once instrumented and reproduced as fragment, it's crawled
905
- // to find out where each update is in the fragment tree
906
- const tw = createTreeWalker(content, 1 | 128);
907
- const nodes = [];
908
- const length = template.length - 1;
909
- let i = 0;
910
- // updates are searched via unique names, linearly increased across the tree
911
- // <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
912
- let search = `${prefix}${i}`;
913
- while (i < length) {
914
- const node = tw.nextNode();
915
- // if not all updates are bound but there's nothing else to crawl
916
- // it means that there is something wrong with the template.
917
- if (!node) throw `bad template: ${text}`;
918
- // if the current node is a comment, and it contains isµX
919
- // it means the update should take care of any content
920
- if (node.nodeType === 8) {
921
- // The only comments to be considered are those
922
- // which content is exactly the same as the searched one.
923
- if (node.data === search) {
924
- nodes.push({ type: 'node', path: createPath(node) });
925
- search = `${prefix}${++i}`;
926
- }
927
- } else {
928
- // if the node is not a comment, loop through all its attributes
929
- // named isµX and relate attribute updates to this node and the
930
- // attribute name, retrieved through node.getAttribute("isµX")
931
- // the isµX attribute will be removed as irrelevant for the layout
932
- // let svg = -1;
933
- while (node.hasAttribute(search)) {
934
- nodes.push({
935
- type: 'attr',
936
- path: createPath(node),
937
- name: node.getAttribute(search)
938
- });
939
- node.removeAttribute(search);
940
- search = `${prefix}${++i}`;
941
- }
942
- // if the node was a style, textarea, or others, check its content
943
- // and if it is <!--isµX--> then update tex-only this node
944
- if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
945
- node.textContent = '';
946
- nodes.push({ type: 'text', path: createPath(node) });
947
- search = `${prefix}${++i}`;
948
- }
949
- }
950
- }
951
- // once all nodes to update, or their attributes, are known, the content
952
- // will be cloned in the future to represent the template, and all updates
953
- // related to such content retrieved right away without needing to re-crawl
954
- // the exact same template, and its content, more than once.
955
- return { content, nodes };
863
+ const svg = type === 'svg';
864
+ const text = instrument(template, prefix, svg);
865
+ const content = createContent(text, svg);
866
+ // once instrumented and reproduced as fragment, it's crawled
867
+ // to find out where each update is in the fragment tree
868
+ const tw = createTreeWalker(content, 1 | 128);
869
+ const nodes = [];
870
+ const length = template.length - 1;
871
+ let i = 0;
872
+ // updates are searched via unique names, linearly increased across the tree
873
+ // <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
874
+ let search = `${prefix}${i}`;
875
+ while (i < length) {
876
+ const node = tw.nextNode();
877
+ // if not all updates are bound but there's nothing else to crawl
878
+ // it means that there is something wrong with the template.
879
+ if (!node)
880
+ throw `bad template: ${text}`;
881
+ // if the current node is a comment, and it contains isµX
882
+ // it means the update should take care of any content
883
+ if (node.nodeType === 8) {
884
+ // The only comments to be considered are those
885
+ // which content is exactly the same as the searched one.
886
+ if (node.data === search) {
887
+ nodes.push({ type: 'node', path: createPath(node) });
888
+ search = `${prefix}${++i}`;
889
+ }
890
+ }
891
+ else {
892
+ // if the node is not a comment, loop through all its attributes
893
+ // named isµX and relate attribute updates to this node and the
894
+ // attribute name, retrieved through node.getAttribute("isµX")
895
+ // the isµX attribute will be removed as irrelevant for the layout
896
+ // let svg = -1;
897
+ while (node.hasAttribute(search)) {
898
+ nodes.push({
899
+ type: 'attr',
900
+ path: createPath(node),
901
+ name: node.getAttribute(search)
902
+ });
903
+ node.removeAttribute(search);
904
+ search = `${prefix}${++i}`;
905
+ }
906
+ // if the node was a style, textarea, or others, check its content
907
+ // and if it is <!--isµX--> then update tex-only this node
908
+ if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
909
+ node.textContent = '';
910
+ nodes.push({ type: 'text', path: createPath(node) });
911
+ search = `${prefix}${++i}`;
912
+ }
913
+ }
914
+ }
915
+ // once all nodes to update, or their attributes, are known, the content
916
+ // will be cloned in the future to represent the template, and all updates
917
+ // related to such content retrieved right away without needing to re-crawl
918
+ // the exact same template, and its content, more than once.
919
+ return { content, nodes };
956
920
  };
957
-
958
921
  // if a template is unknown, perform the previous mapping, otherwise grab
959
922
  // its details such as the fragment with all nodes, and updates info.
960
923
  const mapUpdates = (type, template) => {
961
- const { content, nodes } =
962
- cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
963
- // clone deeply the fragment
964
- const fragment = importNode(content, true);
965
- // and relate an update handler per each node that needs one
966
- const updates = nodes.map(handlers, fragment);
967
- // return the fragment and all updates to use within its nodes
968
- return { content: fragment, updates };
924
+ const { content, nodes } = cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
925
+ // clone deeply the fragment
926
+ const fragment = importNode(content, true);
927
+ // and relate an update handler per each node that needs one
928
+ const updates = nodes.map(handlers, fragment);
929
+ // return the fragment and all updates to use within its nodes
930
+ return { content: fragment, updates };
969
931
  };
970
-
971
932
  // as html and svg can be nested calls, but no parent node is known
972
933
  // until rendered somewhere, the unroll operation is needed to
973
934
  // discover what to do with each interpolation, which will result
974
935
  // into an update operation.
975
936
  const unroll = (info, { type, template, values }) => {
976
- // interpolations can contain holes and arrays, so these need
977
- // to be recursively discovered
978
- const length = unrollValues(info, values);
979
- let { entry } = info;
980
- // if the cache entry is either null or different from the template
981
- // and the type this unroll should resolve, create a new entry
982
- // assigning a new content fragment and the list of updates.
983
- if (!entry || entry.template !== template || entry.type !== type)
984
- info.entry = entry = createEntry(type, template);
985
- const { content, updates, wire } = entry;
986
- // even if the fragment and its nodes is not live yet,
987
- // it is already possible to update via interpolations values.
988
- for (let i = 0; i < length; i++) updates[i](values[i]);
989
- // if the entry was new, or representing a different template or type,
990
- // create a new persistent entity to use during diffing.
991
- // This is simply a DOM node, when the template has a single container,
992
- // as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
993
- return wire || (entry.wire = persistent(content));
937
+ // interpolations can contain holes and arrays, so these need
938
+ // to be recursively discovered
939
+ const length = unrollValues(info, values);
940
+ let { entry } = info;
941
+ // if the cache entry is either null or different from the template
942
+ // and the type this unroll should resolve, create a new entry
943
+ // assigning a new content fragment and the list of updates.
944
+ if (!entry || entry.template !== template || entry.type !== type)
945
+ info.entry = entry = createEntry(type, template);
946
+ const { content, updates, wire } = entry;
947
+ // even if the fragment and its nodes is not live yet,
948
+ // it is already possible to update via interpolations values.
949
+ for (let i = 0; i < length; i++)
950
+ updates[i](values[i]);
951
+ // if the entry was new, or representing a different template or type,
952
+ // create a new persistent entity to use during diffing.
953
+ // This is simply a DOM node, when the template has a single container,
954
+ // as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
955
+ return wire || (entry.wire = persistent(content));
994
956
  };
995
-
996
957
  // the stack retains, per each interpolation value, the cache
997
958
  // related to each interpolation value, or null, if the render
998
959
  // was conditional and the value is not special (Array or Hole)
999
960
  const unrollValues = ({ stack }, values) => {
1000
- const { length } = values;
1001
- for (let i = 0; i < length; i++) {
1002
- const hole = values[i];
1003
- // each Hole gets unrolled and re-assigned as value
1004
- // so that domdiff will deal with a node/wire, not with a hole
1005
- if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
1006
- // arrays are recursively resolved so that each entry will contain
1007
- // also a DOM node or a wire, hence it can be diffed if/when needed
1008
- else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole);
1009
- // if the value is nothing special, the stack doesn't need to retain data
1010
- // this is useful also to cleanup previously retained data, if the value
1011
- // was a Hole, or an Array, but not anymore, i.e.:
1012
- // const update = content => html`<div>${content}</div>`;
1013
- // update(listOfItems); update(null); update(html`hole`)
1014
- else stack[i] = null;
1015
- }
1016
- if (length < stack.length) stack.splice(length);
1017
- return length;
961
+ const { length } = values;
962
+ for (let i = 0; i < length; i++) {
963
+ const hole = values[i];
964
+ // each Hole gets unrolled and re-assigned as value
965
+ // so that domdiff will deal with a node/wire, not with a hole
966
+ if (hole instanceof Hole)
967
+ values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
968
+ // arrays are recursively resolved so that each entry will contain
969
+ // also a DOM node or a wire, hence it can be diffed if/when needed
970
+ else if (isArray(hole))
971
+ unrollValues(stack[i] || (stack[i] = createCache()), hole);
972
+ // if the value is nothing special, the stack doesn't need to retain data
973
+ // this is useful also to cleanup previously retained data, if the value
974
+ // was a Hole, or an Array, but not anymore, i.e.:
975
+ // const update = content => html`<div>${content}</div>`;
976
+ // update(listOfItems); update(null); update(html`hole`)
977
+ else
978
+ stack[i] = null;
979
+ }
980
+ if (length < stack.length)
981
+ stack.splice(length);
982
+ return length;
1018
983
  };
1019
-
1020
984
  /**
1021
985
  * Holds all details wrappers needed to render the content further on.
1022
986
  * @constructor
@@ -1025,70 +989,60 @@ const unrollValues = ({ stack }, values) => {
1025
989
  * @param {Array} values Zero, one, or more interpolated values to render.
1026
990
  */
1027
991
  class Hole {
1028
- constructor(type, template, values) {
1029
- this.type = type;
1030
- this.template = template;
1031
- this.values = values;
1032
- }
992
+ constructor(type, template, values) {
993
+ this.type = type;
994
+ this.template = template;
995
+ this.values = values;
996
+ }
1033
997
  }
1034
-
1035
998
  // both `html` and `svg` template literal tags are polluted
1036
999
  // with a `for(ref[, id])` and a `node` tag too
1037
1000
  const tag = (type) => {
1038
- // both `html` and `svg` tags have their own cache
1039
- const keyed = new WeakMapSet();
1040
- // keyed operations always re-use the same cache and unroll
1041
- // the template and its interpolations right away
1042
- const fixed =
1043
- (cache) =>
1044
- (template, ...values) =>
1045
- unroll(cache, { type, template, values });
1046
- return Object.assign(
1047
- // non keyed operations are recognized as instance of Hole
1048
- // during the "unroll", recursively resolved and updated
1049
- (template, ...values) => new Hole(type, template, values),
1050
- {
1051
- // keyed operations need a reference object, usually the parent node
1052
- // which is showing keyed results, and optionally a unique id per each
1053
- // related node, handy with JSON results and mutable list of objects
1054
- // that usually carry a unique identifier
1055
- for(ref, id) {
1056
- const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
1057
- return memo.get(id) || memo.set(id, fixed(createCache()));
1058
- },
1059
- // it is possible to create one-off content out of the box via node tag
1060
- // this might return the single created node, or a fragment with all
1061
- // nodes present at the root level and, of course, their child nodes
1062
- node: (template, ...values) =>
1063
- unroll(createCache(), new Hole(type, template, values)).valueOf()
1064
- }
1065
- );
1001
+ // both `html` and `svg` tags have their own cache
1002
+ const keyed = new WeakMapSet();
1003
+ // keyed operations always re-use the same cache and unroll
1004
+ // the template and its interpolations right away
1005
+ const fixed = (cache) => (template, ...values) => unroll(cache, { type, template, values });
1006
+ return Object.assign(
1007
+ // non keyed operations are recognized as instance of Hole
1008
+ // during the "unroll", recursively resolved and updated
1009
+ (template, ...values) => new Hole(type, template, values), {
1010
+ // keyed operations need a reference object, usually the parent node
1011
+ // which is showing keyed results, and optionally a unique id per each
1012
+ // related node, handy with JSON results and mutable list of objects
1013
+ // that usually carry a unique identifier
1014
+ for(ref, id) {
1015
+ const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
1016
+ return memo.get(id) || memo.set(id, fixed(createCache()));
1017
+ },
1018
+ // it is possible to create one-off content out of the box via node tag
1019
+ // this might return the single created node, or a fragment with all
1020
+ // nodes present at the root level and, of course, their child nodes
1021
+ node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
1022
+ });
1066
1023
  };
1067
-
1068
1024
  // each rendered node gets its own cache
1069
1025
  const cache = new WeakMapSet();
1070
-
1071
1026
  // rendering means understanding what `html` or `svg` tags returned
1072
1027
  // and it relates a specific node to its own unique cache.
1073
1028
  // Each time the content to render changes, the node is cleaned up
1074
1029
  // and the new new content is appended, and if such content is a Hole
1075
1030
  // then it's "unrolled" to resolve all its inner nodes.
1076
1031
  const render = (where, what) => {
1077
- const hole = typeof what === 'function' ? what() : what;
1078
- const info = cache.get(where) || cache.set(where, createCache());
1079
- const wire = hole instanceof Hole ? unroll(info, hole) : hole;
1080
- if (wire !== info.wire) {
1081
- info.wire = wire;
1082
- // valueOf() simply returns the node itself, but in case it was a "wire"
1083
- // it will eventually re-append all nodes to its fragment so that such
1084
- // fragment can be re-appended many times in a meaningful way
1085
- // (wires are basically persistent fragments facades with special behavior)
1086
- where.replaceChildren(wire.valueOf());
1087
- }
1088
- return where;
1032
+ const hole = typeof what === 'function' ? what() : what;
1033
+ const info = cache.get(where) || cache.set(where, createCache());
1034
+ const wire = hole instanceof Hole ? unroll(info, hole) : hole;
1035
+ if (wire !== info.wire) {
1036
+ info.wire = wire;
1037
+ // valueOf() simply returns the node itself, but in case it was a "wire"
1038
+ // it will eventually re-append all nodes to its fragment so that such
1039
+ // fragment can be re-appended many times in a meaningful way
1040
+ // (wires are basically persistent fragments facades with special behavior)
1041
+ where.replaceChildren(wire.valueOf());
1042
+ }
1043
+ return where;
1089
1044
  };
1090
-
1091
- const html$1 = tag('html');
1045
+ const html = tag('html');
1092
1046
  tag('svg');
1093
1047
 
1094
1048
  /**
@@ -1133,30 +1087,18 @@ const requestUpdate = (target, name, previous, callback) => {
1133
1087
  const raw = target.constructor[STATIC_STYLE];
1134
1088
  if (!raw)
1135
1089
  return;
1136
- const regex1 = /this-([\w-]+)(?:-([\w-]+))?/g;
1137
- const regex2 = /(\s*\w+\s*:\s*(undefined|null)\s*;?)/g;
1138
- const regex3 = /global\s+[^{]+\{[^{}]*\{[^{}]*\}[^{}]*\}|global\s+[^{]+\{[^{}]*\}/g;
1090
+ const regex = /global\s+[^{]+\{[^{}]*\{[^{}]*\}[^{}]*\}|global\s+[^{]+\{[^{}]*\}/g;
1139
1091
  const hasGlobal = raw.includes('global');
1140
- const hasVariable = raw.includes('this-');
1141
1092
  let localSheet = target[API_STYLE];
1142
1093
  let globalSheet = target.constructor[API_STYLE];
1143
- if (!hasVariable && localSheet)
1094
+ if (localSheet)
1144
1095
  return;
1145
- const parsed = raw
1146
- .replace(regex1, (_match, key) => {
1147
- let value = target;
1148
- for (const section of key.split('-')) {
1149
- value = value?.[section];
1150
- }
1151
- return value;
1152
- })
1153
- .replace(regex2, '');
1154
1096
  if (!localSheet) {
1155
1097
  localSheet = new CSSStyleSheet();
1156
1098
  target[API_STYLE] = localSheet;
1157
1099
  shadowRoot(target)?.adoptedStyleSheets.push(localSheet);
1158
1100
  }
1159
- const localStyle = parsed.replace(regex3, '');
1101
+ const localStyle = raw.replace(regex, '');
1160
1102
  localSheet.replaceSync(localStyle);
1161
1103
  if (!hasGlobal || globalSheet)
1162
1104
  return;
@@ -1165,8 +1107,8 @@ const requestUpdate = (target, name, previous, callback) => {
1165
1107
  target.constructor[API_STYLE] = globalSheet;
1166
1108
  document.adoptedStyleSheets.push(globalSheet);
1167
1109
  }
1168
- const globalStyle = parsed
1169
- ?.match(regex3)
1110
+ const globalStyle = raw
1111
+ ?.match(regex)
1170
1112
  ?.join('')
1171
1113
  ?.replaceAll('global', '')
1172
1114
  ?.replaceAll(':host', getTag(target) || '');
@@ -1212,7 +1154,7 @@ const slots = (target) => {
1212
1154
  /**
1213
1155
  * Converts a JavaScript object containing CSS styles to a CSS string.
1214
1156
  */
1215
- const styles$1 = (input) => {
1157
+ const styles = (input) => {
1216
1158
  return Object.keys(input)
1217
1159
  .filter((key) => input[key] !== undefined && input[key] !== null)
1218
1160
  .map((key) => `${key.startsWith('--') ? '--' : ''}${kebabCase(key)}: ${input[key]}`)
@@ -2178,8 +2120,8 @@ function Watch(keys, immediate) {
2178
2120
  };
2179
2121
  }
2180
2122
 
2181
- const attributes = attributes$2;
2182
- const html = html$1;
2183
- const styles = styles$1;
2123
+ const _internal_a_ = attributes$1;
2124
+ const _internal_h_ = html;
2125
+ const _internal_s_ = styles;
2184
2126
 
2185
- export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Overrides, Property, Provider, Query, QueryAll, Slots, State, Style, Variant, Watch, attributes as a, classes, direction, dispatch, getConfig, getConfigCreator, html as h, host, isCSSColor, isCSSUnit, isRTL, off, on, query, queryAll, styles as s, setConfig, setConfigCreator, slots, toUnit };
2127
+ export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Overrides, Property, Provider, Query, QueryAll, Slots, State, Style, Variant, Watch, _internal_a_, _internal_h_, _internal_s_, classes, direction, dispatch, getConfig, getConfigCreator, host, isCSSColor, isCSSUnit, isRTL, off, on, query, queryAll, setConfig, setConfigCreator, slots, toUnit };