@jungherz-de/glasskit-elements 1.26.0 → 1.28.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 +47 -1
- package/README.md +49 -6
- package/SKILL.md +72 -10
- package/dist/components/base.js +9 -3
- package/dist/components/glk-modal.js +37 -1
- package/dist/components/glk-segmented.js +58 -4
- package/dist/components/glk-tab-bar.js +81 -14
- package/dist/components/glk-tab-item.js +42 -3
- package/dist/components/shared/glasskit-droplet-3vJVXB3u.js +237 -0
- package/dist/glasskit-elements.esm.js +464 -28
- package/dist/glasskit-elements.js +464 -28
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/base.js +9 -3
- package/src/components/feedback/glk-modal.js +37 -1
- package/src/components/forms/glk-segmented.js +58 -4
- package/src/components/navigation/glk-tab-bar.js +81 -14
- package/src/components/navigation/glk-tab-item.js +42 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.28.0] – 2026-10-20
|
|
11
|
+
|
|
12
|
+
Versions stay aligned with GlassKit at 1.28.0, which this release is built against (peer dependency `>=1.28.0`).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Icons that are not inline SVG in `<glk-tab-item>`: `slot="icon"`.** The item copied the first `<svg>` of its light DOM into its shadow root and showed nothing else, so an app whose tab icons come from Material Icons, a ligature font, or from an element of its own could not use them. Anything slotted as `icon` now sits in GlassKit's `.glass-tab-bar__icon`, before the badge, and takes the state colour from it (GlassKit 1.28.0): inactive, hover, active, the lens of the drop and the drag; on a bar without droplet the active icon carries the glow. An `<svg>` without `slot` is copied as before; with a slotted icon nothing is copied. ([#17](https://github.com/JUNGHERZ/GlassKit-Elements/issues/17))
|
|
17
|
+
- **Icons per option in `<glk-segmented>`: `slot="icon-<value>"`, and `icon-only`.** The options were text only. Each option now takes an icon from the slot named after its value — an `<svg>`, a font icon, an `<img>` — in GlassKit's `.glass-segmented__icon` before the label; an option without one has no icon box, and an icon added or removed later shows or hides it. The icon box is `aria-hidden`, so a font icon's text never joins the name. `icon-only` (property `iconOnly`) shows the icons alone, for a light / dark / auto switch: the row takes `.glass-segmented--icons`, and each label becomes the button's `aria-label` and `title`. ([#17](https://github.com/JUNGHERZ/GlassKit-Elements/issues/17))
|
|
18
|
+
- **`droplet` and `floating` are properties of `<glk-tab-bar>`.** The README called `droplet` "also a property", but `GlkTabBar` had no accessor, so `bar.droplet = true` set an own property and changed nothing; `floating` had the same gap. ([#17](https://github.com/JUNGHERZ/GlassKit-Elements/issues/17))
|
|
19
|
+
- **GlassKit 1.28.0's tokens reach the elements by inheritance:** `--gl-tab-bar-color`, `--gl-tab-bar-hover-color`, `--gl-tab-bar-label-size` and `--gl-tab-bar-z-index` for `<glk-tab-item>`, `<glk-tab-bar>` and `<glk-tab-dock>`; `--gl-segmented-active-bg`, `-shadow` and `-color` for the chosen option of `<glk-segmented>` and its drop. ([GlassKit #17](https://github.com/JUNGHERZ/GlassKit/issues/17), [GlassKit #14](https://github.com/JUNGHERZ/GlassKit/issues/14))
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`glk-tab-select` comes before `active` changes, and it is cancelable.** A bar item that opens a side drawer or an external link must not become active, but the item set `active` before it emitted `glk-tab-select`, and the bar, listening on its way up, removed `active` from the others and emitted `glk-tab-change` before a listener on the document had even seen the event: the page could only put `active` back, and on a droplet bar the drop ran to the item and back. Now `event.preventDefault()` keeps the current tab — no `active`, no `glk-tab-change`, and the drop stays. A drag that ends on such an item takes the same path, because the drop clicks the item, and the drop goes back. Uncancelled, the item becomes active and the bar emits `glk-tab-change` after `glk-tab-select`; in 1.27.0 a listener on the document received `glk-tab-change` first. ([#17](https://github.com/JUNGHERZ/GlassKit-Elements/issues/17))
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **The action buttons of `<glk-modal>` follow attribute changes.** The actions are copies of the buttons in `slot="actions"`, kept in step by the light-DOM observer — which watched content but not attributes. A confirm button that starts `disabled` stayed disabled in its copy after the page enabled it, a disabled copy forwards no click, and with a framework that binds only `disabled` (hybrids, lit, Vue) the dialog could never be confirmed. The observer of every element that copies light-DOM children now watches attributes too, so the copies of `<glk-select>`'s options and `<glk-tab-item>`'s SVG follow them as well. With as many actions as before, the copies change in place, so the one that has the focus keeps it while it is relabelled or disabled. Measured in Chromium 153, WebKit 26.6 and Firefox 151 with the issue's dialog: after typing DELETE the copy of Delete is enabled (in 1.27.0 it stayed disabled) and a click on it runs the original once; relabelled and marked `aria-busy` while focused, the copy stays the same node and keeps the focus; an option of `<glk-select>` disabled after connecting is disabled in the copy. ([#16](https://github.com/JUNGHERZ/GlassKit-Elements/issues/16))
|
|
28
|
+
- **An inline `onclick` on a modal action runs once.** The copy carried the original's `onclick` attribute and ran it, then handed the click to the original, which ran it again: the issue's `alert('deleted')` would have shown twice. The copies carry no inline handlers now. Found while fixing the above. ([#16](https://github.com/JUNGHERZ/GlassKit-Elements/issues/16))
|
|
29
|
+
- **From GlassKit 1.28.0:** under a scaled ancestor the drop of `<glk-tab-bar droplet>` and `<glk-segmented droplet>` lands on the chosen item, at rest, while the transform runs and during a drag ([GlassKit #16](https://github.com/JUNGHERZ/GlassKit/issues/16)); the items of the fixed `<glk-tab-bar>` sit in the middle without a home indicator ([GlassKit #17](https://github.com/JUNGHERZ/GlassKit/issues/17)); seven steps in `<glk-steps>` fit a narrow column, so a `::part(steps)` gap workaround can go ([GlassKit #15](https://github.com/JUNGHERZ/GlassKit/issues/15)).
|
|
30
|
+
|
|
31
|
+
### Compatibility
|
|
32
|
+
|
|
33
|
+
- A listener on `glk-tab-select` that read the item's `active` there sees the previous state now; `glk-tab-change` still comes after the change. The bar no longer listens for `glk-tab-select`: the item calls it once the event went through uncancelled, so a `glk-tab-select` dispatched by a page switches no tab.
|
|
34
|
+
|
|
35
|
+
Measured in Chromium 153, WebKit 26.6 and Firefox 151: a slotted text icon, a custom element and a slotted SVG take the inactive colour, the hover colour, the primary when active and while the dragged drop is over them, and no SVG is copied next to them; `glk-tab-select` cancelled from a listener on the document leaves `active`, the drop and `glk-tab-change` alone on a tap and on a drag, where the drop returns to the current tab; segmented icons are 18 × 18 px, icon-only options 34 × 34 px with `aria-label` and `title`, the icon under a dragged drop scales by 1.15; `bar.droplet = false` removes the attribute, the class and the drop, `true` brings them back; the new GlassKit tokens arrive in every shadow root. Docs, showcase and landing pages render pixel-identical to 1.27.0, except the fixed tab bar of the docs, whose items moved to the middle, and the narrow steps, whose lines reach 2px closer to their circles.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## [1.27.0] – 2026-10-18
|
|
40
|
+
|
|
41
|
+
Versions stay aligned with GlassKit at 1.27.0, which this release is built against (peer dependency `>=1.27.0`).
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`droplet` on `<glk-tab-bar>` and `<glk-segmented>`: a fluid selection.** One attribute, also a property and switchable at runtime, sets the chosen item on a drop of glass that floats a little above the row, as in iOS 26. A tap sends it to the new item like water on a pane: the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, and the icons or the text it passes grow or light up as under a lens. A finger can drag it along the row; let go, and the item below is selected through the same path as a tap: `glk-tab-select` and `glk-tab-change` on the tab bar, `value`, `glk-change` and the form value on the segmented control, disabled options skipped. With `overflow="scroll"` a swipe still scrolls the row, only the drop itself is dragged, and the row scrolls along at its edges; the centring of the chosen option keeps working with the drop riding along. It is GlassKit 1.27.0's `--droplet`, moved by its `glasskit-droplet.js`. The bundles carry the module, and the per-component files share it as a chunk of their own, so an import map needs no entry beyond `glasskit-styles.js`. Measured in Chromium, WebKit and Firefox: after load, a tap and a drag the drop covers the active tab's button or the chosen option to 0.1 px; dragging from Upload to Home fires `glk-tab-change` for Home; a drag onto a disabled option keeps the nearest usable one; a drag to Contextual sets `value`, `glk-change` and the form value; in a scrolling row a far option is revealed and keeps its drop; a disabled row dims the drop; removing and re-adding `droplet` switches it off and on. ([GlassKit #13](https://github.com/JUNGHERZ/GlassKit/issues/13))
|
|
46
|
+
- **GlassKit's tokens for the active tab reach `<glk-tab-item>`:** `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` for a capsule behind it, `--gl-tab-bar-spotlight-opacity` and `--gl-tab-bar-active-glow`. A capsule no longer needs a sheet of its own through `hostStyles`. Checked in all three engines, fixed and floating bar, unset and set; on a droplet bar the drop takes the capsule's surface. ([GlassKit #13](https://github.com/JUNGHERZ/GlassKit/issues/13))
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **The items of `<glk-tab-bar floating>` take the floating look.** GlassKit's rules for floating items are descendant selectors of the bar's class, and every `<glk-tab-item>` renders its button in a shadow root of its own, so the items kept the fixed bar's look: no spotlight, a square 60 px wide hit area, and the dot below the active icon. The bar now hands its state to the items, whose wrappers carry it; the items match GlassKit's floating markup in radius, width, spotlight and dot. On the docs, showcase and landing pages as they were in 1.26.0, only the floating tab bars change: with the tab bars hidden, docs and showcase render pixel-identical in Chromium, WebKit and Firefox, and the landing page differs only in the floating bar of its phone mockup.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
10
54
|
## [1.26.0] – 2026-10-18
|
|
11
55
|
|
|
12
56
|
Versions stay aligned with GlassKit at 1.26.0, which this release is built against (peer dependency `>=1.26.0`).
|
|
@@ -881,7 +925,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
|
|
|
881
925
|
|
|
882
926
|
---
|
|
883
927
|
|
|
884
|
-
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.
|
|
928
|
+
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.28.0...HEAD
|
|
929
|
+
[1.28.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.28.0
|
|
930
|
+
[1.27.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.27.0
|
|
885
931
|
[1.26.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.26.0
|
|
886
932
|
[1.25.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.25.0
|
|
887
933
|
[1.24.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.24.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.28.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
|
|
|
@@ -56,6 +56,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
56
56
|
| 📦 **Three bundle formats** | IIFE for `<script>`, minified IIFE for production, ESM for bundlers & tree-shaking |
|
|
57
57
|
| 🎛️ **Form Participation** | Input, toggle, checkbox, radio, select — all work natively with `<form>` via `ElementInternals`: value, reset and, since 1.20.0, validity; `<glk-button type="submit">` submits |
|
|
58
58
|
| 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html>`, compact controls via `data-density`, opaque surfaces via `data-surface` |
|
|
59
|
+
| 💧 **Fluid selection** | `droplet` on `<glk-tab-bar>` and `<glk-segmented>`: the chosen item rides a drop of glass that glides to the next one and can be dragged (since 1.27.0) |
|
|
59
60
|
| 📱 **Mobile-first** | Inherits GlassKit's mobile-optimized design with `safe-area-inset` support |
|
|
60
61
|
|
|
61
62
|
---
|
|
@@ -66,7 +67,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
66
67
|
|
|
67
68
|
```html
|
|
68
69
|
<!-- 1. GlassKit CSS -->
|
|
69
|
-
<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.28/glasskit.min.css">
|
|
70
71
|
|
|
71
72
|
<!-- 2. GlassKit Elements -->
|
|
72
73
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
@@ -104,7 +105,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
104
105
|
<!DOCTYPE html>
|
|
105
106
|
<html data-theme="dark">
|
|
106
107
|
<head>
|
|
107
|
-
<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.28/glasskit.min.css">
|
|
108
109
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
109
110
|
</head>
|
|
110
111
|
<body>
|
|
@@ -125,8 +126,8 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
125
126
|
|---|---|---|
|
|
126
127
|
| `<glk-nav>` | Horizontal navigation bar | — |
|
|
127
128
|
| `<glk-pill>` | Circular icon button (46×46px) | `label`, `disabled` |
|
|
128
|
-
| `<glk-tab-bar>` | Bottom tab bar navigation | `static`, `floating` |
|
|
129
|
-
| `<glk-tab-item>` | Tab bar item | `label`, `active`, `badge` |
|
|
129
|
+
| `<glk-tab-bar>` | Bottom tab bar navigation | `static`, `floating`, `droplet` |
|
|
130
|
+
| `<glk-tab-item>` | Tab bar item; any icon in `slot="icon"`, a cancelable `glk-tab-select` | `label`, `active`, `badge` |
|
|
130
131
|
| `<glk-tab-dock>` | Wrapper for floating tab bar + optional accessory | `accessory-left` |
|
|
131
132
|
| `<glk-tab-accessory>` | Standalone glass capsule (search, compose…) | `label`, `variant` (accent, success, error), `disabled` |
|
|
132
133
|
| `<glk-steps>` | Progress through a short flow | `steps`, `current`, `label` |
|
|
@@ -164,7 +165,7 @@ All form components support `name`, `value`, `disabled` and participate in nativ
|
|
|
164
165
|
| `<glk-checkbox>` | Checkbox | `label`, `checked`, `disabled`, `required` |
|
|
165
166
|
| `<glk-radio>` | Radio button | `label`, `name`, `value`, `checked`, `required` (per group) |
|
|
166
167
|
| `<glk-range>` | Range slider | `label`, `min`, `max`, `value`, `step` |
|
|
167
|
-
| `<glk-segmented>` | Small exclusive choice as one control
|
|
168
|
+
| `<glk-segmented>` | Small exclusive choice as one control, icons per option in `slot="icon-<value>"`; form-associated | `options` (JSON), `value`, `full`, `overflow` (scroll, wrap), `droplet`, `icon-only`, `label`, `name` |
|
|
168
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` |
|
|
169
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` |
|
|
170
171
|
|
|
@@ -216,6 +217,48 @@ Fields and buttons become 40px high instead of 52 and 56, toggles, checkboxes an
|
|
|
216
217
|
|
|
217
218
|
> **Requires GlassKit CSS >= 1.21.0.** The sizes are GlassKit's density tokens, so they reach every shadow root by inheritance, like the brand colors below — and so do your own: `[data-density='compact'] { --gl-btn-height: 36px; }`. With `data-glk-defaults="off"` the preset comes from `glasskit.css` on the page, like every other token. The global observer mirrors `data-density` onto each element's theme wrapper as well, so a rule keyed on the attribute matches inside the shadow root too.
|
|
218
219
|
|
|
220
|
+
### Droplet: a fluid selection
|
|
221
|
+
|
|
222
|
+
Since 1.27.0 one attribute makes the selection of a tab bar or a segmented control fluid, as in iOS 26:
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<glk-tab-dock>
|
|
226
|
+
<glk-tab-bar floating droplet>
|
|
227
|
+
<glk-tab-item label="Home" active>…</glk-tab-item>
|
|
228
|
+
<glk-tab-item label="Search">…</glk-tab-item>
|
|
229
|
+
</glk-tab-bar>
|
|
230
|
+
</glk-tab-dock>
|
|
231
|
+
|
|
232
|
+
<glk-segmented droplet overflow="scroll" value="general" options='[…]'></glk-segmented>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The chosen item sits on a drop of glass that floats a little above the row. A tap sends it to the new item like water on a pane: the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, and what it passes grows or lights up as under a lens. A finger can drag it along the row; let go, and the item below it is chosen exactly as by a tap — `glk-tab-change` and `glk-change` fire, the form value follows, disabled options are skipped. With `overflow="scroll"` a swipe still scrolls the row: only the drop itself is dragged, and the row scrolls along at its edges. `droplet` is also a property and may be switched at runtime.
|
|
236
|
+
|
|
237
|
+
**Already using the elements?** Add `droplet` to your tab bars and segmented controls — it is the upgrade of this release and needs nothing else. It is GlassKit's `--droplet` and its module `glasskit-droplet.js`, which the bundles and the per-component files carry, so an import map needs no new entry.
|
|
238
|
+
|
|
239
|
+
> **Requires GlassKit CSS >= 1.27.0.** The tokens for a capsule behind the active tab (`--gl-tab-bar-active-bg`, `--gl-tab-bar-active-shadow`, `--gl-tab-bar-spotlight-opacity`, `--gl-tab-bar-active-glow`) reach `<glk-tab-item>` by inheritance; the drop takes the same surface. Since 1.27.0 the items of `<glk-tab-bar floating>` also take the floating look — spotlight and pill instead of the fixed bar's dot.
|
|
240
|
+
|
|
241
|
+
### Icons of any kind, and a tab that keeps the current one
|
|
242
|
+
|
|
243
|
+
Since 1.28.0 a tab item and each segmented option take icons that are not inline SVG — Material Icons, an `<img>`, an element of your own — and they follow every state, the drop's lens and the drag:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<glk-tab-item label="Home" active>
|
|
247
|
+
<span slot="icon" class="material-icons" aria-hidden="true">home</span>
|
|
248
|
+
</glk-tab-item>
|
|
249
|
+
|
|
250
|
+
<glk-segmented droplet icon-only label="Theme" value="dark"
|
|
251
|
+
options='[{"value":"light","label":"Light"},{"value":"dark","label":"Dark"},{"value":"auto","label":"Auto"}]'>
|
|
252
|
+
<svg slot="icon-light" viewBox="0 0 24 24">…</svg>
|
|
253
|
+
<span slot="icon-dark" class="material-icons">dark_mode</span>
|
|
254
|
+
<svg slot="icon-auto" viewBox="0 0 24 24">…</svg>
|
|
255
|
+
</glk-segmented>
|
|
256
|
+
```
|
|
257
|
+
|
|
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
|
+
|
|
260
|
+
> **Requires GlassKit CSS >= 1.28.0.** 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
|
+
|
|
219
262
|
### Solid and tint surfaces
|
|
220
263
|
|
|
221
264
|
Not every screen wants glass. Set `data-surface="solid"` on `<html>`, next to `data-theme` (since 1.24.0):
|
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.28.0) wrapping GlassKit CSS v1.28.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
|
|
@@ -8,6 +8,10 @@ description: GlassKit Elements is a vanilla-JS Web Components library (v1.26.0)
|
|
|
8
8
|
> **Purpose:** AI-optimized reference for generating correct `<glk-*>` markup.
|
|
9
9
|
> Companion to the class-based `SKILL.md` in [`@jungherz-de/glasskit`](https://github.com/JUNGHERZ/GlassKit) — the element wrappers delegate visuals and tokens to GlassKit CSS, so both references are best used together.
|
|
10
10
|
|
|
11
|
+
> **New in 1.27.0 — recommend `droplet`.** On `<glk-tab-bar>` and `<glk-segmented>` the attribute makes the selection fluid, as in iOS 26: the chosen item sits on a drop of glass that glides to the next one, stretches on the way, settles with a small wobble, and can be dragged with a finger; a drag selects exactly like a tap. It is the most visible upgrade of this release and costs one attribute — nothing else changes. **When you update a project to 1.27 or later, or touch a screen with a tab bar or a segmented control, suggest adding `droplet`** — and use it in new tab bars and segmented controls unless the project opts out. Details in 3.3 and 3.25.
|
|
12
|
+
|
|
13
|
+
> **New in 1.28.0 — icons of any kind, and a tab that keeps the current one.** `<glk-tab-item>` takes an icon that is not inline SVG in `slot="icon"` — Material Icons, an `<img>`, a custom element — and it follows every state; `<glk-segmented>` takes one per option in `slot="icon-<value>"` and shows them alone with `icon-only`. `glk-tab-select` is cancelable, for an item that opens a drawer or a link. **Where a project styles tab icons through `hostStyles`, or puts `active` back after `glk-tab-select`, replace that** (3.3, 3.25).
|
|
14
|
+
|
|
11
15
|
---
|
|
12
16
|
|
|
13
17
|
## 1. Setup & Boilerplate
|
|
@@ -176,6 +180,8 @@ Those copies are now kept in step by a `MutationObserver`, so swapping the child
|
|
|
176
180
|
— by hand or through a framework — updates the rendered element. Before 1.12.0 the
|
|
177
181
|
copy was made once on the first frame and then silently went stale.
|
|
178
182
|
|
|
183
|
+
Since 1.28.0 the observer also watches the children's attributes. A framework that binds only `disabled` (hybrids, lit, Vue) used to leave the copy behind: a modal action that started disabled stayed disabled in its copy and never forwarded the click. `<glk-tab-item>` has `slot="icon"` since 1.28.0 for icons that are not inline SVG; with a slotted icon nothing is copied.
|
|
184
|
+
|
|
179
185
|
If you ever need to force it, `element.refresh()` re-runs the copy immediately.
|
|
180
186
|
There should be no reason to reach into `element.shadowRoot`.
|
|
181
187
|
|
|
@@ -363,16 +369,34 @@ Fixed bottom navigation. Requires `.glass-bg--has-tab-bar` on the outer backgrou
|
|
|
363
369
|
|---|---|---|
|
|
364
370
|
| `static` | Boolean | Switches from `position: fixed` to `position: relative` (useful for docs / previews) |
|
|
365
371
|
| `floating` | Boolean | Pill-shaped Liquid Glass variant — adds `.glass-tab-bar--floating`. Use inside `<glk-tab-dock>` |
|
|
372
|
+
| `droplet` | Boolean | The active item sits on a drop of glass that glides to the next one and can be dragged (since 1.27.0) — recommended; also a property, like `floating` (since 1.28.0: before, `bar.droplet = true` set an own property and changed nothing) |
|
|
366
373
|
|
|
367
374
|
**`<glk-tab-item>`**
|
|
368
375
|
|
|
369
376
|
| Attribute | Type | Description |
|
|
370
377
|
|---|---|---|
|
|
371
378
|
| `label` | String | Tab label |
|
|
372
|
-
| `active` | Boolean | Active state — gets a soft radial Spotlight halo when inside a `floating` bar |
|
|
379
|
+
| `active` | Boolean | Active state — gets a soft radial Spotlight halo when inside a `floating` bar (since 1.27.0 really: before, the items of a floating bar kept the fixed bar's look), the drop when inside a `droplet` bar |
|
|
373
380
|
| `badge` | String / Number | Numeric badge on the icon |
|
|
374
381
|
|
|
375
|
-
|
|
382
|
+
Icon: an `<svg>` child is copied into the shadow root. Anything else — a font icon such as Material Icons, an `<img>`, a custom element — goes into `slot="icon"` (since 1.28.0) and takes the state colour from GlassKit's icon box: inactive, hover, active, the lens of the drop and the drag. Give a font icon `aria-hidden="true"`, or its text becomes part of the tab's name. With a slotted icon no SVG is copied.
|
|
383
|
+
|
|
384
|
+
```html
|
|
385
|
+
<glk-tab-item label="Home" active>
|
|
386
|
+
<span slot="icon" class="material-icons" aria-hidden="true">home</span>
|
|
387
|
+
</glk-tab-item>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Events: `glk-tab-select` on the item, then `glk-tab-change` `{ tab }` on the bar. **`glk-tab-select` is cancelable (since 1.28.0)** and comes before `active` changes: `event.preventDefault()` keeps the current tab — for an item that opens a drawer or a link — so `active` stays, no `glk-tab-change` follows and the drop does not move. A drag of the drop that ends on such an item takes the same path, and the drop goes back. Before 1.28.0 the item was active already when the event arrived, and the bar had moved `active` and emitted `glk-tab-change` before a listener on the document could react.
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
menuItem.addEventListener('glk-tab-select', (event) => {
|
|
394
|
+
event.preventDefault(); // stays on the current tab
|
|
395
|
+
openDrawer();
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
GlassKit 1.28.0's tab bar tokens reach the items by inheritance: `--gl-tab-bar-color` (inactive icon and label), `--gl-tab-bar-hover-color`, `--gl-tab-bar-label-size`, `--gl-tab-bar-z-index` (the fixed bar and `<glk-tab-dock>`).
|
|
376
400
|
|
|
377
401
|
#### Floating variant + Accessory (`<glk-tab-dock>` + `<glk-tab-accessory>`)
|
|
378
402
|
|
|
@@ -414,7 +438,22 @@ iOS 26 Liquid Glass-style floating tab bar. Wrap a `<glk-tab-bar floating>` in a
|
|
|
414
438
|
| `variant` | String | `accent`, `success`, `error` — filled colored capsule with white icon |
|
|
415
439
|
| `disabled` | Boolean | Disabled state |
|
|
416
440
|
|
|
417
|
-
Default slot: icon SVG (22×22). Events: `glk-click` (suppressed when disabled).
|
|
441
|
+
Default slot: icon SVG (22×22), or a font icon, which follows hover and the variants (GlassKit 1.28.0). Events: `glk-click` (suppressed when disabled).
|
|
442
|
+
|
|
443
|
+
#### Droplet (since 1.27.0) — recommended
|
|
444
|
+
|
|
445
|
+
```html
|
|
446
|
+
<glk-tab-dock>
|
|
447
|
+
<glk-tab-bar floating droplet>
|
|
448
|
+
<glk-tab-item label="Home" active>…</glk-tab-item>
|
|
449
|
+
<glk-tab-item label="Docs">…</glk-tab-item>
|
|
450
|
+
</glk-tab-bar>
|
|
451
|
+
</glk-tab-dock>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
The active item sits on a drop of glass that floats a little above the bar. A tap sends it to the new tab like water on a pane — the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, the icons it passes grow as under a lens. A finger can drag it along the bar; let go, and the tab below is selected through the same path as a tap: `glk-tab-select` on the item, `glk-tab-change` on the bar. It is GlassKit's `.glass-tab-bar--droplet`, moved by GlassKit's `glasskit-droplet.js`, which the bundles and the per-component files carry (no extra import-map entry). The bar hands its state to the items' shadow roots. Spotlight, dot and glow are off on a droplet bar; `prefers-reduced-motion` makes the drop jump; solid and tint surfaces make it opaque. Works on the fixed bar too, but suits the floating one.
|
|
455
|
+
|
|
456
|
+
**Active tab surface (GlassKit 1.27.0).** `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` put a capsule behind the active item (the drop takes the same surface), `--gl-tab-bar-spotlight-opacity: 0` turns the spotlight off, `--gl-tab-bar-active-glow: none` the icon's glow. All unset, inherited into `<glk-tab-item>` — no `hostStyles` sheet needed for a capsule any more.
|
|
418
457
|
|
|
419
458
|
---
|
|
420
459
|
|
|
@@ -886,6 +925,8 @@ Closes automatically on overlay click and <kbd>Escape</kbd>, with its fade, and
|
|
|
886
925
|
|
|
887
926
|
While it is open, **everything outside is inert** — a `<glk-toast>` shown then is neither clickable nor announced. Give feedback inside the modal, or close it first.
|
|
888
927
|
|
|
928
|
+
**The actions are copies** of the `<button>`s in `slot="actions"`; a click on one is handed to its original. Since 1.28.0 they follow every change of the originals, attributes included: a confirm button that starts `disabled` unlocks once the page or a framework enables it — no `refresh()` needed. With the same number of actions the copies change in place, so the focused one keeps the focus while it is relabelled or disabled. An inline `onclick` runs once per click, on the original; until 1.28.0 the copy carried it too and it ran twice.
|
|
929
|
+
|
|
889
930
|
---
|
|
890
931
|
|
|
891
932
|
### 3.20 `<glk-popover>`
|
|
@@ -1119,8 +1160,23 @@ A small, exclusive choice as one control (since 1.15.0) — traffic light, morni
|
|
|
1119
1160
|
| `label` | String | `aria-label` of the group (no default — set it) |
|
|
1120
1161
|
| `name` | String | Form field name |
|
|
1121
1162
|
| `disabled` | Boolean | The whole row (since 1.22.3); a `<fieldset disabled>` around it disables it too |
|
|
1163
|
+
| `droplet` | Boolean | The chosen option sits on a drop of glass that glides to the next one and can be dragged (since 1.27.0) — recommended |
|
|
1164
|
+
| `icon-only` | Boolean | The options show their icons alone, square; each label becomes the button's `aria-label` and `title` (since 1.28.0) |
|
|
1165
|
+
|
|
1166
|
+
Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`, `disabled`, `droplet`, `iconOnly`. Slots: `icon-<value>` (since 1.28.0). Events: `glk-change` `{ value }` — only on a change made by the user, not on `.value = …`. Part: `group`.
|
|
1122
1167
|
|
|
1123
|
-
|
|
1168
|
+
**Icons (since 1.28.0).** Each option takes its icon from the slot `icon-<value>` — an `<svg>`, a font icon, an `<img>`. It sits in GlassKit's `.glass-segmented__icon` before the label, in the option's colour, and grows under the drop; an option without one has no icon box, and an icon added or removed later shows or hides it. The icon box is `aria-hidden`, so a font icon's text never joins the name. `icon-only` is for a row of icons alone — a light / dark / auto switch:
|
|
1169
|
+
|
|
1170
|
+
```html
|
|
1171
|
+
<glk-segmented droplet icon-only label="Theme" value="dark"
|
|
1172
|
+
options='[{"value":"light","label":"Light"},{"value":"dark","label":"Dark"},{"value":"auto","label":"Auto"}]'>
|
|
1173
|
+
<svg slot="icon-light" viewBox="0 0 24 24"><!-- sun --></svg>
|
|
1174
|
+
<span slot="icon-dark" class="material-icons">dark_mode</span>
|
|
1175
|
+
<svg slot="icon-auto" viewBox="0 0 24 24"><!-- screen --></svg>
|
|
1176
|
+
</glk-segmented>
|
|
1177
|
+
```
|
|
1178
|
+
|
|
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.
|
|
1124
1180
|
|
|
1125
1181
|
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:
|
|
1126
1182
|
|
|
@@ -1131,11 +1187,17 @@ With `overflow="scroll"` the chosen option is centred in the row after the first
|
|
|
1131
1187
|
|
|
1132
1188
|
The attribute is `overflow`, not `scroll`: `scroll` is a method of every element, and a framework that sets a property whenever the element has one (hybrids, Vue, React 19) would overwrite that method instead of setting the attribute.
|
|
1133
1189
|
|
|
1190
|
+
**Droplet (since 1.27.0) — recommended.** `<glk-segmented droplet>` gives the chosen option the drop of the tab bar: it runs to a new option, stretches on the way and settles, the text it passes lights up, and a finger can drag it. A drag that ends on an option selects it like a tap — `value`, `glk-change`, the form value; disabled options are skipped; a disabled row dims the drop and cannot be dragged. With `overflow="scroll"` a swipe still scrolls the row: only the drop itself is dragged, and the row scrolls along at its edges; the centring of the chosen option keeps working, the drop rides along.
|
|
1191
|
+
|
|
1192
|
+
```html
|
|
1193
|
+
<glk-segmented droplet full overflow="scroll" label="Area" value="a" options='[…]'></glk-segmented>
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1134
1196
|
---
|
|
1135
1197
|
|
|
1136
1198
|
### 3.26 `<glk-steps>`
|
|
1137
1199
|
|
|
1138
|
-
Progress through a short flow (since 1.15.0), purely presentational: the current step carries `aria-current="step"`, done steps show an SVG check. The narrowing in tight frames is the CSS block's container query (below 360 px only the current label stays) — the element knows nothing about it, so in a flex row give it a width.
|
|
1200
|
+
Progress through a short flow (since 1.15.0), purely presentational: the current step carries `aria-current="step"`, done steps show an SVG check. The narrowing in tight frames is the CSS block's container query (below 360 px only the current label stays) — the element knows nothing about it, so in a flex row give it a width. Since GlassKit 1.28.0 the lines give way there first, the circles keep their size and the row never runs past its frame; a `::part(steps) { gap: … }` workaround can go.
|
|
1139
1201
|
|
|
1140
1202
|
```html
|
|
1141
1203
|
<glk-steps steps="Day,Slot,Dog,Confirm" current="2" label="Progress"></glk-steps>
|
|
@@ -1432,7 +1494,7 @@ Key points:
|
|
|
1432
1494
|
|---|---|---|---|---|
|
|
1433
1495
|
| `<glk-button>` | — | `disabled` | `glk-click` | — |
|
|
1434
1496
|
| `<glk-pill>` | — | `disabled` | `glk-click` | — |
|
|
1435
|
-
| `<glk-tab-item>` | — | `active` | `glk-tab-select` | — |
|
|
1497
|
+
| `<glk-tab-item>` | — | `active` | `glk-tab-select` (cancelable since 1.28.0; `glk-tab-change` `{ tab }` follows on the bar) | — |
|
|
1436
1498
|
| `<glk-tab-accessory>` | — | `disabled` | `glk-click` | — |
|
|
1437
1499
|
| `<glk-input>` | `.value` | `value` | `glk-input`, `glk-change` (+ native `input`, `change`) | `{ value }` |
|
|
1438
1500
|
| `<glk-textarea>` | `.value` | `value` | `glk-input`, `glk-change` | `{ value }` |
|
|
@@ -1501,14 +1563,14 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
|
|
|
1501
1563
|
|---|---|---|---|---|
|
|
1502
1564
|
| `<glk-nav>` | Navigation | — | default | — |
|
|
1503
1565
|
| `<glk-pill>` | Navigation | `label`, `disabled` | default (icon) | `glk-click` |
|
|
1504
|
-
| `<glk-tab-bar>` | Navigation | `static`, `floating` | default |
|
|
1505
|
-
| `<glk-tab-item>` | Navigation | `label`, `active`, `badge` |
|
|
1566
|
+
| `<glk-tab-bar>` | Navigation | `static`, `floating`, `droplet` | default | `glk-tab-change` |
|
|
1567
|
+
| `<glk-tab-item>` | Navigation | `label`, `active`, `badge` | `icon`; an unslotted `<svg>` is copied | `glk-tab-select` (cancelable) |
|
|
1506
1568
|
| `<glk-tab-dock>` | Navigation | `accessory-left` | default | — |
|
|
1507
1569
|
| `<glk-tab-accessory>` | Navigation | `label`, `variant`, `disabled` | default (icon) | `glk-click` |
|
|
1508
1570
|
| `<glk-avatar>` | Content | `size`, `src` | default (initials) | — |
|
|
1509
1571
|
| `<glk-badge>` | Content | `variant`, `interactive`, `selected` | default | `glk-click` (only when interactive) |
|
|
1510
1572
|
| `<glk-empty>` | Content | `title`, `text` | default (icon), `action` | — |
|
|
1511
|
-
| `<glk-segmented>` | Forms | `options`, `value`, `full`, `overflow`, `label`, `name` |
|
|
1573
|
+
| `<glk-segmented>` | Forms | `options`, `value`, `full`, `overflow`, `droplet`, `icon-only`, `label`, `name` | `icon-<value>` | `glk-change` |
|
|
1512
1574
|
| `<glk-steps>` | Navigation | `steps`, `current`, `label` | — | — |
|
|
1513
1575
|
| `<glk-sheet>` | Feedback | `open`, `inline`, `title` | default, `actions` | `glk-close` |
|
|
1514
1576
|
| `<glk-date-strip>` | Navigation | `start`, `days`, `value`, `today`, `marks`, `locale`, `label` | — | `glk-change` |
|
package/dist/components/base.js
CHANGED
|
@@ -209,8 +209,9 @@ class GlkElement extends HTMLElement {
|
|
|
209
209
|
/**
|
|
210
210
|
* Opt in when the component copies light-DOM children into its shadow tree.
|
|
211
211
|
* A MutationObserver then calls projectLightDom() again whenever those
|
|
212
|
-
* children change
|
|
213
|
-
*
|
|
212
|
+
* children change — their content and, since 1.28.0, their attributes — so
|
|
213
|
+
* a framework that swaps them or only binds `disabled` keeps the rendered
|
|
214
|
+
* element in step. Without it the copy is made once and silently goes stale.
|
|
214
215
|
*/
|
|
215
216
|
static get observesLightDom() { return false; }
|
|
216
217
|
|
|
@@ -272,8 +273,13 @@ class GlkElement extends HTMLElement {
|
|
|
272
273
|
|
|
273
274
|
if (this.constructor.observesLightDom) {
|
|
274
275
|
this._lightDomObserver ??= new MutationObserver(records => this.projectLightDom(records));
|
|
276
|
+
// Attributes too: the copies show them. Until 1.27.0 a modal action
|
|
277
|
+
// that started disabled stayed disabled in its copy after the page
|
|
278
|
+
// enabled it, and the dialog could not be confirmed. The elements'
|
|
279
|
+
// own projectLightDom() compare a signature first, so a change
|
|
280
|
+
// elsewhere in the content costs a string comparison.
|
|
275
281
|
this._lightDomObserver.observe(this, {
|
|
276
|
-
childList: true, subtree: true, characterData: true
|
|
282
|
+
childList: true, subtree: true, characterData: true, attributes: true
|
|
277
283
|
});
|
|
278
284
|
}
|
|
279
285
|
}
|
|
@@ -24,6 +24,33 @@ function reducedMotion() {
|
|
|
24
24
|
return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// The copies carry no inline handlers (onclick="…"): a click on a copy is
|
|
28
|
+
// handed to its original, and until 1.28.0 such a handler ran twice.
|
|
29
|
+
const isHandler = name => name.startsWith('on');
|
|
30
|
+
|
|
31
|
+
function copyOf(node) {
|
|
32
|
+
const copy = node.cloneNode(true);
|
|
33
|
+
if (copy.nodeType === Node.ELEMENT_NODE) {
|
|
34
|
+
for (const el of [copy, ...copy.querySelectorAll('*')]) {
|
|
35
|
+
for (const { name } of [...el.attributes]) if (isHandler(name)) el.removeAttribute(name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return copy;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Gives an action's copy the attributes and the content of its original. */
|
|
42
|
+
function mirror(copy, original) {
|
|
43
|
+
for (const { name } of [...copy.attributes]) {
|
|
44
|
+
if (!original.hasAttribute(name)) copy.removeAttribute(name);
|
|
45
|
+
}
|
|
46
|
+
for (const { name, value } of original.attributes) {
|
|
47
|
+
if (!isHandler(name) && copy.getAttribute(name) !== value) copy.setAttribute(name, value);
|
|
48
|
+
}
|
|
49
|
+
if (copy.innerHTML !== original.innerHTML) {
|
|
50
|
+
copy.replaceChildren(...[...original.childNodes].map(copyOf));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
class GlkModal extends GlkElement {
|
|
28
55
|
static get observedAttributes() {
|
|
29
56
|
return ['open', 'title'];
|
|
@@ -69,9 +96,18 @@ class GlkModal extends GlkElement {
|
|
|
69
96
|
if (signature === this._footerSignature) return;
|
|
70
97
|
this._footerSignature = signature;
|
|
71
98
|
|
|
99
|
+
// As many actions as copies: each copy takes its original's attributes
|
|
100
|
+
// and content in place, so the one that has the focus keeps it — a
|
|
101
|
+
// confirm button that the page enables, or relabels while it works.
|
|
102
|
+
const copies = [...this._footer.children];
|
|
103
|
+
if (copies.length === buttons.length) {
|
|
104
|
+
buttons.forEach((btn, i) => mirror(copies[i], btn));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
72
108
|
this._footer.innerHTML = '';
|
|
73
109
|
buttons.forEach((btn, i) => {
|
|
74
|
-
const clone = btn
|
|
110
|
+
const clone = copyOf(btn);
|
|
75
111
|
// Look the original up again at click time instead of closing over it: a
|
|
76
112
|
// framework that re-renders replaces the node, and a captured reference
|
|
77
113
|
// would then forward the click to a button no longer in the document.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GlkFormElement } from './base.js';
|
|
2
2
|
import { r as revealCentered } from './shared/reveal-CjjzoxTa.js';
|
|
3
|
+
import { a as attachDroplet } from './shared/glasskit-droplet-3vJVXB3u.js';
|
|
3
4
|
import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
4
5
|
|
|
5
6
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
@@ -10,9 +11,18 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
|
10
11
|
// do not fit — without it the row stays one line and runs past
|
|
11
12
|
// the edge), label (aria-label of the group), name (form field),
|
|
12
13
|
// disabled (the whole row; since 1.22.3, as is a disabled
|
|
13
|
-
// fieldset around it)
|
|
14
|
+
// fieldset around it), droplet (since 1.27.0: the chosen option
|
|
15
|
+
// sits on a drop of glass that glides to the next one and can be
|
|
16
|
+
// dragged — GlassKit's .glass-segmented--droplet), icon-only
|
|
17
|
+
// (since 1.28.0: the options show their icons alone, each label
|
|
18
|
+
// becomes the button's aria-label and title — GlassKit's
|
|
19
|
+
// .glass-segmented--icons)
|
|
14
20
|
// Properties: value, options (array or JSON text), full, overflow, label,
|
|
15
|
-
// disabled
|
|
21
|
+
// disabled, droplet, iconOnly
|
|
22
|
+
// Slots: icon-<value> (since 1.28.0) — the icon of the option with that
|
|
23
|
+
// value: an <svg>, a font icon, an <img>. It sits in GlassKit's
|
|
24
|
+
// .glass-segmented__icon before the label and takes the option's
|
|
25
|
+
// colour; an option without one has no icon box.
|
|
16
26
|
// Event: glk-change { value } — only on a change made by the user
|
|
17
27
|
// Part: group
|
|
18
28
|
//
|
|
@@ -41,7 +51,7 @@ function parseList(json) {
|
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
class GlkSegmented extends GlkFormElement {
|
|
44
|
-
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label']; }
|
|
54
|
+
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label', 'droplet', 'icon-only']; }
|
|
45
55
|
|
|
46
56
|
// A row of buttons with one tab stop: delegated focus would land on the
|
|
47
57
|
// first button, not the chosen one.
|
|
@@ -58,20 +68,39 @@ class GlkSegmented extends GlkFormElement {
|
|
|
58
68
|
|
|
59
69
|
_build() {
|
|
60
70
|
this._group.replaceChildren();
|
|
71
|
+
const iconOnly = this.iconOnly;
|
|
61
72
|
for (const option of parseList(this.getAttribute('options'))) {
|
|
62
73
|
const value = String(option.value ?? '');
|
|
74
|
+
const label = String(option.label ?? value);
|
|
63
75
|
const button = this.createElement('button', ['glass-segmented__item'], { type: 'button', 'data-value': value });
|
|
64
76
|
if (TONES.includes(option.tone)) {
|
|
65
77
|
button.classList.add(`glass-segmented__item--${option.tone}`);
|
|
66
78
|
button.appendChild(this.createElement('span', ['glass-segmented__dot']));
|
|
67
79
|
}
|
|
68
|
-
|
|
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
|
+
}
|
|
69
90
|
this._group.appendChild(button);
|
|
70
91
|
}
|
|
92
|
+
this._syncIcons();
|
|
71
93
|
this.applyDisabled(this._actuallyDisabled);
|
|
72
94
|
this._syncValue();
|
|
73
95
|
}
|
|
74
96
|
|
|
97
|
+
/** An option shows its icon box only while an icon is slotted into it. */
|
|
98
|
+
_syncIcons() {
|
|
99
|
+
for (const slot of this._group.querySelectorAll('.glass-segmented__icon > slot')) {
|
|
100
|
+
slot.parentElement.hidden = slot.assignedElements().length === 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
75
104
|
/** The whole row is disabled, or an option by its own `disabled`. */
|
|
76
105
|
applyDisabled(disabled) {
|
|
77
106
|
const options = this.options;
|
|
@@ -92,6 +121,8 @@ class GlkSegmented extends GlkFormElement {
|
|
|
92
121
|
_applyLayout() {
|
|
93
122
|
const overflow = this.overflow;
|
|
94
123
|
this._group.classList.toggle('glass-segmented--full', this.getBoolAttr('full'));
|
|
124
|
+
this._group.classList.toggle('glass-segmented--droplet', this.getBoolAttr('droplet'));
|
|
125
|
+
this._group.classList.toggle('glass-segmented--icons', this.iconOnly);
|
|
95
126
|
for (const mode of OVERFLOWS) this._group.classList.toggle(`glass-segmented--${mode}`, overflow === mode);
|
|
96
127
|
this._reveal('instant');
|
|
97
128
|
}
|
|
@@ -118,15 +149,30 @@ class GlkSegmented extends GlkFormElement {
|
|
|
118
149
|
this.emit('glk-change', { value });
|
|
119
150
|
};
|
|
120
151
|
this._onResize = () => { if (this._group.clientWidth !== this._revealedAt) this._reveal('instant'); };
|
|
152
|
+
this._onSlotChange = () => this._syncIcons();
|
|
121
153
|
this._group.addEventListener('click', this._onClick);
|
|
154
|
+
this._group.addEventListener('slotchange', this._onSlotChange);
|
|
122
155
|
this._resizeObserver = new ResizeObserver(this._onResize);
|
|
123
156
|
this._resizeObserver.observe(this._group);
|
|
157
|
+
this._syncIcons();
|
|
158
|
+
this._applyDroplet();
|
|
124
159
|
}
|
|
125
160
|
|
|
126
161
|
teardownEvents() {
|
|
127
162
|
this._group?.removeEventListener('click', this._onClick);
|
|
163
|
+
this._group?.removeEventListener('slotchange', this._onSlotChange);
|
|
128
164
|
this._resizeObserver?.disconnect();
|
|
129
165
|
this._resizeObserver = null;
|
|
166
|
+
this._droplet?.destroy();
|
|
167
|
+
this._droplet = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// The drop follows aria-pressed inside this shadow root by itself; a drag
|
|
171
|
+
// that ends on an option clicks it, the same path as a tap.
|
|
172
|
+
_applyDroplet() {
|
|
173
|
+
const on = this.getBoolAttr('droplet') && this.isConnected;
|
|
174
|
+
if (on && !this._droplet) this._droplet = attachDroplet(this._group);
|
|
175
|
+
else if (!on && this._droplet) { this._droplet.destroy(); this._droplet = null; }
|
|
130
176
|
}
|
|
131
177
|
|
|
132
178
|
onAttributeChanged(name) {
|
|
@@ -136,6 +182,8 @@ class GlkSegmented extends GlkFormElement {
|
|
|
136
182
|
case 'options': this._build(); break;
|
|
137
183
|
case 'full':
|
|
138
184
|
case 'overflow': this._applyLayout(); break;
|
|
185
|
+
case 'droplet': this._applyLayout(); this._applyDroplet(); break;
|
|
186
|
+
case 'icon-only': this._build(); this._applyLayout(); break;
|
|
139
187
|
case 'label': this._applyLabel(); break;
|
|
140
188
|
}
|
|
141
189
|
}
|
|
@@ -155,6 +203,12 @@ class GlkSegmented extends GlkFormElement {
|
|
|
155
203
|
get full() { return this.getBoolAttr('full'); }
|
|
156
204
|
set full(v) { this.setBoolAttr('full', v); }
|
|
157
205
|
|
|
206
|
+
get droplet() { return this.getBoolAttr('droplet'); }
|
|
207
|
+
set droplet(v) { this.setBoolAttr('droplet', v); }
|
|
208
|
+
|
|
209
|
+
get iconOnly() { return this.getBoolAttr('icon-only'); }
|
|
210
|
+
set iconOnly(v) { this.setBoolAttr('icon-only', v); }
|
|
211
|
+
|
|
158
212
|
get overflow() {
|
|
159
213
|
const v = this.getAttribute('overflow');
|
|
160
214
|
return OVERFLOWS.includes(v) ? v : '';
|