@jungherz-de/glasskit-elements 1.20.0 → 1.21.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 +32 -1
- package/README.md +35 -7
- package/SKILL.md +38 -6
- package/dist/components/base.js +41 -3
- package/dist/glasskit-elements.esm.js +45 -7
- package/dist/glasskit-elements.js +45 -7
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/base.js +41 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.21.1] – 2026-10-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Printed pages keep the state of the controls.** Rebuilt against GlassKit 1.21.1, whose print rule keeps the parts that carry a state in colour on paper — print dialogs leave out background graphics by default, and a checked `<glk-checkbox>` printed like an empty one, a `<glk-progress>` printed empty, the chosen day of `<glk-calendar>` paler than the others. The rule is part of GlassKit's component sheet, so it reaches the shadow roots; the bundles carry that sheet and needed the rebuild, the per-component files take it from the project's GlassKit. Measured in Chromium with `page.pdf()` without background graphics, inked pixels inside the indicator, on / off: `<glk-toggle>` 231 / 222 → 1025 / 226, `<glk-checkbox>` 87 / 79 → 511 / 79, `<glk-radio>` 75 / 67 → 383 / 67, the chosen `<glk-calendar>` day 42 → 1915 against 56 for another day; the `<glk-progress>` fill, the current `<glk-steps>` step and the calendar's tone dots now print. The peer dependency moves to `>=1.21.1`. ([GlassKit #6](https://github.com/JUNGHERZ/GlassKit/issues/6))
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- README, SKILL.md and the docs (English and German) describe printing, with a `beforeprint` / `afterprint` snippet that prints a dark page in the light theme; the elements follow the switch in time for the print (checked in Chromium).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.21.0] – 2026-10-05
|
|
23
|
+
|
|
24
|
+
Versions realign with GlassKit at 1.21.0, which this release needs (peer dependency `>=1.21.0`). Contributed as pull request [#10](https://github.com/JUNGHERZ/GlassKit-Elements/pull/10), the companion of GlassKit's [#5](https://github.com/JUNGHERZ/GlassKit/pull/5).
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`data-density` on `<html>` reaches the elements, like `data-theme`.** GlassKit 1.21.0 turns the sizes of fields, buttons, toggles, checkboxes, radios, list rows and modal actions into density tokens, with `data-density="compact"` on `<html>` as a denser preset for admin screens, desktop layouts and narrow forms. The values arrive in every shadow root by inheritance — GlassKit puts the preset into `tokensCss`, which the elements place on the document in `@layer glasskit-defaults` — so a project's own density values (`[data-density='compact'] { --gl-btn-height: 36px; }`) reach the elements too; with `data-glk-defaults="off"` the preset comes from `glasskit.css` on the page, like every other token. The global `MutationObserver` now watches `data-density` next to `data-theme` and `data-glk-defaults` and mirrors it onto every element's `.glk-wrapper`: set when the wrapper is created, updated when `<html>` changes, removed when `<html>` has none. A rule keyed on the attribute — a GlassKit component rule, the `hostStyles` of a project's own `GlkElement` — therefore matches inside the shadow root, as it does for the theme. `GlkElement` gains `_syncDensity(density)` next to `_syncTheme(theme)`; the observer calls each only for the attribute that changed. The bundles are built against GlassKit 1.21.0 and carry its tokens; the per-component files use the GlassKit the project installs.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **An element that was out of the document while the theme changed came back with the old theme on its wrapper.** The observer only reaches connected elements, and the wrapper took `data-theme` once, when it was created; an element removed and re-inserted after a theme switch — a cached view, a list a framework re-sorts — kept the old value. Since 1.20.0 that value also sets the wrapper's `color-scheme`, so such an element drew its native parts — a select's list, a date picker, scrollbars — in the other scheme. It now takes `data-theme` and `data-density` from `<html>` again whenever it is connected anew.
|
|
33
|
+
|
|
34
|
+
Verified in Chromium and WebKit (Playwright 1.63), the elements built against the GlassKit branch with the density tokens: without `data-density` every element measures as with 1.20.0, and the elements' index, showcase and docs (EN/DE) render pixel-identical, apart from regions that also differ between two renders of 1.20.0; with `data-density="compact"` on `<html>`, at load and switched at runtime, `<glk-input>`, `<glk-select>` and `<glk-search>` measure 40 px with 12 px inner padding, `<glk-button>` 40 / 32 / 48 px, `<glk-textarea>` 88 px, `<glk-list-item>` 48 px, `<glk-toggle>` 44 × 26 px with an 18 px thumb, `<glk-checkbox>` and `<glk-radio>` 20 px, the `<glk-modal>` actions 44 px — with and without `glasskit.css` on the page, left to right and right to left. Every wrapper carries `data-density="compact"` and loses it with `<html>`; an element created after the switch starts with it, one detached during the switch catches up when it is inserted again, its `color-scheme` included.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
10
38
|
## [1.20.0] – 2026-10-05
|
|
11
39
|
|
|
12
40
|
Versions realign with GlassKit at 1.20.0, which this release needs (peer dependency `>=1.20.0`). The first batch of findings filed as GitHub issues, from a product that is adopting GlassKit at full depth — mostly accessibility and forms. Everything below was measured in Chromium and WebKit.
|
|
@@ -16,7 +44,7 @@ Versions realign with GlassKit at 1.20.0, which this release needs (peer depende
|
|
|
16
44
|
- **Importing the elements no longer turns the page dark.** The token defaults went onto the document with GlassKit's `color-scheme: dark` included, so on a page that does not use GlassKit's theme the whole document switched: `color-scheme` of `:root` and of every native control to `dark`, unstyled text from black to white, links from `rgb(0, 0, 238)` to `rgb(158, 158, 255)`. The injected layer now carries the `--gl-*` tokens only. Each element sets `color-scheme` on its own theme wrapper, following `data-theme`, so native parts inside it — a select's list, a date picker — still match the element. Measured on a white page without GlassKit CSS: scheme, text and link colour stay as they were; the select inside `<glk-select>` is `dark`, under `data-theme="light"` `light`. **New:** `<html data-glk-defaults="off">` switches the defaults off, for a page that declares the tokens itself or wants them on part of the page only; the attribute is watched, so it may also be set or removed later. Until now the only way was the internal flag meant to detect duplicate bundles. Compatibility: a page that counted on the elements to make it dark sets `color-scheme` itself, or loads `glasskit.css`, whose token blocks set it. ([#1](https://github.com/JUNGHERZ/glasskit-elements/issues/1))
|
|
17
45
|
- **Form fields have accessible names, and focus reaches them.** The `<label>` of `<glk-input>`, `<glk-select>`, `<glk-textarea>` and `<glk-range>` sat next to the field without `for`, so the field had no name — with a placeholder, screen readers announced only that — and a click on the label text did nothing. Label and field are tied by id inside the shadow root now: `textbox "Vorname"`, `combobox "Land"`, `slider "Lautstärke"`. The `hint` of `<glk-input>` describes the field (`aria-describedby`), and `error` marks it `aria-invalid`. `<glk-search>`, which has no visible label, takes `label` as its accessible name. Form elements with one field open their shadow root with `delegatesFocus`, so `element.focus()`, a click on the label text and a click on a `<label for>` naming the element all land in the field. `<glk-segmented>` and `<glk-calendar>` keep it off: their first button is not the chosen one, and Tab already reaches the chosen one. An outer `<label for>` focuses the field but cannot name it across the shadow boundary — use `label` for the name. ([#2](https://github.com/JUNGHERZ/glasskit-elements/issues/2))
|
|
18
46
|
- **The form learns whether the fields are valid.** `GlkFormElement` had a `setValidity()` that nothing called, so an empty `required` field counted as valid: `form.checkValidity()` returned `true` and `requestSubmit()` sent the form. Every element built on one native field now copies that field's verdict onto the host after each change — value, attribute, reset — with the field as anchor, so the browser's bubble points at it and `reportValidity()` focuses it. `required`, `pattern`, `min`/`max`, `minlength`/`maxlength`, `type="email"` and the like stop a submit with the browser's own message; `validity`, `validationMessage`, `willValidate`, `checkValidity()` and `reportValidity()` answer on the element. `<glk-checkbox>`, `<glk-toggle>` and `<glk-radio>` gain `required`, radios by the native rule: one required radio makes its group required, any checked one satisfies it. `<glk-textarea>` and `<glk-select>` now also follow a `required` set after they rendered. A disabled field is barred from validation, as natively. `error` stays a marker and does not block — a server's verdict arrives that way, and it must not stop the next attempt. Measured: empty required fields make `checkValidity()` false, `requestSubmit()` sends nothing, `reportValidity()` focuses the first field; "nope" in a `type="email"` field is a `typeMismatch`; checking the second radio of a required group makes both valid; `form.reset()` validates again. ([#3](https://github.com/JUNGHERZ/glasskit-elements/issues/3))
|
|
19
|
-
- **`<glk-button type="submit">` submits its form, `type="reset"` resets it.** The native button lives in the shadow root, outside every form, so both types only emitted `glk-click`. The element is form-associated now and acts on its form once the click has finished bubbling — `preventDefault()` on the click stops it, as on a native button. Invalid fields stop the submit with the browser's message; the submit event names the element as its `submitter`; `form` and `type` are properties; `element.click()` acts like a click. Measured: one submit with complete `FormData` and the element as `submitter`, none after `preventDefault()`, one from `element.click()`; reset restores values and validity. Not covered: implicit submission — Enter in a `<glk-input>` does not submit the form. ([#4](https://github.com/JUNGHERZ/glasskit-elements/issues/4))
|
|
47
|
+
- **`<glk-button type="submit">` submits its form, `type="reset"` resets it.** The native button lives in the shadow root, outside every form, so both types only emitted `glk-click`. The element is form-associated now and acts on its form once the click has finished bubbling — `preventDefault()` on the click stops it, as on a native button. Invalid fields stop the submit with the browser's message; the submit event names the element as its `submitter`; `form` and `type` are properties; `element.click()` acts like a click. Measured: one submit with complete `FormData` and the element as `submitter`, none after `preventDefault()`, one from `element.click()`; reset restores values and validity. Not covered: implicit submission — Enter in a `<glk-input>` does not submit the form. Compatibility: a form whose `<glk-button type="submit">` did its work in a `glk-click` handler now also submits for real — without a `submit` listener that calls `preventDefault()`, the browser sends the form and loads the page again. Handle `submit` instead, or drop `type="submit"`. ([#4](https://github.com/JUNGHERZ/glasskit-elements/issues/4))
|
|
20
48
|
- **`<glk-modal>` is a native dialog.** It had no dialog role, the focus stayed behind it, Tab walked through the page before it reached the modal's buttons, and its content was reachable while it was closed. The overlay is a `<dialog>` now, opened with `showModal()`: top layer, the page behind it inert for pointer, keyboard and screen readers, focus moving in and back to the opener on closing, named by its title (`aria-labelledby`), the content out of reach while closed. The fade stays — Escape arrives as the dialog's `cancel`, and the element turns it into the same animated close as a click on the dimmed area; both emit `glk-close`. The browsers pick the first focus differently, Chrome the first field of the content, Safari the first action; `autofocus` on an element in the content or on an action settles it. Moved while open, it opens as a modal again. Measured: `:modal` matches, `dialog "Eintrag löschen?"`, Tab cycles inside, a point over the page hits the overlay, Escape emits `glk-close` once and returns the focus to the opener. Compatibility: everything outside an open modal is inert — a `<glk-toast>` shown while it is open is neither clickable nor announced (moved into the top layer as a popover, it stays inert too; measured), so give feedback inside the modal or close it first; a `<glk-sheet>` opened from an open modal ends up behind it. ([#5](https://github.com/JUNGHERZ/glasskit-elements/issues/5))
|
|
21
49
|
- **`<glk-toggle>` is one switch, and clicks aimed at the element work.** The host carried `role="switch"` and `aria-checked` with a focusable native checkbox inside, so screen readers met a switch that contained a checkbox. The role moved onto the checkbox; the host carries no `role`, `aria-checked` or `aria-disabled` any more, and the tree shows `switch "Benachrichtigungen"`, nothing else. `toggle.click()` changed nothing, where a native checkbox toggles — and a `<label>` around the element or naming it clicks the host, too. A click aimed at the host itself is now handed to the input and the original stopped, so listeners see one click and one change; the same holds for `<glk-checkbox>`, `<glk-radio>` and `<glk-button>`. A press on the host's empty width (it is a block, the control narrower) still does nothing: Chrome gives a label's click the same `detail` and `isTrusted` as a real one, so the two are told apart by where the pointer went down. With GlassKit 1.20.0 the input lies on top of the track, so Playwright's `getByRole('switch').click()`, `check()` and `uncheck()` reach it instead of timing out on the covering track — `getByRole('checkbox')` and `getByRole('radio')` gain the same. Compatibility: CSS or tests that select `glk-toggle[aria-checked]` or `[role="switch"]` on the host use `glk-toggle[checked]` now. ([#6](https://github.com/JUNGHERZ/glasskit-elements/issues/6))
|
|
22
50
|
- **Accordion and popover triggers say whether they are open.** The accordion trigger carries `aria-expanded` and `aria-controls` now, and a closed section is `inert`: before, it was only squeezed to zero height, so Tab still walked into its links and screen readers read them. `<glk-popover>` sets `aria-expanded` on its trigger, again whenever the trigger is swapped; `<glk-button>` and `<glk-pill>` hand `aria-expanded`, `aria-haspopup` and `aria-pressed` on to their native button. Measured: `button "Abschnitt 1" [expanded=false]`, the closed section's link takes no focus and is missing from Chromium's accessibility tree, `button "Menü" [expanded]` while the popover is open. ([#7](https://github.com/JUNGHERZ/glasskit-elements/issues/7))
|
|
@@ -735,6 +763,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
|
|
|
735
763
|
|
|
736
764
|
---
|
|
737
765
|
|
|
766
|
+
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.21.1...HEAD
|
|
767
|
+
[1.21.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.21.1
|
|
768
|
+
[1.21.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.21.0
|
|
738
769
|
[1.20.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.20.0
|
|
739
770
|
[1.19.2]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.19.2
|
|
740
771
|
[1.19.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.19.1
|
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.21.1-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
|
|
|
@@ -55,7 +55,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
55
55
|
| 🪶 **Lightweight** | 156 KB minified / 28 KB gzipped (IIFE bundle, GlassKit's component rules included), no external dependencies |
|
|
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
|
-
| 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html
|
|
58
|
+
| 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html>`, compact controls via `data-density` |
|
|
59
59
|
| 📱 **Mobile-first** | Inherits GlassKit's mobile-optimized design with `safe-area-inset` support |
|
|
60
60
|
|
|
61
61
|
---
|
|
@@ -66,7 +66,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
66
66
|
|
|
67
67
|
```html
|
|
68
68
|
<!-- 1. GlassKit CSS -->
|
|
69
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
69
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.21/glasskit.min.css">
|
|
70
70
|
|
|
71
71
|
<!-- 2. GlassKit Elements -->
|
|
72
72
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
@@ -104,7 +104,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
104
104
|
<!DOCTYPE html>
|
|
105
105
|
<html data-theme="dark">
|
|
106
106
|
<head>
|
|
107
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
107
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.21/glasskit.min.css">
|
|
108
108
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
109
109
|
</head>
|
|
110
110
|
<body>
|
|
@@ -204,6 +204,34 @@ const current = html.getAttribute('data-theme');
|
|
|
204
204
|
html.setAttribute('data-theme', current === 'dark' ? 'light' : 'dark');
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
+
### Density
|
|
208
|
+
|
|
209
|
+
Admin screens, desktop layouts and narrow forms want denser controls. Set `data-density="compact"` on `<html>`, next to `data-theme`:
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<html data-theme="dark" data-density="compact">
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Fields and buttons become 40px high instead of 52 and 56, toggles, checkboxes and radios smaller, list rows and modal actions denser; `removeAttribute('data-density')` switches back at runtime. Without the attribute nothing changes.
|
|
216
|
+
|
|
217
|
+
> **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
|
+
### Printing
|
|
220
|
+
|
|
221
|
+
Print dialogs leave out background graphics by default. With GlassKit 1.21.1 the parts that carry a state keep their colours on paper — checkbox, radio, toggle, progress, the current step, the chosen day, the slider thumb, the tone dots — inside the elements as well. A dark page prints light text and surfaces on white paper; a page meant for printing switches to the light theme for it, and the elements follow:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
let printTheme = null;
|
|
225
|
+
addEventListener('beforeprint', () => {
|
|
226
|
+
printTheme = document.documentElement.getAttribute('data-theme');
|
|
227
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
228
|
+
});
|
|
229
|
+
addEventListener('afterprint', () => {
|
|
230
|
+
if (printTheme) document.documentElement.setAttribute('data-theme', printTheme);
|
|
231
|
+
else document.documentElement.removeAttribute('data-theme');
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
207
235
|
### Icons
|
|
208
236
|
|
|
209
237
|
Pass an icon as a **direct child**, not wrapped:
|
|
@@ -256,15 +284,15 @@ itself, or wants them on part of the page only, switches the defaults off:
|
|
|
256
284
|
## 🛠️ Architecture
|
|
257
285
|
|
|
258
286
|
- **Shadow DOM** with `adoptedStyleSheets` — GlassKit's `componentsSheet` is shared across all component instances; token declarations stay on the document so branding can be inherited
|
|
259
|
-
- **Theme wrapper** with `display: contents` — layout-transparent `<div>` for `data-theme` CSS selectors
|
|
260
|
-
- **Global `MutationObserver`** — single observer watches `data-theme` changes and syncs all instances
|
|
287
|
+
- **Theme wrapper** with `display: contents` — layout-transparent `<div>` for `data-theme` and `data-density` CSS selectors
|
|
288
|
+
- **Global `MutationObserver`** — single observer watches `data-theme` and `data-density` changes and syncs all instances
|
|
261
289
|
- **`GlkElement`** base class — handles Shadow DOM setup, theme sync, attribute reflection
|
|
262
290
|
- **`GlkFormElement`** extends `GlkElement` — adds `ElementInternals` for native form participation, delegates focus to its field and reports the field's validity
|
|
263
291
|
- **Native semantics** — labels tied to their fields, the toggle's `role="switch"` on its native checkbox, `<glk-modal>` a native `<dialog>`, `aria-expanded` on accordion and popover triggers
|
|
264
292
|
|
|
265
293
|
### Build your own element
|
|
266
294
|
|
|
267
|
-
Since 1.14.0 both base classes are exported, so a project element inherits the whole setup — open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), theme wrapper following `data-theme`, listeners re-armed when the element moves, `emit()` for bubbling, composed events:
|
|
295
|
+
Since 1.14.0 both base classes are exported, so a project element inherits the whole setup — open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), theme wrapper following `data-theme` and `data-density`, listeners re-armed when the element moves, `emit()` for bubbling, composed events:
|
|
268
296
|
|
|
269
297
|
```js
|
|
270
298
|
import { GlkElement } from '@jungherz-de/glasskit-elements'; // or '…/base.js', or GlassKitElements.GlkElement from the CDN bundle
|
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.21.1) wrapping GlassKit CSS v1.21.1. 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
|
|
@@ -18,7 +18,7 @@ description: GlassKit Elements is a vanilla-JS Web Components library (v1.20.0)
|
|
|
18
18
|
npm install @jungherz-de/glasskit-elements @jungherz-de/glasskit
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Peer dependency `@jungherz-de/glasskit >=1.
|
|
21
|
+
Peer dependency `@jungherz-de/glasskit >=1.21.1` is required — 1.21.1 keeps the controls' state on paper, 1.21.0 brings the density tokens behind `data-density`, 1.20.0 the rules the elements build on since then (the dialog overlay, hidden list slots, empty control labels, control inputs on top); 1.9.0 is the release that made the stylesheet splittable, which is what lets document-level branding reach the elements at all.
|
|
22
22
|
|
|
23
23
|
### Import (ES modules)
|
|
24
24
|
|
|
@@ -89,6 +89,31 @@ function toggleTheme() {
|
|
|
89
89
|
|
|
90
90
|
A single module-level `MutationObserver` watches `data-theme` on `<html>` and syncs every live GlassKit Element instance — you never set `data-theme` on individual elements.
|
|
91
91
|
|
|
92
|
+
### Density
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<html data-theme="dark" data-density="compact">
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Denser controls for admin screens, desktop layouts and narrow forms: fields and buttons 40px instead of 52 and 56, smaller toggles, checkboxes and radios, denser list rows and modal actions. Without the attribute nothing changes; `removeAttribute('data-density')` switches back at runtime. The sizes are GlassKit's density tokens (GlassKit 1.21.0 or later), so they reach every shadow root by inheritance, and a project's own values do too (`[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 same observer mirrors `data-density` onto each element's theme wrapper — set it on `<html>` only, never on individual elements.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Printing (since 1.21.1)
|
|
102
|
+
|
|
103
|
+
Print dialogs leave out backgrounds by default; with GlassKit 1.21.1 the parts that carry a state keep their colours on paper, inside the elements too (`print-color-adjust: exact` in GlassKit's `@media print` rule — the component sheet reaches the shadow roots). A dark page prints light text and surfaces on white paper; for pages meant to be printed, switch to the light theme around printing — the elements follow `data-theme` as always:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
let printTheme = null;
|
|
107
|
+
addEventListener('beforeprint', () => {
|
|
108
|
+
printTheme = document.documentElement.getAttribute('data-theme');
|
|
109
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
110
|
+
});
|
|
111
|
+
addEventListener('afterprint', () => {
|
|
112
|
+
if (printTheme) document.documentElement.setAttribute('data-theme', printTheme);
|
|
113
|
+
else document.documentElement.removeAttribute('data-theme');
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
92
117
|
---
|
|
93
118
|
|
|
94
119
|
## 2. Core Concepts
|
|
@@ -98,7 +123,7 @@ A single module-level `MutationObserver` watches `data-theme` on `<html>` and sy
|
|
|
98
123
|
| Tag prefix | `glk-*` (analogous to the `glass-*` CSS prefix) |
|
|
99
124
|
| Rendering | Shadow DOM (`mode: 'open'`) with `adoptedStyleSheets` |
|
|
100
125
|
| Stylesheet sharing | GlassKit's `componentsSheet` is the same `CSSStyleSheet` object in every element — no CSS duplication. Token declarations live on the document, not in the shadow roots |
|
|
101
|
-
| Theme sync | Global MutationObserver on `<html data-theme>` |
|
|
126
|
+
| Theme sync | Global MutationObserver on `<html data-theme>` and `<html data-density>` |
|
|
102
127
|
| Events | Custom `glk-*` events, all `bubbles: true, composed: true` |
|
|
103
128
|
| Form participation | `GlkFormElement` uses `ElementInternals` (`static formAssociated = true`): value, reset and — since 1.20.0 — the inner field's validity |
|
|
104
129
|
| Focus | Form elements with one field delegate focus (since 1.20.0): `element.focus()`, a click on the label, a `<label for>` land in the field |
|
|
@@ -108,7 +133,7 @@ Custom properties (`--gl-*`) defined on `:root` or `<html>` pass through shadow
|
|
|
108
133
|
|
|
109
134
|
### Building your own element (since 1.14.0)
|
|
110
135
|
|
|
111
|
-
`GlkElement` and `GlkFormElement` are exported — from the bundle (`import { GlkElement } from '@jungherz-de/glasskit-elements'`), from the stable subpath `@jungherz-de/glasskit-elements/base.js` (the very module the components import, so `instanceof GlkElement` holds across both), and as `GlassKitElements.GlkElement` from the CDN `<script>` bundle. A subclass inherits the whole setup: open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), the theme wrapper following `data-theme`, listeners re-armed when the element moves, `emit()` for bubbling, composed events.
|
|
136
|
+
`GlkElement` and `GlkFormElement` are exported — from the bundle (`import { GlkElement } from '@jungherz-de/glasskit-elements'`), from the stable subpath `@jungherz-de/glasskit-elements/base.js` (the very module the components import, so `instanceof GlkElement` holds across both), and as `GlassKitElements.GlkElement` from the CDN `<script>` bundle. A subclass inherits the whole setup: open shadow root with GlassKit's stylesheet adopted (`.glass-*` classes work inside), the theme wrapper following `data-theme` and `data-density`, listeners re-armed when the element moves, `emit()` for bubbling, composed events.
|
|
112
137
|
|
|
113
138
|
```js
|
|
114
139
|
class DemoCounter extends GlkElement {
|
|
@@ -131,7 +156,7 @@ customElements.define('demo-counter', DemoCounter);
|
|
|
131
156
|
|
|
132
157
|
Which import to take depends on how the elements are loaded, and mixing them is the one mistake to avoid: the `<script>` / ESM **bundle** carries its own copy of `GlkElement`, so a subclass built on `base.js` next to it is a different class — `instanceof` fails across the two, and the GlassKit stylesheet lives twice. Bundle loaded → take `GlassKitElements.GlkElement` or `import { GlkElement } from '@jungherz-de/glasskit-elements'`. Per-component files loaded → `base.js`, which the components import themselves. The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` external, so a bundler resolves it from `node_modules` and an import-map project maps it to the one copy it already loads for its own elements.
|
|
133
158
|
|
|
134
|
-
Rules for a subclass: build only inside `this._wrapper` (it carries `data-theme`); keep listeners in `setupEvents()` / `teardownEvents()`, never in `render()`, or a moved element loses them; use `emit()` instead of `dispatchEvent()` so the event bubbles and crosses the shadow boundary — `emit(name, detail, { cancelable: true })` lets a listener call `preventDefault()`, and `emit()` returns `false` then (since 1.19.0); return `true` from `static get displayInline()` for inline elements; set `static get observesLightDom()` to `true` and implement `projectLightDom()` when copying light-DOM children into the shadow tree. `GlkFormElement` adds `setFormValue()`, `setValidity()`, `resetValue()` / `restoreValue()` and `static formAssociated = true`.
|
|
159
|
+
Rules for a subclass: build only inside `this._wrapper` (it carries `data-theme`, and `data-density` while `<html>` has one); keep listeners in `setupEvents()` / `teardownEvents()`, never in `render()`, or a moved element loses them; use `emit()` instead of `dispatchEvent()` so the event bubbles and crosses the shadow boundary — `emit(name, detail, { cancelable: true })` lets a listener call `preventDefault()`, and `emit()` returns `false` then (since 1.19.0); return `true` from `static get displayInline()` for inline elements; set `static get observesLightDom()` to `true` and implement `projectLightDom()` when copying light-DOM children into the shadow tree. `GlkFormElement` adds `setFormValue()`, `setValidity()`, `resetValue()` / `restoreValue()` and `static formAssociated = true`.
|
|
135
160
|
|
|
136
161
|
### Light-DOM children (since 1.12.0)
|
|
137
162
|
|
|
@@ -154,6 +179,8 @@ listeners and its theme-sync registration are re-armed on every connect, so it k
|
|
|
154
179
|
working. Before 1.12.0 they were torn down on disconnect and never restored: the
|
|
155
180
|
element still looked right but no longer fired events.
|
|
156
181
|
|
|
182
|
+
An element that was out of the document while `data-theme` or `data-density` changed on `<html>` catches up when it is connected again (since 1.21.0); before, its wrapper kept the old value — and since 1.20.0 that value also sets the `color-scheme` of the native parts inside.
|
|
183
|
+
|
|
157
184
|
### Branding (since 1.9.0)
|
|
158
185
|
|
|
159
186
|
Override `--gl-*` tokens anywhere in the document — a brand stylesheet, a `<style>` block,
|
|
@@ -555,6 +582,10 @@ stop the submit with the browser's message; the submit event's `submitter` is th
|
|
|
555
582
|
`.form` is its form; `element.click()` acts like a click. No implicit submission: Enter in a
|
|
556
583
|
`<glk-input>` does not submit the form.
|
|
557
584
|
|
|
585
|
+
**Upgrading from ≤ 1.19:** code that set `type="submit"` and did the work in a `glk-click`
|
|
586
|
+
handler now gets a real submit as well — handle the form's `submit` event (with
|
|
587
|
+
`preventDefault()` in an SPA) or drop `type="submit"`, or the browser reloads the page.
|
|
588
|
+
|
|
558
589
|
`aria-expanded`, `aria-haspopup` and `aria-pressed` set on the element are handed on to the
|
|
559
590
|
native button inside, which is what screen readers read (since 1.20.0).
|
|
560
591
|
|
|
@@ -1429,7 +1460,7 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
|
|
|
1429
1460
|
|
|
1430
1461
|
### Always follow
|
|
1431
1462
|
|
|
1432
|
-
1. **`data-theme` on `<html>`** — never on `<body>` or individual elements; the global MutationObserver only watches `<html>`.
|
|
1463
|
+
1. **`data-theme` and `data-density` on `<html>`** — never on `<body>` or individual elements; the global MutationObserver only watches `<html>`.
|
|
1433
1464
|
2. **Form elements belong inside a `<form>`** — otherwise `ElementInternals.setFormValue()` has no effect and `new FormData()` will be empty.
|
|
1434
1465
|
3. **`<glk-list-item>` must be a direct child of `<glk-list>`** — nested deeper, it won't be observed for the auto-divider last-item detection.
|
|
1435
1466
|
4. **Popover trigger via `slot="trigger"`** — don't reference external elements by ID. The popover's built-in click handler listens on the trigger slot.
|
|
@@ -1448,6 +1479,7 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
|
|
|
1448
1479
|
| `<glk-list>` inside `<glk-popover>` without `bare` | Add `bare` to the inner list so only the popover surface has glass. |
|
|
1449
1480
|
| `document.querySelector('.glass-modal').classList.add('is-active')` | Set `<glk-modal>.open = true` instead — the element manages `.is-active` internally. |
|
|
1450
1481
|
| `data-theme` directly on a `<glk-*>` element | Set it on `<html>`; theme propagates automatically through the MutationObserver. |
|
|
1482
|
+
| Shrinking elements one by one (`::part`, host heights) for a denser UI | `data-density="compact"` on `<html>`, or a density token such as `--gl-btn-height` on `:root` — GlassKit 1.21.0 or later. |
|
|
1451
1483
|
| Form element outside `<form>` | Wrap in a `<form>`; `ElementInternals` needs a form owner. |
|
|
1452
1484
|
| Overriding shadow styles with external CSS | Use CSS custom properties (`--gl-color-primary`, etc.) — they pierce shadow roots. Hard-coded selectors inside shadow roots cannot be targeted externally. |
|
|
1453
1485
|
| Forgetting `slot="trigger"` on the popover trigger element | Without it, the popover's internal click handler can't identify the trigger and never toggles. |
|
package/dist/components/base.js
CHANGED
|
@@ -53,9 +53,16 @@ function syncDefaultTokens() {
|
|
|
53
53
|
|
|
54
54
|
injectDefaultTokens();
|
|
55
55
|
|
|
56
|
-
// ── Global Theme Sync ──
|
|
57
|
-
// Single MutationObserver that watches data-theme on <html>
|
|
56
|
+
// ── Global Theme & Density Sync ──
|
|
57
|
+
// Single MutationObserver that watches data-theme and data-density on <html>
|
|
58
58
|
// and notifies all GlkElement instances.
|
|
59
|
+
//
|
|
60
|
+
// The density tokens themselves arrive from <html> by inheritance, like every
|
|
61
|
+
// other token: GlassKit's [data-density="compact"] preset is part of
|
|
62
|
+
// tokensCss, not of the components sheet. The wrapper mirrors the attribute
|
|
63
|
+
// anyway, as it mirrors data-theme — a rule keyed on it (a component rule, a
|
|
64
|
+
// subclass's hostStyles) can match inside a shadow root only on an element
|
|
65
|
+
// there.
|
|
59
66
|
|
|
60
67
|
const instances = new Set();
|
|
61
68
|
|
|
@@ -63,6 +70,11 @@ function getCurrentTheme() {
|
|
|
63
70
|
return document.documentElement.getAttribute('data-theme') || 'dark';
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
/** The density on <html>, or null — there is no default to fall back to. */
|
|
74
|
+
function getCurrentDensity() {
|
|
75
|
+
return document.documentElement.getAttribute('data-density');
|
|
76
|
+
}
|
|
77
|
+
|
|
66
78
|
function syncAllThemes() {
|
|
67
79
|
const theme = getCurrentTheme();
|
|
68
80
|
for (const instance of instances) {
|
|
@@ -70,14 +82,22 @@ function syncAllThemes() {
|
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
function syncAllDensities() {
|
|
86
|
+
const density = getCurrentDensity();
|
|
87
|
+
for (const instance of instances) {
|
|
88
|
+
instance._syncDensity(density);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
|
|
74
93
|
const observer = new MutationObserver(records => {
|
|
75
94
|
if (records.some(r => r.attributeName === 'data-theme')) syncAllThemes();
|
|
95
|
+
if (records.some(r => r.attributeName === 'data-density')) syncAllDensities();
|
|
76
96
|
if (records.some(r => r.attributeName === DEFAULTS_ATTR)) syncDefaultTokens();
|
|
77
97
|
});
|
|
78
98
|
observer.observe(document.documentElement, {
|
|
79
99
|
attributes: true,
|
|
80
|
-
attributeFilter: ['data-theme', DEFAULTS_ATTR]
|
|
100
|
+
attributeFilter: ['data-theme', 'data-density', DEFAULTS_ATTR]
|
|
81
101
|
});
|
|
82
102
|
}
|
|
83
103
|
|
|
@@ -211,9 +231,17 @@ class GlkElement extends HTMLElement {
|
|
|
211
231
|
this._wrapper = document.createElement('div');
|
|
212
232
|
this._wrapper.className = 'glk-wrapper';
|
|
213
233
|
this._wrapper.setAttribute('data-theme', getCurrentTheme());
|
|
234
|
+
this._syncDensity(getCurrentDensity());
|
|
214
235
|
this._shadow.appendChild(this._wrapper);
|
|
215
236
|
|
|
216
237
|
this.render();
|
|
238
|
+
} else {
|
|
239
|
+
// Back in the document. While it was out, the observer did not reach
|
|
240
|
+
// it, so a theme or density switched in the meantime is caught up here
|
|
241
|
+
// — the wrapper's data-theme also sets the color-scheme of the native
|
|
242
|
+
// parts inside.
|
|
243
|
+
this._syncTheme(getCurrentTheme());
|
|
244
|
+
this._syncDensity(getCurrentDensity());
|
|
217
245
|
}
|
|
218
246
|
|
|
219
247
|
// Everything below runs on every connect, not just the first. Moving an
|
|
@@ -249,6 +277,16 @@ class GlkElement extends HTMLElement {
|
|
|
249
277
|
}
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
/** Mirrors data-density from <html>; without one there, the wrapper has none. */
|
|
281
|
+
_syncDensity(density) {
|
|
282
|
+
if (!this._wrapper) return;
|
|
283
|
+
if (density) {
|
|
284
|
+
this._wrapper.setAttribute('data-density', density);
|
|
285
|
+
} else {
|
|
286
|
+
this._wrapper.removeAttribute('data-density');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
252
290
|
/** Subclasses override to build inner DOM inside this._wrapper. */
|
|
253
291
|
render() {}
|
|
254
292
|
|