@skirbi/sugar 0.1.5 → 0.1.7
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 +23 -0
- package/README.md +73 -46
- package/lib/htmlelement.mjs +36 -2
- package/lib/index.mjs +1 -1
- package/package.json +5 -2
package/Changes
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
Revision history for @skirbi/sugar
|
|
2
2
|
|
|
3
|
+
0.1.7 2026-06-08 10:59:27Z
|
|
4
|
+
|
|
5
|
+
* Add moveSlot to sugar, multiple components start to create the same logic.
|
|
6
|
+
* Add keywords to dist.toml/package.json
|
|
7
|
+
* Add tests for aliases
|
|
8
|
+
|
|
9
|
+
0.1.6 2026-06-08 01:05:36Z
|
|
10
|
+
|
|
11
|
+
* Add attributeChangedCallback hook system
|
|
12
|
+
Extend attributeChangedCallback with a declarative hook system in
|
|
13
|
+
attributeDefs. When an observed attribute changes and the component
|
|
14
|
+
has a rendered shape, sugar automatically updates the DOM:
|
|
15
|
+
|
|
16
|
+
Convention: attribute 'foo' maps to [semtic-foo] textContent
|
|
17
|
+
String hook: CSS selector target, updates textContent
|
|
18
|
+
Function hook: full control, called with (el, newValue, oldValue)
|
|
19
|
+
Escape hatch: attributeChangedCallbackSugar(name, old, new)
|
|
20
|
+
|
|
21
|
+
Requires renderGuardSelector to be set. Hooks only fire when the
|
|
22
|
+
component has already rendered. This makes renderGuardSelector
|
|
23
|
+
effectively mandatory for components that want reactive attribute
|
|
24
|
+
updates.
|
|
25
|
+
|
|
3
26
|
0.1.5 2026-06-06 18:09:56Z
|
|
4
27
|
|
|
5
28
|
* Now actually do it.. pls send coffee
|
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ It provides:
|
|
|
20
20
|
You can create Web Components like so:
|
|
21
21
|
|
|
22
22
|
```javascript
|
|
23
|
+
import { HTMLElementSugar } from '@skirbi/sugar';
|
|
24
|
+
|
|
23
25
|
class MyNewElement extends HTMLElementSugar {
|
|
24
26
|
static tag = 'my-new-element';
|
|
25
27
|
static attributeDefs = {
|
|
@@ -27,20 +29,21 @@ class MyNewElement extends HTMLElementSugar {
|
|
|
27
29
|
bar: { parser: parseBoolean },
|
|
28
30
|
};
|
|
29
31
|
}
|
|
32
|
+
MyNewElement.register();
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
You can also create aliases:
|
|
33
36
|
|
|
34
37
|
```javascript
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
import { registerDevAlias } from '@skirbi/sugar';
|
|
39
|
+
registerDevAlias('some-song', 'track-item', { type: 'song' });
|
|
40
|
+
registerDevAlias('a-book', 'your-book');
|
|
37
41
|
```
|
|
38
42
|
|
|
39
43
|
And to register them:
|
|
40
44
|
|
|
41
45
|
```javascript
|
|
42
|
-
|
|
43
|
-
YourBook.register();
|
|
46
|
+
MyNewElement.register();
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
## Observable patterns
|
|
@@ -288,21 +291,21 @@ HTMLElementSugarSelect extends HTMLElementSugarInput and provides a
|
|
|
288
291
|
structured way to author `<select>`-based components.
|
|
289
292
|
|
|
290
293
|
It supports:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
- Static option lists (via JSON)
|
|
295
|
+
- Remote option loading (via endpoint)
|
|
296
|
+
- Flexible data mapping via jpath
|
|
297
|
+
- Label/value templates
|
|
298
|
+
- Optional search input
|
|
299
|
+
- Attribute forwarding to the underlying `<select>`
|
|
297
300
|
|
|
298
301
|
It always renders a real `<select>` in the light DOM.
|
|
299
302
|
|
|
300
303
|
### Contract
|
|
301
304
|
|
|
302
305
|
A subclass must:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
- Extend HTMLElementSugarSelect
|
|
307
|
+
- Render exactly one `<select wc-control>`
|
|
308
|
+
- Optionally include a search input if searchable is enabled
|
|
306
309
|
|
|
307
310
|
Example:
|
|
308
311
|
|
|
@@ -336,11 +339,11 @@ You may provide options via the options attribute as a JSON array:
|
|
|
336
339
|
### Mapping Options
|
|
337
340
|
|
|
338
341
|
Data mapping is controlled via:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
- `jpath`: path to the array in a nested JSON response
|
|
343
|
+
- `jpath-label`: path for the option label
|
|
344
|
+
- `jpath-value`: path for the option value
|
|
345
|
+
- `jpath-label-template`: template string for label
|
|
346
|
+
- `jpath-value-template`: template string for value
|
|
344
347
|
|
|
345
348
|
Templates take precedence over path mappings.
|
|
346
349
|
|
|
@@ -375,53 +378,77 @@ Options can be loaded from a remote endpoint:
|
|
|
375
378
|
```
|
|
376
379
|
|
|
377
380
|
Behavior:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
- Fetches JSON from endpoint
|
|
382
|
+
- Adds search query via param
|
|
383
|
+
- Applies mapping rules
|
|
384
|
+
- Replaces options on each fetch
|
|
382
385
|
|
|
383
386
|
If `searchable` is enabled:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
on connect. If `nosearch-initial` is enabled this behavior is negated
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
* No initial searches are
|
|
387
|
+
- A search input is rendered
|
|
388
|
+
- Input is debounced
|
|
389
|
+
- Fetch is triggered after min-chars is reached
|
|
390
|
+
- By default, a remote endpoint triggers an initial fetch with an empty query
|
|
391
|
+
on connect. If `nosearch-initial` is enabled this behavior is negated and no
|
|
392
|
+
initial searches are executed.
|
|
392
393
|
|
|
393
394
|
### Search Behavior (Static Mode)
|
|
394
395
|
|
|
395
396
|
When using static options, enabling searchable:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
- Filters `<option>` elements in place
|
|
398
|
+
- Uses case-insensitive substring matching
|
|
399
|
+
- Does not modify underlying data
|
|
399
400
|
|
|
400
401
|
### Value Handling
|
|
401
402
|
|
|
402
403
|
The value attribute behaves consistently with `HTMLElementSugarInput`:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
- Initial value is applied after options render
|
|
405
|
+
- With data-sync, updates are mirrored to the `<select>` element
|
|
406
|
+
- Native input and change events are re-emitted from the host
|
|
406
407
|
|
|
407
408
|
### Framework Compatibility
|
|
408
409
|
|
|
409
410
|
Because it renders a real `<select>` in the light DOM, it works naturally with:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
411
|
+
- Livewire (`wire:*`)
|
|
412
|
+
- Alpine (`x-*`)
|
|
413
|
+
- HTMX (`hx-*`)
|
|
414
|
+
- `aria-*`
|
|
415
|
+
- `data-*`
|
|
415
416
|
|
|
416
417
|
Attributes not defined in attributeDefs are forwarded to the `<select>`
|
|
417
418
|
element.
|
|
418
419
|
|
|
420
|
+
#### Livewire
|
|
421
|
+
|
|
422
|
+
For Livewire applications using Sugar components, import the optional
|
|
423
|
+
Livewire integration:
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
import '@skirbi/sugar/livewire';
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
This fixes a fundamental incompatibility between Livewire's morph algorithm
|
|
430
|
+
and Web Components. Livewire compares its authored HTML against an already
|
|
431
|
+
rendered DOM — structurally different trees cause cascade disconnects,
|
|
432
|
+
reconnects, and focus loss on every update.
|
|
433
|
+
|
|
434
|
+
The integration hydrates Livewire's new HTML in a staging element before
|
|
435
|
+
morphing starts, so Alpine compares two fully rendered trees instead of
|
|
436
|
+
authored vs rendered. No cascade, no focus loss.
|
|
437
|
+
|
|
438
|
+
Import it before registering your components:
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
import '@skirbi/sugar/livewire';
|
|
442
|
+
import '@skirbi/semtic/register-semtic';
|
|
443
|
+
```
|
|
444
|
+
|
|
419
445
|
### Why This Works
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
446
|
+
|
|
447
|
+
- No shadow DOM
|
|
448
|
+
- No custom dropdown UI
|
|
449
|
+
- No CSS lock-in
|
|
450
|
+
- Real `<option>` elements
|
|
451
|
+
- Progressive enhancement friendly
|
|
425
452
|
|
|
426
453
|
## Code of Conduct
|
|
427
454
|
|
package/lib/htmlelement.mjs
CHANGED
|
@@ -347,11 +347,31 @@ export class HTMLElementSugar extends HTMLElement {
|
|
|
347
347
|
/**
|
|
348
348
|
* Called when an observed attribute changes.
|
|
349
349
|
* @param {string} name - The attribute name
|
|
350
|
-
* @param {string|null}
|
|
350
|
+
* @param {string|null} oldVal - Previous value (unused)
|
|
351
351
|
* @param {string|null} newVal - New value
|
|
352
352
|
*/
|
|
353
|
-
attributeChangedCallback(name,
|
|
353
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
354
354
|
this._applyAttribute(name, newVal);
|
|
355
|
+
|
|
356
|
+
if (!this.hasRenderedShape?.()) return;
|
|
357
|
+
|
|
358
|
+
const def = this.constructor.attributeDefs?.[name];
|
|
359
|
+
|
|
360
|
+
if (def?.hook) {
|
|
361
|
+
if (typeof def.hook === 'function') {
|
|
362
|
+
def.hook(this, newVal, oldVal);
|
|
363
|
+
} else if (typeof def.hook === 'string') {
|
|
364
|
+
const el = this.querySelector(def.hook);
|
|
365
|
+
if (el) el.textContent = newVal ?? '';
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
const el = this.querySelector(`[semtic-${name}]`);
|
|
369
|
+
if (el) el.textContent = newVal ?? '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (this.attributeChangedCallbackSugar) {
|
|
373
|
+
this.attributeChangedCallbackSugar(name, oldVal, newVal);
|
|
374
|
+
}
|
|
355
375
|
}
|
|
356
376
|
|
|
357
377
|
/**
|
|
@@ -491,5 +511,19 @@ export class HTMLElementSugar extends HTMLElement {
|
|
|
491
511
|
this._morphElement(this, frag);
|
|
492
512
|
}
|
|
493
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Move named slot children into the rendered target.
|
|
516
|
+
*
|
|
517
|
+
* @param {string} name
|
|
518
|
+
* @param {Element} target
|
|
519
|
+
* @returns {void}
|
|
520
|
+
*/
|
|
521
|
+
moveSlot(name, target) {
|
|
522
|
+
for (const node of [...this.$$(`[slot="${name}"]`)]) {
|
|
523
|
+
node.removeAttribute('slot');
|
|
524
|
+
target.appendChild(node);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
494
528
|
}
|
|
495
529
|
|
package/lib/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"semantic",
|
|
36
36
|
"htmlelementsugar",
|
|
37
37
|
"html",
|
|
38
|
-
"authoring"
|
|
38
|
+
"authoring",
|
|
39
|
+
"light dom",
|
|
40
|
+
"progressive enhancement",
|
|
41
|
+
"design system"
|
|
39
42
|
],
|
|
40
43
|
"license": "MIT",
|
|
41
44
|
"name": "@skirbi/sugar",
|
|
@@ -53,5 +56,5 @@
|
|
|
53
56
|
},
|
|
54
57
|
"sideEffects": true,
|
|
55
58
|
"type": "module",
|
|
56
|
-
"version": "0.1.
|
|
59
|
+
"version": "0.1.7"
|
|
57
60
|
}
|