@jungherz-de/glasskit-elements 1.28.0 → 1.29.0
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 +10 -2
- package/dist/components/glk-segmented.js +61 -24
- package/dist/glasskit-elements.esm.js +63 -26
- package/dist/glasskit-elements.js +63 -26
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/forms/glk-segmented.js +61 -24
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.29.0] – 2026-10-21
|
|
11
|
+
|
|
12
|
+
Versions stay aligned with GlassKit at 1.29.0, which this release is built against (peer dependency `>=1.29.0`).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **A count per option in `<glk-segmented>`: `badge` and `badgeLabel`.** In an admin area a customer record has a row of tabs — payments, subscriptions, invoices, tickets — and a tab should say that something waits there, and how much. `tone` puts a dot before the label, which says that, not how much, so the count went into the label ("Current (2)"), where it read as part of it and could be neither styled nor announced apart. An option is `{ value, label, tone?, disabled?, badge?, badgeLabel? }` now. `badge`, a number or a short text, shows as GlassKit 1.29.0's `.glass-segmented__badge` after the label, neutral or in the tone's colour; missing, empty or `0` shows none. The button's name says it with the label, "Tickets, 1", and `badgeLabel` words it, "Tickets, 1 open". With `icon-only` the badge sits at the icon's corner, as on `<glk-tab-item>`, and the button's `title` carries it as well. Measured in Chromium 153, WebKit 26.6 and Firefox 151: the badges stand after their labels, or 4px above and 8px beyond the icon in an icon-only row; the names read "Payments, 2", "Invoices, 12 open", "Alerts, 1 new", and "Mails" for a `0`; the drop covers the chosen option with its badge, and in a scrolling row the option stays centred; a tap on a badge chooses its option. ([#19](https://github.com/JUNGHERZ/GlassKit-Elements/issues/19))
|
|
17
|
+
- **New options with the same values in the same order fill the buttons in place.** Setting `options` rebuilt every button, so a keyboard user on the row lost the focus whenever a count changed, and the drop was handed a new element. Now the buttons stay and take their new label, tone, icon box and count; other values or another order rebuild the row as before. Measured in all three engines: after three counts changed, the six buttons are the same nodes, the focus stays on the focused option, the drop stays where it was, and no `glk-change` fires; in 1.28.1 the buttons were new and the focus was gone. ([#19](https://github.com/JUNGHERZ/GlassKit-Elements/issues/19))
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [1.28.1] – 2026-10-20
|
|
22
|
+
|
|
23
|
+
GlassKit is unchanged; the peer dependency stays `>=1.28.0`.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **A tap on a slotted icon chooses the `<glk-segmented>` option.** The row found the option's button with `event.target.closest('button[data-value]')`. A click on an icon from `slot="icon-<value>"` has that icon as its target, in the light DOM, where `closest()` walks the light-DOM ancestors and never meets the button in the shadow root. In an `icon-only` row, where the 18px icon covers the middle of every 34px option, most taps chose nothing. The button now comes from the event's composed path. Slotted SVGs, font icons and custom elements were affected alike; text labels were not, they live in the shadow root. Measured in Chromium 153, WebKit 26.6 and Firefox 151: a tap on the centre of a slotted SVG, a font icon or a custom element chooses its option and emits `glk-change` once — in 1.28.0 it chose nothing; the icon of a disabled option chooses nothing; label text, the keyboard and a drag of the drop work as before, and so does an icon with `pointer-events: none`, a workaround that can go now. ([#18](https://github.com/JUNGHERZ/GlassKit-Elements/issues/18))
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
10
31
|
## [1.28.0] – 2026-10-20
|
|
11
32
|
|
|
12
33
|
Versions stay aligned with GlassKit at 1.28.0, which this release is built against (peer dependency `>=1.28.0`).
|
|
@@ -925,7 +946,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
|
|
|
925
946
|
|
|
926
947
|
---
|
|
927
948
|
|
|
928
|
-
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.
|
|
949
|
+
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.29.0...HEAD
|
|
950
|
+
[1.29.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.29.0
|
|
951
|
+
[1.28.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.28.1
|
|
929
952
|
[1.28.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.28.0
|
|
930
953
|
[1.27.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.27.0
|
|
931
954
|
[1.26.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.26.0
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-44cc11?style=flat-square" alt="Vanilla JS"></a>
|
|
6
6
|
<a href="#"><img src="https://img.shields.io/badge/components-36-7ec8e3?style=flat-square" alt="36 Components"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
|
|
8
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.29.0-lightgrey?style=flat-square" alt="Changelog"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@jungherz-de/glasskit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fglasskit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
@@ -67,7 +67,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
67
67
|
|
|
68
68
|
```html
|
|
69
69
|
<!-- 1. GlassKit CSS -->
|
|
70
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
70
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.29/glasskit.min.css">
|
|
71
71
|
|
|
72
72
|
<!-- 2. GlassKit Elements -->
|
|
73
73
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
@@ -105,7 +105,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
105
105
|
<!DOCTYPE html>
|
|
106
106
|
<html data-theme="dark">
|
|
107
107
|
<head>
|
|
108
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
108
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.29/glasskit.min.css">
|
|
109
109
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
110
110
|
</head>
|
|
111
111
|
<body>
|
|
@@ -165,7 +165,7 @@ All form components support `name`, `value`, `disabled` and participate in nativ
|
|
|
165
165
|
| `<glk-checkbox>` | Checkbox | `label`, `checked`, `disabled`, `required` |
|
|
166
166
|
| `<glk-radio>` | Radio button | `label`, `name`, `value`, `checked`, `required` (per group) |
|
|
167
167
|
| `<glk-range>` | Range slider | `label`, `min`, `max`, `value`, `step` |
|
|
168
|
-
| `<glk-segmented>` | Small exclusive choice as one control, icons per option in `slot="icon-<value>"
|
|
168
|
+
| `<glk-segmented>` | Small exclusive choice as one control, icons per option in `slot="icon-<value>"`, a count per option (`badge`); form-associated | `options` (JSON), `value`, `full`, `overflow` (scroll, wrap), `droplet`, `icon-only`, `label`, `name` |
|
|
169
169
|
| `<glk-calendar>` | One month with a day to pick; keyboard navigation, `Intl` names; form-associated | `month`, `value`, `today`, `min`, `max`, `marks`, `locale`, `week-start`, `label`, `name` |
|
|
170
170
|
| `<glk-image-picker>` | One image with preview, resized on the client, EXIF rotation applied; not form-associated | `src`, `label`, `hint`, `round`, `max`, `type`, `quality`, `accept` |
|
|
171
171
|
|
|
@@ -257,7 +257,14 @@ Since 1.28.0 a tab item and each segmented option take icons that are not inline
|
|
|
257
257
|
|
|
258
258
|
`glk-tab-select` is cancelable and comes before `active` changes: `event.preventDefault()` keeps the current tab — for an item that opens a drawer or a link — and the bar, its drop and `glk-tab-change` stay put. `droplet` and `floating` are properties of `<glk-tab-bar>` as well. The action buttons of `<glk-modal>` follow attribute changes of their originals, so a confirm button that starts `disabled` unlocks when the page or a framework enables it.
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
Since 1.29.0 a segmented option also takes a count, `badge`, shown as a pill after its label or at the icon's corner, with `badgeLabel` for the words a screen reader says — `"Tickets, 1 open"`. New options with the same values fill the buttons in place, so a live count keeps the focus and the drop:
|
|
261
|
+
|
|
262
|
+
```html
|
|
263
|
+
<glk-segmented droplet overflow="scroll" label="Customer" value="invoices"
|
|
264
|
+
options='[{"value":"invoices","label":"Invoices","badge":2},{"value":"tickets","label":"Tickets","tone":"warning","badge":1,"badgeLabel":"1 open"}]'></glk-segmented>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
> **Requires GlassKit CSS >= 1.28.0** (1.29.0 for the counts). The icon colours come from GlassKit's icon boxes; its new tokens reach the elements by inheritance: `--gl-tab-bar-color`, `--gl-tab-bar-hover-color`, `--gl-tab-bar-label-size`, `--gl-tab-bar-z-index` for the tab bar, `--gl-segmented-active-bg`, `-shadow` and `-color` for the chosen option and its drop.
|
|
261
268
|
|
|
262
269
|
### Solid and tint surfaces
|
|
263
270
|
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: glasskit-elements
|
|
3
|
-
description: GlassKit Elements is a vanilla-JS Web Components library (v1.
|
|
3
|
+
description: GlassKit Elements is a vanilla-JS Web Components library (v1.29.0) wrapping GlassKit CSS v1.29.0. It provides 36 custom elements with the `glk-` prefix, Dark/Light mode with automatic theme sync, Shadow DOM encapsulation, and form-associated custom elements. Use this reference whenever generating HTML that uses `<glk-*>` tags to ensure correct attributes, slots, events, and composition.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# GlassKit Elements – AI Component Reference
|
|
@@ -1153,7 +1153,7 @@ A small, exclusive choice as one control (since 1.15.0) — traffic light, morni
|
|
|
1153
1153
|
|
|
1154
1154
|
| Attribute | Type | Description |
|
|
1155
1155
|
|---|---|---|
|
|
1156
|
-
| `options` | JSON | `[{ value, label, tone?, disabled? }]`; `tone` is `success` \| `warning` \| `error` and puts a dot in that colour before the label |
|
|
1156
|
+
| `options` | JSON | `[{ value, label, tone?, disabled?, badge?, badgeLabel? }]`; `tone` is `success` \| `warning` \| `error` and puts a dot in that colour before the label; `badge` is a count after the label (since 1.29.0), `badgeLabel` words it for the name |
|
|
1157
1157
|
| `value` | String | Value of the chosen option (reflected property) |
|
|
1158
1158
|
| `full` | Boolean | Buttons share the width |
|
|
1159
1159
|
| `overflow` | String | What happens when the options do not fit: `scroll` — one row that scrolls sideways, the chosen option kept in view; `wrap` — breaks into lines. Without it the row stays one line (since 1.17.0) |
|
|
@@ -1178,6 +1178,14 @@ Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`
|
|
|
1178
1178
|
|
|
1179
1179
|
GlassKit 1.28.0's tokens for the chosen option reach the element by inheritance: `--gl-segmented-active-bg`, `--gl-segmented-active-shadow` and `--gl-segmented-active-color` — for a dark brand, on which the light surface leaves a white label below 4.5:1. The drop takes the same surface.
|
|
1180
1180
|
|
|
1181
|
+
**Counts (since 1.29.0).** `badge` on an option shows how much waits behind it — open invoices, new tickets — as a pill after the label (GlassKit's `.glass-segmented__badge`, tabular figures), neutral without a tone and in the tone's colour with one. A number or a short text; missing, empty or `0` shows none. Use it instead of a count in the label: `"Tickets (1)"` reads as part of the label and can be neither styled nor announced apart. The button's name says both, `"Tickets, 1"`; `badgeLabel` words it, `"Tickets, 1 open"`. With `icon-only` the badge sits at the icon's corner, as on `<glk-tab-item>`, and the button's `title` carries it too. Setting `options` again with the same values in the same order fills the buttons in place, so a live count keeps the focus and the drop; other values rebuild the row.
|
|
1182
|
+
|
|
1183
|
+
```html
|
|
1184
|
+
<glk-segmented droplet overflow="scroll" label="Customer" value="invoices"
|
|
1185
|
+
options='[{"value":"payments","label":"Payments"},{"value":"invoices","label":"Invoices","badge":2},{"value":"tickets","label":"Tickets","tone":"warning","badge":1,"badgeLabel":"1 open"}]'>
|
|
1186
|
+
</glk-segmented>
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1181
1189
|
With `overflow="scroll"` the chosen option is centred in the row after the first layout, on every value change and when the row's width changes — the row scrolls, never the page. Seven areas on a phone:
|
|
1182
1190
|
|
|
1183
1191
|
```html
|
|
@@ -6,7 +6,11 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
|
6
6
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
7
7
|
// styles exactly that attribute. Came back from EhrenPfoten in 1.15.0.
|
|
8
8
|
//
|
|
9
|
-
// Attributes: options (JSON [{value,label,tone?,disabled?}]
|
|
9
|
+
// Attributes: options (JSON [{value,label,tone?,disabled?,badge?,badgeLabel?}]
|
|
10
|
+
// — badge since 1.29.0: a count after the label, GlassKit's
|
|
11
|
+
// .glass-segmented__badge, in the tone's colour where one is
|
|
12
|
+
// set; missing, empty or 0 shows none; badgeLabel words it for
|
|
13
|
+
// the name, "1 open"), value, full,
|
|
10
14
|
// overflow ("scroll" | "wrap": what happens when the options
|
|
11
15
|
// do not fit — without it the row stays one line and runs past
|
|
12
16
|
// the edge), label (aria-label of the group), name (form field),
|
|
@@ -28,7 +32,9 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
|
28
32
|
//
|
|
29
33
|
// Form-associated: a surrounding <form> receives name=value, reset restores
|
|
30
34
|
// the initial value. A value change only re-sets aria-pressed, never rebuilds,
|
|
31
|
-
// so focus stays on the button that was pressed.
|
|
35
|
+
// so focus stays on the button that was pressed. New options with the same
|
|
36
|
+
// values in the same order — a count that changed — fill the buttons in
|
|
37
|
+
// place (since 1.29.0), so the focus and the drop stay as well.
|
|
32
38
|
//
|
|
33
39
|
// overflow="scroll" keeps the chosen option in view: it is centred in the
|
|
34
40
|
// row after the first layout, on every value change and when the row's width
|
|
@@ -50,6 +56,13 @@ function parseList(json) {
|
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
/** The text of an option's count; empty when there is none to show. */
|
|
60
|
+
function badgeText(badge) {
|
|
61
|
+
if (badge == null || badge === false) return '';
|
|
62
|
+
const text = String(badge).trim();
|
|
63
|
+
return text === '0' ? '' : text;
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
class GlkSegmented extends GlkFormElement {
|
|
54
67
|
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label', 'droplet', 'icon-only']; }
|
|
55
68
|
|
|
@@ -67,33 +80,54 @@ class GlkSegmented extends GlkFormElement {
|
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
_build() {
|
|
70
|
-
this.
|
|
83
|
+
const list = parseList(this.getAttribute('options'));
|
|
71
84
|
const iconOnly = this.iconOnly;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
// The label names the button; an icon font's text must not join it.
|
|
81
|
-
const icon = this.createElement('span', ['glass-segmented__icon'], { 'aria-hidden': 'true' });
|
|
82
|
-
icon.appendChild(this.createElement('slot', [], { name: `icon-${value}` }));
|
|
83
|
-
button.appendChild(icon);
|
|
84
|
-
if (iconOnly) {
|
|
85
|
-
button.setAttribute('aria-label', label);
|
|
86
|
-
button.title = label;
|
|
87
|
-
} else {
|
|
88
|
-
button.appendChild(document.createTextNode(label));
|
|
89
|
-
}
|
|
90
|
-
this._group.appendChild(button);
|
|
91
|
-
}
|
|
85
|
+
// The same values in the same order keep their buttons; anything else
|
|
86
|
+
// starts the row afresh.
|
|
87
|
+
const buttons = [...this._group.children];
|
|
88
|
+
const same = buttons.length === list.length
|
|
89
|
+
&& list.every((option, i) => buttons[i].dataset.value === String(option.value ?? ''));
|
|
90
|
+
if (!same) this._group.replaceChildren(...list.map(() => this.createElement('button', [], { type: 'button' })));
|
|
91
|
+
[...this._group.children].forEach((button, i) => this._fill(button, list[i], iconOnly));
|
|
92
92
|
this._syncIcons();
|
|
93
93
|
this.applyDisabled(this._actuallyDisabled);
|
|
94
94
|
this._syncValue();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** Gives an option's button its tone, icon box, label and count. */
|
|
98
|
+
_fill(button, option, iconOnly) {
|
|
99
|
+
const value = String(option.value ?? '');
|
|
100
|
+
const label = String(option.label ?? value);
|
|
101
|
+
const parts = [];
|
|
102
|
+
button.className = 'glass-segmented__item';
|
|
103
|
+
button.dataset.value = value;
|
|
104
|
+
if (TONES.includes(option.tone)) {
|
|
105
|
+
button.classList.add(`glass-segmented__item--${option.tone}`);
|
|
106
|
+
parts.push(this.createElement('span', ['glass-segmented__dot']));
|
|
107
|
+
}
|
|
108
|
+
// The label names the button; an icon font's text must not join it.
|
|
109
|
+
const icon = this.createElement('span', ['glass-segmented__icon'], { 'aria-hidden': 'true' });
|
|
110
|
+
icon.appendChild(this.createElement('slot', [], { name: `icon-${value}` }));
|
|
111
|
+
parts.push(icon);
|
|
112
|
+
if (!iconOnly) parts.push(document.createTextNode(label));
|
|
113
|
+
// The count after the label, or at the icon's corner in an icon-only row;
|
|
114
|
+
// the name says it in words — "Tickets, 1 open".
|
|
115
|
+
const badge = badgeText(option.badge);
|
|
116
|
+
let name = label;
|
|
117
|
+
if (badge) {
|
|
118
|
+
const pill = this.createElement('span', ['glass-segmented__badge']);
|
|
119
|
+
pill.textContent = badge;
|
|
120
|
+
if (iconOnly) icon.appendChild(pill);
|
|
121
|
+
else parts.push(pill);
|
|
122
|
+
name = `${label}, ${option.badgeLabel ? String(option.badgeLabel) : badge}`;
|
|
123
|
+
}
|
|
124
|
+
button.replaceChildren(...parts);
|
|
125
|
+
if (iconOnly || badge) button.setAttribute('aria-label', name);
|
|
126
|
+
else button.removeAttribute('aria-label');
|
|
127
|
+
if (iconOnly) button.title = name;
|
|
128
|
+
else button.removeAttribute('title');
|
|
129
|
+
}
|
|
130
|
+
|
|
97
131
|
/** An option shows its icon box only while an icon is slotted into it. */
|
|
98
132
|
_syncIcons() {
|
|
99
133
|
for (const slot of this._group.querySelectorAll('.glass-segmented__icon > slot')) {
|
|
@@ -141,7 +175,10 @@ class GlkSegmented extends GlkFormElement {
|
|
|
141
175
|
|
|
142
176
|
setupEvents() {
|
|
143
177
|
this._onClick = (event) => {
|
|
144
|
-
|
|
178
|
+
// The option's button from the event's path. A click on a slotted icon
|
|
179
|
+
// has that icon as its target, in the light DOM, where closest() never
|
|
180
|
+
// meets the button — until 1.28.1 such a tap chose nothing.
|
|
181
|
+
const button = event.composedPath().find(node => node.parentNode === this._group);
|
|
145
182
|
if (!button || button.disabled) return;
|
|
146
183
|
const { value } = button.dataset;
|
|
147
184
|
if (value === this.value) return;
|