@skirbi/sugar 0.0.9 → 0.0.11
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 +58 -0
- package/lib/index.mjs +1 -1
- package/lib/testing.mjs +35 -16
- package/lib/with-connected-sugar.mjs +27 -6
- package/package.json +4 -4
package/Changes
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
Revision history for @skirbi/sugar
|
|
2
2
|
|
|
3
|
+
0.0.11 2026-02-24 16:53:49Z
|
|
4
|
+
|
|
5
|
+
* Fix comparing HTML nodes. Whitespace text nodes are a PITA, we deal with
|
|
6
|
+
them and now you can just compare two nodes regardless how you wrote it.
|
|
7
|
+
Whitespace kinda matters in HTML I guess, until it doesn't.
|
|
8
|
+
|
|
9
|
+
0.0.10 2026-02-24 03:09:34Z
|
|
10
|
+
|
|
11
|
+
* Improve Livewire/morphdom support in withConnectedSugar
|
|
12
|
+
|
|
13
|
+
In certain morph scenarios the host element instance remains the same
|
|
14
|
+
while its child nodes are replaced. In that case `connectedCallback`
|
|
15
|
+
is not triggered again.
|
|
16
|
+
|
|
17
|
+
The mixin now provides `_observeChildListOnce(when, fn)` which allows
|
|
18
|
+
components to react to child mutations and re-apply their compile logic
|
|
19
|
+
when necessary.
|
|
20
|
+
|
|
21
|
+
This keeps DOM-compiling components (e.g. semtic-steps in @skirbi/semtic)
|
|
22
|
+
stable even when morphing frameworks replace their internal markup without
|
|
23
|
+
recreating the custom element instance.
|
|
24
|
+
|
|
25
|
+
The pattern for a `connectedCallbackSugar` now becomes:
|
|
26
|
+
|
|
27
|
+
connectedCallbackSugar() {
|
|
28
|
+
this._compile();
|
|
29
|
+
|
|
30
|
+
this._observeChildListOnce(
|
|
31
|
+
() => this.querySelector(':scope > your-tag-here'),
|
|
32
|
+
() => this._compile()
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Where `your-tag-here` represents the authored child element that
|
|
37
|
+
indicates the component has been reverted to its pre-compiled state.
|
|
38
|
+
|
|
39
|
+
The `_compile` function is part of the contract as it authors child
|
|
40
|
+
elements into the final DOM structure. But you are free give it any name
|
|
41
|
+
you'd like. An example implementation may look like this:
|
|
42
|
+
|
|
43
|
+
_compile() {
|
|
44
|
+
const steps = Array.from(this.children)
|
|
45
|
+
.filter(n => n.tagName?.toLowerCase() === 'x-step')
|
|
46
|
+
.map(n => n.textContent ?? '');
|
|
47
|
+
|
|
48
|
+
const frag = this.renderFromTemplate();
|
|
49
|
+
const ol = frag.querySelector('ol[data-steps]');
|
|
50
|
+
|
|
51
|
+
for (const label of steps) {
|
|
52
|
+
const li = document.createElement('li');
|
|
53
|
+
li.textContent = label;
|
|
54
|
+
ol.appendChild(li);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.replaceChildren(frag);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
3
61
|
0.0.9 2026-02-23 10:56:27Z
|
|
4
62
|
|
|
5
63
|
* Fix a bug for connectedCallbacks with Livewire
|
package/lib/index.mjs
CHANGED
package/lib/testing.mjs
CHANGED
|
@@ -56,23 +56,21 @@ export async function setupDomForTesting(
|
|
|
56
56
|
*
|
|
57
57
|
* This helper verifies:
|
|
58
58
|
*
|
|
59
|
-
* 1. `Class.exampleHTML` matches the provided `example` snippet.
|
|
60
|
-
* 2. After mounting and lifecycle execution, the rendered output
|
|
61
|
-
*
|
|
59
|
+
* 1. `Class.exampleHTML` matches the provided `options.example` snippet.
|
|
60
|
+
* 2. After mounting and lifecycle execution, the rendered output matches
|
|
61
|
+
* `Class.exampleRenderedHTML`.
|
|
62
62
|
*
|
|
63
|
-
* The component must define:
|
|
63
|
+
* The component class must define:
|
|
64
64
|
*
|
|
65
|
-
* - `static exampleHTML`
|
|
66
|
-
* - `static exampleRenderedHTML`
|
|
65
|
+
* - `static exampleHTML` (string)
|
|
66
|
+
* - `static exampleRenderedHTML` (string)
|
|
67
67
|
*
|
|
68
|
-
*
|
|
68
|
+
* It may also define:
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* exampleRenderedHTML: string
|
|
75
|
-
* }} Class
|
|
70
|
+
* - `static register()` (optional)
|
|
71
|
+
* - `static init()` (optional)
|
|
72
|
+
*
|
|
73
|
+
* @param {Function} Class
|
|
76
74
|
* The component class under test.
|
|
77
75
|
*
|
|
78
76
|
* @param {Object} options
|
|
@@ -82,7 +80,7 @@ export async function setupDomForTesting(
|
|
|
82
80
|
* @param {string} options.example
|
|
83
81
|
* Expected authoring HTML to compare against `Class.exampleHTML`.
|
|
84
82
|
*
|
|
85
|
-
* @param {
|
|
83
|
+
* @param {Object} options.tap
|
|
86
84
|
* Tap test instance used for assertions.
|
|
87
85
|
*
|
|
88
86
|
* @returns {Promise<void>}
|
|
@@ -131,8 +129,29 @@ export async function assertComponentContract(Class, example) {
|
|
|
131
129
|
*/
|
|
132
130
|
function htmlToFragment(html) {
|
|
133
131
|
const tpl = document.createElement('template');
|
|
134
|
-
tpl.innerHTML = html.trim();
|
|
135
|
-
return tpl.content;
|
|
132
|
+
tpl.innerHTML = html.trim().normalize();
|
|
133
|
+
return stripWhitespaceTextNodes(tpl.content);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Strip whitespace textnodes from an HTML fragment
|
|
138
|
+
*
|
|
139
|
+
* Use primairly to sanitize HTML to compare it
|
|
140
|
+
*
|
|
141
|
+
* @param {node} node - an HTML node
|
|
142
|
+
* @returns {node}
|
|
143
|
+
*/
|
|
144
|
+
function stripWhitespaceTextNodes(node) {
|
|
145
|
+
for (const child of Array.from(node.childNodes)) {
|
|
146
|
+
if (child.nodeType === 3) { // TEXT_NODE
|
|
147
|
+
if (!child.nodeValue.trim()) {
|
|
148
|
+
child.remove();
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
stripWhitespaceTextNodes(child);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return node;
|
|
136
155
|
}
|
|
137
156
|
|
|
138
157
|
/**
|
|
@@ -11,20 +11,41 @@ export const withConnectedSugar = (Base) =>
|
|
|
11
11
|
|
|
12
12
|
_shouldRenderOnConnect() {
|
|
13
13
|
const guard = this.constructor.renderGuardSelector;
|
|
14
|
-
|
|
15
|
-
if (guard) {
|
|
16
|
-
return !this.querySelector(guard);
|
|
17
|
-
}
|
|
14
|
+
if (guard) return !this.querySelector(guard);
|
|
18
15
|
|
|
19
16
|
if (this._rendered) return false;
|
|
20
|
-
|
|
21
17
|
this._rendered = true;
|
|
22
18
|
return true;
|
|
23
19
|
}
|
|
24
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Install a single MutationObserver that calls `fn()` when direct children
|
|
23
|
+
* change and `when()` returns true.
|
|
24
|
+
*
|
|
25
|
+
* Intended for morphdom/livewire scenarios where the host stays the same
|
|
26
|
+
* instance.
|
|
27
|
+
*
|
|
28
|
+
* @param {Function} when - predicate
|
|
29
|
+
* @param {Function} fn - callback
|
|
30
|
+
*/
|
|
31
|
+
_observeChildListOnce(when, fn) {
|
|
32
|
+
if (this._sugarMo) return;
|
|
33
|
+
|
|
34
|
+
this._sugarMo = new MutationObserver(() => {
|
|
35
|
+
if (when()) fn();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this._sugarMo.observe(this, { childList: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
disconnectedCallback() {
|
|
42
|
+
// polite cleanup
|
|
43
|
+
this._sugarMo?.disconnect();
|
|
44
|
+
this._sugarMo = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
connectedCallback() {
|
|
26
48
|
this._syncObservedAttributesToConfig();
|
|
27
|
-
|
|
28
49
|
if (!this._shouldRenderOnConnect()) return;
|
|
29
50
|
|
|
30
51
|
if (typeof this.connectedCallbackSugar === 'function') {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"name": "Wesley Schwengle"
|
|
5
5
|
},
|
|
6
6
|
"bugs": {
|
|
7
|
-
"url": "https://gitlab.com/skirbi/
|
|
7
|
+
"url": "https://gitlab.com/skirbi/skirbi/-/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@opndev/util": "latest"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"./testing": "./lib/testing.mjs",
|
|
30
30
|
"./with-connected-sugar": "./lib/with-connected-sugar.mjs"
|
|
31
31
|
},
|
|
32
|
-
"homepage": "https://gitlab.com/skirbi/
|
|
32
|
+
"homepage": "https://gitlab.com/skirbi/skirbi",
|
|
33
33
|
"keywords": [
|
|
34
34
|
"custom elements",
|
|
35
35
|
"web components",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"name": "@skirbi/sugar",
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
45
|
-
"url": "git+https://gitlab.com/skirbi/
|
|
45
|
+
"url": "git+https://gitlab.com/skirbi/skirbi.git"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "rzil build",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
},
|
|
54
54
|
"sideEffects": false,
|
|
55
55
|
"type": "module",
|
|
56
|
-
"version": "0.0.
|
|
56
|
+
"version": "0.0.11"
|
|
57
57
|
}
|