@skirbi/sugar 0.0.8 → 0.0.9

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  Revision history for @skirbi/sugar
2
2
 
3
+ 0.0.9 2026-02-23 10:56:27Z
4
+
5
+ * Fix a bug for connectedCallbacks with Livewire
6
+
7
+ In tests with livewire we found out that the connectedCallback doesn't
8
+ always work when others do DOM manipulation. The solution is an optional
9
+ mixin that solves the problem for those cases when you need more logic
10
+ applied to your system.
11
+
12
+ The idea is such, you define `renderGuardSelector`, eg `footer`, we look
13
+ for this element in the DOM under your custom element. If we cannot find
14
+ it, we know we must rerender. So we do that, the only thing you need to
15
+ define if the how. You do that by implementing `connectedCallbackSugar()`.
16
+
17
+ Example:
18
+ class XActions extends withConnectedSugar(HTMLElementSugar) {
19
+ static tag = 'x-actions';
20
+ static renderGuardSelector = 'footer';
21
+
22
+ static _tpl = this.tpl(`
23
+ <footer>
24
+ <div semtic-body></div>
25
+ </footer>
26
+ `);
27
+
28
+ connectedCallbackSugar() {
29
+ const frag = this.renderFromTemplate();
30
+ const body = frag.querySelector('[semtic-body]');
31
+
32
+ // move children into body
33
+ while (this.firstChild) body.appendChild(this.firstChild);
34
+
35
+ this.replaceChildren(frag);
36
+ }
37
+ }
38
+ XActions.register();
39
+
3
40
  0.0.8 2026-02-23 03:32:55Z
4
41
 
5
42
  * Add assertComponentContract testing method. The idea is this:
package/lib/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: MIT
4
4
 
5
- const VERSION = "0.0.8";
5
+ const VERSION = "0.0.9";
6
6
 
7
7
  export { HTMLElementSugar } from './htmlelement.mjs';
8
8
  export { HTMLElementSugarInput } from './htmlelement-input.mjs';
@@ -10,3 +10,4 @@ export { HTMLElementSugarSelect } from './htmlelement-select.mjs';
10
10
  export { parseBoolean } from './boolean.mjs';
11
11
  export { registerDevAlias, applyDevAliases } from './aliases.mjs';
12
12
  export { registerAliases } from './aliases-register.mjs';
13
+ export { withConnectedSugar } from './with-connected-sugar.mjs';
package/lib/testing.mjs CHANGED
@@ -116,6 +116,8 @@ export async function assertComponentContract(Class, example) {
116
116
  needle.isEqualNode(haystack),
117
117
  'connectedCallback works',
118
118
  );
119
+
120
+ if (el.isConnected) document.body.removeChild(el);
119
121
  }
120
122
 
121
123
  /**
@@ -133,3 +135,39 @@ function htmlToFragment(html) {
133
135
  return tpl.content;
134
136
  }
135
137
 
138
+ /**
139
+ * Wait for the DOM lifecycle to settle.
140
+ *
141
+ * Resolves after one microtask and one macrotask tick.
142
+ *
143
+ * Useful in tests to ensure:
144
+ * - customElements upgrades have completed
145
+ * - connectedCallback has run
146
+ * - DOM mutations triggered by innerHTML have flushed
147
+ *
148
+ * @param {number} [times=1] - Number of ticks to wait.
149
+ * @returns {Promise<void>}
150
+ */
151
+ export async function tick(times = 1) {
152
+ for (let i = 0; i < times; i++) {
153
+ await Promise.resolve(); // microtask
154
+ await new Promise(r => setTimeout(r, 0)); // macrotask
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Simulate a morph-style DOM replacement (e.g. Livewire / morphdom).
160
+ *
161
+ * Replaces the element's innerHTML and waits for lifecycle hooks
162
+ * and custom element upgrades to complete.
163
+ *
164
+ * Intended for testing idempotent connectedCallback behavior.
165
+ *
166
+ * @param {Element} el - The container element to replace content in.
167
+ * @param {string} html - The HTML string to inject.
168
+ * @returns {Promise<void>}
169
+ */
170
+ export async function morphReplace(el, html) {
171
+ el.innerHTML = html;
172
+ await tick();
173
+ }
@@ -0,0 +1,35 @@
1
+ export const withConnectedSugar = (Base) =>
2
+ class extends Base {
3
+ static renderGuardSelector = '';
4
+
5
+ _syncObservedAttributesToConfig() {
6
+ for (const attr of this.constructor.observedAttributes ?? []) {
7
+ const val = this.getAttribute(attr);
8
+ if (val !== null) this._applyAttribute(attr, val);
9
+ }
10
+ }
11
+
12
+ _shouldRenderOnConnect() {
13
+ const guard = this.constructor.renderGuardSelector;
14
+
15
+ if (guard) {
16
+ return !this.querySelector(guard);
17
+ }
18
+
19
+ if (this._rendered) return false;
20
+
21
+ this._rendered = true;
22
+ return true;
23
+ }
24
+
25
+ connectedCallback() {
26
+ this._syncObservedAttributesToConfig();
27
+
28
+ if (!this._shouldRenderOnConnect()) return;
29
+
30
+ if (typeof this.connectedCallbackSugar === 'function') {
31
+ this.connectedCallbackSugar();
32
+ }
33
+ }
34
+ };
35
+
package/package.json CHANGED
@@ -26,7 +26,8 @@
26
26
  "./htmlelement": "./lib/htmlelement.mjs",
27
27
  "./htmlelement-input": "./lib/htmlelement-input.mjs",
28
28
  "./htmlelement-select": "./lib/htmlelement-select.mjs",
29
- "./testing": "./lib/testing.mjs"
29
+ "./testing": "./lib/testing.mjs",
30
+ "./with-connected-sugar": "./lib/with-connected-sugar.mjs"
30
31
  },
31
32
  "homepage": "https://gitlab.com/skirbi/sugar",
32
33
  "keywords": [
@@ -52,5 +53,5 @@
52
53
  },
53
54
  "sideEffects": false,
54
55
  "type": "module",
55
- "version": "0.0.8"
56
+ "version": "0.0.9"
56
57
  }