@skirbi/sugar 0.1.0 → 0.1.2
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/Changes +45 -0
- package/lib/htmlelement-select.mjs +1 -1
- package/lib/htmlelement.mjs +54 -0
- package/lib/index.mjs +1 -1
- package/lib/with-connected-sugar.mjs +52 -9
- package/package.json +1 -1
package/Changes
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
Revision history for @skirbi/sugar
|
|
2
2
|
|
|
3
|
+
0.1.2 2026-06-05 13:39:51Z
|
|
4
|
+
|
|
5
|
+
* Add replaceChildrenSugarSafe to allow repaints without focus loss
|
|
6
|
+
|
|
7
|
+
Add initial version of a safer version for `replaceChildren()` called
|
|
8
|
+
`replaceChildrenSugarSafe()`. During real life usage it became apparant
|
|
9
|
+
that inputs lose focus with `wire:model` changes. This is slightly
|
|
10
|
+
annoying. Allow for a more fine grained approach where we update
|
|
11
|
+
existing fragements rather than replacing them full fledged.
|
|
12
|
+
|
|
13
|
+
0.1.1 2026-06-04 15:34:20Z
|
|
14
|
+
|
|
15
|
+
* Added morph-* attributes to withConnectedSugar, the API of this is
|
|
16
|
+
unstable and should probably only be used when debugging Livewire morphing
|
|
17
|
+
or similar tools.
|
|
18
|
+
|
|
19
|
+
Some debugging artifacts have become morph-* attributes:
|
|
20
|
+
|
|
21
|
+
* morph-ignore — disarm the observer entirely (no-op against full node
|
|
22
|
+
replacement by Livewire, but prevents cascade
|
|
23
|
+
re-renders during normal morphing)
|
|
24
|
+
* morph-child — observe childList only (explicit opt-in)
|
|
25
|
+
* morph-attribute — observe attribute changes only
|
|
26
|
+
* morph-subtree — observe the full descendant subtree
|
|
27
|
+
* morph-debug — Enable debuging used by `_armMorphObserver`.
|
|
28
|
+
|
|
29
|
+
When none are present, the existing default (childList: true) applies.
|
|
30
|
+
|
|
31
|
+
* Add `withMorphObserverPaused(fn)` to withConnectedSugar.
|
|
32
|
+
Components that mutate their own children in morphedCallbackSugar or
|
|
33
|
+
connectedCallbackSugar risk triggering their own MutationObserver,
|
|
34
|
+
causing infinite loops.
|
|
35
|
+
|
|
36
|
+
`withMorphObserverPaused(fn)` disconnects the observer, runs `fn`, then
|
|
37
|
+
reconnects with the original options. It is a public API intended for
|
|
38
|
+
use by component authors.
|
|
39
|
+
|
|
40
|
+
Observer options are now stored as `_morphObserveOptions` when the
|
|
41
|
+
observer is armed, so they can be reused by withMorphObserverPaused
|
|
42
|
+
and any future callers without re-deriving them.
|
|
43
|
+
|
|
44
|
+
This again is a WIP feature that may change based on Livewire morphing
|
|
45
|
+
insights.
|
|
46
|
+
|
|
47
|
+
|
|
3
48
|
0.1.0 2026-06-04 01:20:59Z
|
|
4
49
|
|
|
5
50
|
* BREAKING: Sugar components may no longer manually define
|
package/lib/htmlelement.mjs
CHANGED
|
@@ -437,5 +437,59 @@ export class HTMLElementSugar extends HTMLElement {
|
|
|
437
437
|
$$(selector) {
|
|
438
438
|
return [...this.querySelectorAll(selector)]
|
|
439
439
|
}
|
|
440
|
+
|
|
441
|
+
_morphElement(oldEl, newEl) {
|
|
442
|
+
this._morphAttributes(oldEl, newEl);
|
|
443
|
+
|
|
444
|
+
const newChildren = Array.from(newEl.childNodes);
|
|
445
|
+
const oldChildren = Array.from(oldEl.childNodes);
|
|
446
|
+
|
|
447
|
+
newChildren.forEach((newNode, i) => {
|
|
448
|
+
const oldNode = oldChildren[i];
|
|
449
|
+
if (!oldNode) {
|
|
450
|
+
oldEl.appendChild(newNode);
|
|
451
|
+
}
|
|
452
|
+
else if (oldNode.nodeType === newNode.nodeType && oldNode.nodeName === newNode.nodeName) {
|
|
453
|
+
if (oldNode.nodeType === Node.TEXT_NODE) {
|
|
454
|
+
if (oldNode.textContent !== newNode.textContent) {
|
|
455
|
+
oldNode.textContent = newNode.textContent;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
this._morphElement(oldNode, newNode);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
oldEl.replaceChild(newNode, oldNode);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
oldChildren.slice(newChildren.length).forEach(n => oldEl.removeChild(n));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
_morphAttributes(oldEl, newEl) {
|
|
471
|
+
if (!oldEl.attributes || !newEl.attributes) return;
|
|
472
|
+
|
|
473
|
+
for (const attr of newEl.attributes) {
|
|
474
|
+
if (oldEl.getAttribute(attr.name) !== attr.value) {
|
|
475
|
+
oldEl.setAttribute(attr.name, attr.value);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
for (const attr of Array.from(oldEl.attributes)) {
|
|
479
|
+
if (!newEl.hasAttribute(attr.name)) {
|
|
480
|
+
oldEl.removeAttribute(attr.name);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
_morphChildren(frag) {
|
|
486
|
+
this._morphElement(this, frag);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
replaceChildrenSugarSafe(frag) {
|
|
491
|
+
this._morphElement(this, frag);
|
|
492
|
+
}
|
|
493
|
+
|
|
440
494
|
}
|
|
441
495
|
|
package/lib/index.mjs
CHANGED
|
@@ -27,19 +27,51 @@ export const withConnectedSugar = (Base) =>
|
|
|
27
27
|
|
|
28
28
|
_armMorphObserver() {
|
|
29
29
|
if (this._sugarMo) return;
|
|
30
|
+
if (this.hasAttribute('morph-ignore')) return;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const hasExplicit = this.hasAttribute('morph-child') ||
|
|
33
|
+
this.hasAttribute('morph-attribute') ||
|
|
34
|
+
this.hasAttribute('morph-subtree');
|
|
35
|
+
|
|
36
|
+
this._morphObserveOptions = hasExplicit ? {
|
|
37
|
+
childList: this.hasAttribute('morph-child'),
|
|
38
|
+
attributes: this.hasAttribute('morph-attribute'),
|
|
39
|
+
subtree: this.hasAttribute('morph-subtree'),
|
|
40
|
+
} : {
|
|
41
|
+
childList: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this._sugarMo = new MutationObserver((mutations) => {
|
|
45
|
+
|
|
46
|
+
if (this.hasAttribute('morph-debug')) {
|
|
47
|
+
console.group(`[sugar-morph] ${this.tagName}${this.id ? '#' + this.id : ''}${this.getAttribute('label') ? `[label="${this.getAttribute('label')}"]` : ''}${this.getAttribute('title') ? `[title="${this.getAttribute('title')}"]` : ''}`);
|
|
48
|
+
console.log('parent:', this.parentElement?.tagName);
|
|
49
|
+
console.log('mutations:', mutations.map(m => ({
|
|
50
|
+
type: m.type,
|
|
51
|
+
added: Array.from(m.addedNodes).map(n => n.nodeName),
|
|
52
|
+
removed: Array.from(m.removedNodes).map(n => n.nodeName)
|
|
53
|
+
})));
|
|
54
|
+
console.log('hasRenderedShape:', this.hasRenderedShape());
|
|
55
|
+
console.log('shouldRenderOnMorph:', this._shouldRenderOnMorph());
|
|
56
|
+
setTimeout(() => console.log('innerHTML after:', this.innerHTML), 0);
|
|
57
|
+
console.groupEnd();
|
|
39
58
|
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
queueMicrotask(() => {
|
|
62
|
+
if (this._shouldRenderOnMorph()) {
|
|
63
|
+
this.connectedCallbackSugar();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.hasRenderedShape() && this.morphedCallbackSugar) {
|
|
67
|
+
this.morphedCallbackSugar();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
40
72
|
});
|
|
41
73
|
|
|
42
|
-
this._sugarMo.observe(this,
|
|
74
|
+
this._sugarMo.observe(this, this._morphObserveOptions);
|
|
43
75
|
}
|
|
44
76
|
|
|
45
77
|
_shouldRenderOnMorph() {
|
|
@@ -58,6 +90,17 @@ export const withConnectedSugar = (Base) =>
|
|
|
58
90
|
return true;
|
|
59
91
|
}
|
|
60
92
|
|
|
93
|
+
withMorphObserverPaused(fn) {
|
|
94
|
+
this._sugarMo?.disconnect();
|
|
95
|
+
try {
|
|
96
|
+
fn();
|
|
97
|
+
} finally {
|
|
98
|
+
if (this._morphObserveOptions) {
|
|
99
|
+
this._sugarMo?.observe(this, this._morphObserveOptions);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
61
104
|
disconnectedCallback() {
|
|
62
105
|
this._sugarMo?.disconnect();
|
|
63
106
|
this._sugarMo = null;
|
package/package.json
CHANGED