@jungherz-de/glasskit-elements 1.28.1 → 1.29.1
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/CHANGELOG.md +24 -1
- package/README.md +12 -5
- package/SKILL.md +11 -3
- package/dist/components/glk-segmented.js +57 -23
- package/dist/glasskit-elements.esm.js +59 -25
- package/dist/glasskit-elements.js +59 -25
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/forms/glk-segmented.js +57 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungherz-de/glasskit-elements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.1",
|
|
4
4
|
"description": "Vanilla JavaScript Web Components wrapping the GlassKit CSS glassmorphism library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://glasskit-elements.jungherz.com",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"vanilla-js"
|
|
50
50
|
],
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@jungherz-de/glasskit": ">=1.
|
|
52
|
+
"@jungherz-de/glasskit": ">=1.29.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@jungherz-de/glasskit": "^1.
|
|
55
|
+
"@jungherz-de/glasskit": "^1.29.1",
|
|
56
56
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
57
57
|
"@rollup/plugin-terser": "^0.4.0",
|
|
58
58
|
"rollup": "^4.0.0"
|
|
@@ -5,7 +5,11 @@ import { attachDroplet } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
|
5
5
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
6
6
|
// styles exactly that attribute. Came back from EhrenPfoten in 1.15.0.
|
|
7
7
|
//
|
|
8
|
-
// Attributes: options (JSON [{value,label,tone?,disabled?}]
|
|
8
|
+
// Attributes: options (JSON [{value,label,tone?,disabled?,badge?,badgeLabel?}]
|
|
9
|
+
// — badge since 1.29.0: a count after the label, GlassKit's
|
|
10
|
+
// .glass-segmented__badge, in the tone's colour where one is
|
|
11
|
+
// set; missing, empty or 0 shows none; badgeLabel words it for
|
|
12
|
+
// the name, "1 open"), value, full,
|
|
9
13
|
// overflow ("scroll" | "wrap": what happens when the options
|
|
10
14
|
// do not fit — without it the row stays one line and runs past
|
|
11
15
|
// the edge), label (aria-label of the group), name (form field),
|
|
@@ -27,7 +31,9 @@ import { attachDroplet } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
|
27
31
|
//
|
|
28
32
|
// Form-associated: a surrounding <form> receives name=value, reset restores
|
|
29
33
|
// the initial value. A value change only re-sets aria-pressed, never rebuilds,
|
|
30
|
-
// so focus stays on the button that was pressed.
|
|
34
|
+
// so focus stays on the button that was pressed. New options with the same
|
|
35
|
+
// values in the same order — a count that changed — fill the buttons in
|
|
36
|
+
// place (since 1.29.0), so the focus and the drop stay as well.
|
|
31
37
|
//
|
|
32
38
|
// overflow="scroll" keeps the chosen option in view: it is centred in the
|
|
33
39
|
// row after the first layout, on every value change and when the row's width
|
|
@@ -49,6 +55,13 @@ function parseList(json) {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/** The text of an option's count; empty when there is none to show. */
|
|
59
|
+
function badgeText(badge) {
|
|
60
|
+
if (badge == null || badge === false) return '';
|
|
61
|
+
const text = String(badge).trim();
|
|
62
|
+
return text === '0' ? '' : text;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
class GlkSegmented extends GlkFormElement {
|
|
53
66
|
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label', 'droplet', 'icon-only']; }
|
|
54
67
|
|
|
@@ -66,33 +79,54 @@ class GlkSegmented extends GlkFormElement {
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
_build() {
|
|
69
|
-
this.
|
|
82
|
+
const list = parseList(this.getAttribute('options'));
|
|
70
83
|
const iconOnly = this.iconOnly;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
// The label names the button; an icon font's text must not join it.
|
|
80
|
-
const icon = this.createElement('span', ['glass-segmented__icon'], { 'aria-hidden': 'true' });
|
|
81
|
-
icon.appendChild(this.createElement('slot', [], { name: `icon-${value}` }));
|
|
82
|
-
button.appendChild(icon);
|
|
83
|
-
if (iconOnly) {
|
|
84
|
-
button.setAttribute('aria-label', label);
|
|
85
|
-
button.title = label;
|
|
86
|
-
} else {
|
|
87
|
-
button.appendChild(document.createTextNode(label));
|
|
88
|
-
}
|
|
89
|
-
this._group.appendChild(button);
|
|
90
|
-
}
|
|
84
|
+
// The same values in the same order keep their buttons; anything else
|
|
85
|
+
// starts the row afresh.
|
|
86
|
+
const buttons = [...this._group.children];
|
|
87
|
+
const same = buttons.length === list.length
|
|
88
|
+
&& list.every((option, i) => buttons[i].dataset.value === String(option.value ?? ''));
|
|
89
|
+
if (!same) this._group.replaceChildren(...list.map(() => this.createElement('button', [], { type: 'button' })));
|
|
90
|
+
[...this._group.children].forEach((button, i) => this._fill(button, list[i], iconOnly));
|
|
91
91
|
this._syncIcons();
|
|
92
92
|
this.applyDisabled(this._actuallyDisabled);
|
|
93
93
|
this._syncValue();
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/** Gives an option's button its tone, icon box, label and count. */
|
|
97
|
+
_fill(button, option, iconOnly) {
|
|
98
|
+
const value = String(option.value ?? '');
|
|
99
|
+
const label = String(option.label ?? value);
|
|
100
|
+
const parts = [];
|
|
101
|
+
button.className = 'glass-segmented__item';
|
|
102
|
+
button.dataset.value = value;
|
|
103
|
+
if (TONES.includes(option.tone)) {
|
|
104
|
+
button.classList.add(`glass-segmented__item--${option.tone}`);
|
|
105
|
+
parts.push(this.createElement('span', ['glass-segmented__dot']));
|
|
106
|
+
}
|
|
107
|
+
// The label names the button; an icon font's text must not join it.
|
|
108
|
+
const icon = this.createElement('span', ['glass-segmented__icon'], { 'aria-hidden': 'true' });
|
|
109
|
+
icon.appendChild(this.createElement('slot', [], { name: `icon-${value}` }));
|
|
110
|
+
parts.push(icon);
|
|
111
|
+
if (!iconOnly) parts.push(document.createTextNode(label));
|
|
112
|
+
// The count after the label, or at the icon's corner in an icon-only row;
|
|
113
|
+
// the name says it in words — "Tickets, 1 open".
|
|
114
|
+
const badge = badgeText(option.badge);
|
|
115
|
+
let name = label;
|
|
116
|
+
if (badge) {
|
|
117
|
+
const pill = this.createElement('span', ['glass-segmented__badge']);
|
|
118
|
+
pill.textContent = badge;
|
|
119
|
+
if (iconOnly) icon.appendChild(pill);
|
|
120
|
+
else parts.push(pill);
|
|
121
|
+
name = `${label}, ${option.badgeLabel ? String(option.badgeLabel) : badge}`;
|
|
122
|
+
}
|
|
123
|
+
button.replaceChildren(...parts);
|
|
124
|
+
if (iconOnly || badge) button.setAttribute('aria-label', name);
|
|
125
|
+
else button.removeAttribute('aria-label');
|
|
126
|
+
if (iconOnly) button.title = name;
|
|
127
|
+
else button.removeAttribute('title');
|
|
128
|
+
}
|
|
129
|
+
|
|
96
130
|
/** An option shows its icon box only while an icon is slotted into it. */
|
|
97
131
|
_syncIcons() {
|
|
98
132
|
for (const slot of this._group.querySelectorAll('.glass-segmented__icon > slot')) {
|