@skirbi/sugar 0.1.7 → 0.1.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 +35 -0
- package/README.md +12 -0
- package/lib/fakedom.mjs +1 -0
- package/lib/htmlelement-input.mjs +47 -0
- package/lib/htmlelement.mjs +1 -0
- package/lib/index.mjs +1 -1
- package/lib/livewire.mjs +3 -0
- package/lib/with-connected-sugar.mjs +64 -1
- package/package.json +5 -2
package/Changes
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
Revision history for @skirbi/sugar
|
|
2
2
|
|
|
3
|
+
0.1.9 2026-06-10 21:49:27Z
|
|
4
|
+
|
|
5
|
+
* Add wc:rendered custom event to sugar
|
|
6
|
+
|
|
7
|
+
When components load the order in which they load seems kind of totally
|
|
8
|
+
random. This causes multiple connects and disconnects. Now, the Custom
|
|
9
|
+
Element spec defines a `moveBefore()` method, but unfortunatly, that
|
|
10
|
+
isn't supported by all browsers, most notably Safari. Which is also a
|
|
11
|
+
massive consumer of webcomponents in the whole Apple ecosystem.
|
|
12
|
+
|
|
13
|
+
The workaround to this problem is by giving sugar a way to detect if it
|
|
14
|
+
is inside the DOM-tree of another webcomponent. If sugar is inside a
|
|
15
|
+
webcomponent it will wait until its parent is rendered to actually
|
|
16
|
+
render itself. Within a page where all components are sugared, we can
|
|
17
|
+
safely say that parents are always rendered first, minimizing the amount
|
|
18
|
+
of (dis)connect calls.
|
|
19
|
+
|
|
20
|
+
It does this by looking at `el._internals` and checks for the rendered
|
|
21
|
+
state.
|
|
22
|
+
|
|
23
|
+
When we are part of a webcomponent chain where the parent isn't a
|
|
24
|
+
sugared component, we still wait for the parent to finish and hope our
|
|
25
|
+
parent does similar logic.
|
|
26
|
+
|
|
27
|
+
This approach is simply a workaround until Safari (and others) supports
|
|
28
|
+
`moveBefore`.
|
|
29
|
+
|
|
30
|
+
* Made sideEffects actually target files. The true was breaking things in
|
|
31
|
+
ways I didn't expect.
|
|
32
|
+
|
|
33
|
+
0.1.8 2026-06-08 22:43:46Z
|
|
34
|
+
|
|
35
|
+
* Forward value for htmlelement-input.mjs in all cases not just with
|
|
36
|
+
data-sync. Value is a special case for inputs.
|
|
37
|
+
|
|
3
38
|
0.1.7 2026-06-08 10:59:27Z
|
|
4
39
|
|
|
5
40
|
* Add moveSlot to sugar, multiple components start to create the same logic.
|
package/README.md
CHANGED
|
@@ -46,6 +46,18 @@ And to register them:
|
|
|
46
46
|
MyNewElement.register();
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Just like any other npm package:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npm install @skirbi/sugar
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It is however advised to use an override on your project for `@skirbi/sugar` if
|
|
58
|
+
you either use `@skirbi/semtic`, `@skibri/dibuho` or `@skirbi/bolbe`. This will
|
|
59
|
+
allow correct hoisting.
|
|
60
|
+
|
|
49
61
|
## Observable patterns
|
|
50
62
|
|
|
51
63
|
You can define attributes to be observable:
|
package/lib/fakedom.mjs
CHANGED
|
@@ -40,6 +40,7 @@ export async function setupDomForTesting(
|
|
|
40
40
|
global.document = dom.window.document;
|
|
41
41
|
global.window = dom.window;
|
|
42
42
|
global.MutationObserver = dom.window.MutationObserver;
|
|
43
|
+
global.CustomEvent = dom.window.CustomEvent;
|
|
43
44
|
|
|
44
45
|
const projectRoot = process.cwd();
|
|
45
46
|
|
|
@@ -71,6 +71,37 @@ export class HTMLElementSugarInput extends HTMLElementSugar {
|
|
|
71
71
|
return parseBoolean(raw);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Find the wrapped form control.
|
|
76
|
+
*
|
|
77
|
+
* @returns {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|null}
|
|
78
|
+
*/
|
|
79
|
+
get control() {
|
|
80
|
+
return this.querySelector(this.constructor.controlSelector);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
set value(value) {
|
|
84
|
+
this._value = value ?? '';
|
|
85
|
+
this._syncValueToControl();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get value() {
|
|
89
|
+
const control = this.control;
|
|
90
|
+
if (control && 'value' in control) return control.value;
|
|
91
|
+
|
|
92
|
+
return this._value ?? this.getAttribute('value') ?? '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
96
|
+
super.attributeChangedCallback?.(name, oldValue, newValue);
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if (name === 'value') {
|
|
100
|
+
this._value = newValue ?? '';
|
|
101
|
+
this._syncValueToControl();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
/**
|
|
75
106
|
* Resolve the effective label position for form-control components.
|
|
76
107
|
*
|
|
@@ -217,6 +248,18 @@ export class HTMLElementSugarInput extends HTMLElementSugar {
|
|
|
217
248
|
this._attrObserver.observe(this, { attributes: true });
|
|
218
249
|
}
|
|
219
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Sync the host value attribute/property to the wrapped control property.
|
|
253
|
+
*
|
|
254
|
+
* @returns {void}
|
|
255
|
+
*/
|
|
256
|
+
_syncValueToControl() {
|
|
257
|
+
const control = this.control;
|
|
258
|
+
if (!control || !('value' in control)) return;
|
|
259
|
+
|
|
260
|
+
control.value = this.getAttribute('value') ?? this._value ?? '';
|
|
261
|
+
}
|
|
262
|
+
|
|
220
263
|
/**
|
|
221
264
|
* One-liner for subclasses:
|
|
222
265
|
* - find control in fragment
|
|
@@ -229,6 +272,10 @@ export class HTMLElementSugarInput extends HTMLElementSugar {
|
|
|
229
272
|
enhanceControl(frag) {
|
|
230
273
|
const control = this.findControl(frag);
|
|
231
274
|
|
|
275
|
+
if (this._value !== undefined) {
|
|
276
|
+
control.value = this._value;
|
|
277
|
+
}
|
|
278
|
+
|
|
232
279
|
const deny = this.getForwardDenySet();
|
|
233
280
|
const skip = this.getForwardSkipSet();
|
|
234
281
|
|
package/lib/htmlelement.mjs
CHANGED
package/lib/index.mjs
CHANGED
package/lib/livewire.mjs
CHANGED
|
@@ -30,12 +30,15 @@ if (typeof document !== 'undefined') {
|
|
|
30
30
|
document.body.appendChild(staging);
|
|
31
31
|
staging.innerHTML = effects.html;
|
|
32
32
|
|
|
33
|
+
console.log(effects.html);
|
|
34
|
+
|
|
33
35
|
Promise.resolve().then(() => Promise.resolve().then(() => {
|
|
34
36
|
effects.html = staging.innerHTML;
|
|
35
37
|
effects._sugarHydrated = true;
|
|
36
38
|
staging.remove();
|
|
37
39
|
original(effects);
|
|
38
40
|
}));
|
|
41
|
+
console.log(effects.html);
|
|
39
42
|
};
|
|
40
43
|
});
|
|
41
44
|
});
|
|
@@ -2,20 +2,83 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
|
|
5
|
+
const sugarState = new WeakMap();
|
|
6
|
+
|
|
5
7
|
export const withConnectedSugar = (Base) =>
|
|
6
8
|
class extends Base {
|
|
7
9
|
static morphTriggerSelector = '';
|
|
8
10
|
|
|
9
11
|
connectedCallback() {
|
|
12
|
+
if (!sugarState.has(this)) {
|
|
13
|
+
sugarState.set(this, {
|
|
14
|
+
rendered: false,
|
|
15
|
+
firstUpdatedCalled: false,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const state = sugarState.get(this);
|
|
19
|
+
|
|
10
20
|
this._syncObservedAttributesToConfig();
|
|
11
21
|
this._armMorphObserver();
|
|
22
|
+
|
|
12
23
|
if (this.connectedCallbackSugarOnce) {
|
|
13
24
|
this.connectedCallbackSugarOnce();
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
if (this._shouldRenderOnConnect()) {
|
|
17
|
-
this.
|
|
28
|
+
const wcParent = this._findWcParent();
|
|
29
|
+
|
|
30
|
+
const render = () => {
|
|
31
|
+
|
|
32
|
+
this.connectedCallbackSugar();
|
|
33
|
+
state.rendered = true;
|
|
34
|
+
|
|
35
|
+
const event = new CustomEvent('wc:rendered', { bubbles: false });
|
|
36
|
+
this.dispatchEvent(event);
|
|
37
|
+
|
|
38
|
+
if (!state.firstUpdatedCalled) {
|
|
39
|
+
Promise.resolve().then(() => {
|
|
40
|
+
state.firstUpdatedCalled = true;
|
|
41
|
+
this.firstUpdatedSugar?.();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.renderIfParent(wcParent, render);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
renderIfParent(parent = null, cb) {
|
|
51
|
+
|
|
52
|
+
if (!parent || !sugarState.has(parent)) {
|
|
53
|
+
cb();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const parentState = sugarState.get(parent);
|
|
57
|
+
if (parentState.rendered) {
|
|
58
|
+
cb();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const timeout = setTimeout(cb, 1000);
|
|
63
|
+
parent.addEventListener('wc:rendered', () => {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
cb();
|
|
66
|
+
}, { once: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_findWcParent() {
|
|
71
|
+
let el = this.parentElement;
|
|
72
|
+
while (el) {
|
|
73
|
+
if (el.tagName?.includes('-')) {
|
|
74
|
+
if (el._internals || el._sugarRendered !== undefined) {
|
|
75
|
+
return el;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
el = el.parentElement;
|
|
18
80
|
}
|
|
81
|
+
return null;
|
|
19
82
|
}
|
|
20
83
|
|
|
21
84
|
_syncObservedAttributesToConfig() {
|