@skirbi/sugar 0.0.11 → 0.0.13

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,9 +1,63 @@
1
1
  Revision history for @skirbi/sugar
2
2
 
3
+ 0.0.13 2026-04-16 13:27:59Z
4
+
5
+ * Fix jsdoc build issue
6
+ * Retro actively fix Changelog for v0.0.9, we missed two small additions in
7
+ testing.mjs
8
+ * Add assertQuerySelector() method
9
+
10
+ 0.0.12 2026-04-13 00:51:51Z
11
+
12
+ * While checking other Semtic units, we tightened a few tests and exposed an
13
+ issue in how components were rendered and rerendered.
14
+
15
+ We added new test cases to separate what already worked from what did not,
16
+ and validated those cases against Livewire. That led to a small redesign of
17
+ the `connectedSugar` API.
18
+
19
+ The result is fairly clean: a consumer now defines two selectors:
20
+
21
+ * `renderGuardSelector` indicates whether the component is already fully
22
+ rendered.
23
+ * `morphTriggerSelector` indicates whether the component has been morphed back
24
+ to its authored state.
25
+
26
+ Based on these checks, `connectedCallbackSugar()` is called when needed, and
27
+ the component can normalize itself back to its canonical DOM.
28
+
29
+ The previously advised pattern for `connectedCallbackSugar()` is now
30
+ obsolete.
31
+
32
+ This
33
+
34
+ connectedCallbackSugar() {
35
+ this._compile();
36
+ this._observeChildListOnce(...);
37
+ }
38
+
39
+ Becomes:
40
+
41
+ class SugarParent extends withConnectedSugar(HTMLElementSugar) {
42
+ static tag = 'sugar-parent';
43
+
44
+ static renderGuardSelector = ':scope > div[data-sugar-parent]'
45
+ static morphTriggerSelector = ':scope > sugar-child';
46
+
47
+ static _tpl = this.tpl(`
48
+ <div data-sugar-parent></div>
49
+ `);
50
+
51
+ connectedCallbackSugar() {
52
+ /* Whatever you want to do here to (re-)render the component */
53
+ }
54
+ }
55
+
56
+
3
57
  0.0.11 2026-02-24 16:53:49Z
4
58
 
5
59
  * 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.
60
+ <F2>them and now you can just compare two nodes regardless how you wrote it.
7
61
  Whitespace kinda matters in HTML I guess, until it doesn't. &nbsp;
8
62
 
9
63
  0.0.10 2026-02-24 03:09:34Z
@@ -95,6 +149,8 @@ Revision history for @skirbi/sugar
95
149
  }
96
150
  XActions.register();
97
151
 
152
+ * Add `tick` and `morphReplace` methods in `testing.mjs`.
153
+
98
154
  0.0.8 2026-02-23 03:32:55Z
99
155
 
100
156
  * Add assertComponentContract testing method. The idea is this:
@@ -327,5 +327,11 @@ export class HTMLElementSugar extends HTMLElement {
327
327
  getConfig() {
328
328
  return { ...this.config };
329
329
  }
330
+
331
+ static assertQuerySelector(haystack, needle, msg) {
332
+ const item = haystack.querySelector(needle)
333
+ if (!item) throw new Error(msg);
334
+ return item;
335
+ }
330
336
  }
331
337
 
package/lib/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: MIT
4
4
 
5
- const VERSION = "0.0.11";
5
+ const VERSION = "0.0.13";
6
6
 
7
7
  export { HTMLElementSugar } from './htmlelement.mjs';
8
8
  export { HTMLElementSugarInput } from './htmlelement-input.mjs';
@@ -1,6 +1,16 @@
1
1
  export const withConnectedSugar = (Base) =>
2
2
  class extends Base {
3
3
  static renderGuardSelector = '';
4
+ static morphTriggerSelector = '';
5
+
6
+ connectedCallback() {
7
+ this._syncObservedAttributesToConfig();
8
+ this._armMorphObserver();
9
+
10
+ if (this._shouldRenderOnConnect()) {
11
+ this.connectedCallbackSugar();
12
+ }
13
+ }
4
14
 
5
15
  _syncObservedAttributesToConfig() {
6
16
  for (const attr of this.constructor.observedAttributes ?? []) {
@@ -9,48 +19,43 @@ export const withConnectedSugar = (Base) =>
9
19
  }
10
20
  }
11
21
 
12
- _shouldRenderOnConnect() {
13
- const guard = this.constructor.renderGuardSelector;
14
- if (guard) return !this.querySelector(guard);
15
-
16
- if (this._rendered) return false;
17
- this._rendered = true;
18
- return true;
19
- }
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) {
22
+ _armMorphObserver() {
32
23
  if (this._sugarMo) return;
33
24
 
34
25
  this._sugarMo = new MutationObserver(() => {
35
- if (when()) fn();
26
+ if (this._shouldRenderOnMorph()) {
27
+ this.connectedCallbackSugar();
28
+ }
36
29
  });
37
30
 
38
31
  this._sugarMo.observe(this, { childList: true });
39
32
  }
40
33
 
34
+ _shouldRenderOnMorph() {
35
+ return this._hasMorphTrigger() && !this._hasRenderedShape();
36
+ }
37
+
38
+ _hasMorphTrigger() {
39
+ const sel = this.constructor.morphTriggerSelector;
40
+ return !!(sel && this.querySelector(sel));
41
+ }
42
+
43
+ _shouldRenderOnConnect() {
44
+ const guard = this.constructor.renderGuardSelector;
45
+ if (guard) return !this._hasRenderedShape();
46
+
47
+ return true;
48
+ }
49
+
50
+ _hasRenderedShape() {
51
+ const sel = this.constructor.renderGuardSelector;
52
+ return !!(sel && this.querySelector(sel));
53
+ }
54
+
41
55
  disconnectedCallback() {
42
- // polite cleanup
43
56
  this._sugarMo?.disconnect();
44
57
  this._sugarMo = null;
45
58
  }
46
59
 
47
- connectedCallback() {
48
- this._syncObservedAttributesToConfig();
49
- if (!this._shouldRenderOnConnect()) return;
50
-
51
- if (typeof this.connectedCallbackSugar === 'function') {
52
- this.connectedCallbackSugar();
53
- }
54
- }
55
60
  };
56
61
 
package/package.json CHANGED
@@ -53,5 +53,5 @@
53
53
  },
54
54
  "sideEffects": false,
55
55
  "type": "module",
56
- "version": "0.0.11"
56
+ "version": "0.0.13"
57
57
  }