@jungherz-de/glasskit-elements 1.19.2 → 1.21.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +56 -31
  3. package/SKILL.md +124 -27
  4. package/dist/components/base.js +185 -12
  5. package/dist/components/glk-accordion-item.js +20 -5
  6. package/dist/components/glk-button.js +69 -2
  7. package/dist/components/glk-calendar.js +3 -0
  8. package/dist/components/glk-checkbox.js +12 -2
  9. package/dist/components/glk-input.js +25 -4
  10. package/dist/components/glk-modal.js +99 -21
  11. package/dist/components/glk-pill.js +14 -1
  12. package/dist/components/glk-popover.js +21 -3
  13. package/dist/components/glk-radio.js +26 -2
  14. package/dist/components/glk-range.js +3 -1
  15. package/dist/components/glk-search.js +14 -1
  16. package/dist/components/glk-segmented.js +4 -0
  17. package/dist/components/glk-select.js +8 -2
  18. package/dist/components/glk-textarea.js +8 -2
  19. package/dist/components/glk-toggle.js +18 -11
  20. package/dist/glasskit-elements.esm.js +528 -68
  21. package/dist/glasskit-elements.js +528 -68
  22. package/dist/glasskit-elements.min.js +1 -1
  23. package/package.json +3 -3
  24. package/src/base.js +184 -11
  25. package/src/components/buttons/glk-button.js +69 -2
  26. package/src/components/containers/glk-accordion-item.js +20 -5
  27. package/src/components/feedback/glk-modal.js +99 -21
  28. package/src/components/feedback/glk-popover.js +21 -3
  29. package/src/components/forms/glk-calendar.js +3 -0
  30. package/src/components/forms/glk-checkbox.js +12 -2
  31. package/src/components/forms/glk-input.js +25 -4
  32. package/src/components/forms/glk-radio.js +26 -2
  33. package/src/components/forms/glk-range.js +3 -1
  34. package/src/components/forms/glk-search.js +14 -1
  35. package/src/components/forms/glk-segmented.js +4 -0
  36. package/src/components/forms/glk-select.js +8 -2
  37. package/src/components/forms/glk-textarea.js +8 -2
  38. package/src/components/forms/glk-toggle.js +18 -11
  39. package/src/components/navigation/glk-pill.js +14 -1
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.19.2) wrapping GlassKit CSS v1.19.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.
3
+ description: GlassKit Elements is a vanilla-JS Web Components library (v1.21.0) wrapping GlassKit CSS v1.21.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
@@ -18,7 +18,7 @@ description: GlassKit Elements is a vanilla-JS Web Components library (v1.19.2)
18
18
  npm install @jungherz-de/glasskit-elements @jungherz-de/glasskit
19
19
  ```
20
20
 
21
- Peer dependency `@jungherz-de/glasskit >=1.19.1` is required — 1.9.0 is the release that made the stylesheet splittable, which is what lets document-level branding reach the elements at all.
21
+ Peer dependency `@jungherz-de/glasskit >=1.21.0` is required — 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
 
@@ -37,8 +37,9 @@ import { GlkButton } from '@jungherz-de/glasskit-elements/components/glk-button.
37
37
  ```
38
38
 
39
39
  Every element has its own entry at `components/glk-{name}.js`; they import `./base.js` and leave `@jungherz-de/glasskit/glasskit-styles.js` external (bundler or import map resolves it). Importing one pulls that
40
- file plus a single shared chunk (`base.js` + the GlassKit stylesheet, ~48 KB) rather than
41
- the 112 KB full bundle; several imports share that chunk. Each module registers its
40
+ file plus `base.js` (~16 KB, shared by all of them) and GlassKit's `glasskit-styles.js`
41
+ (~70 KB, resolved once by your project) rather than the 221 KB full bundle; several imports
42
+ share both. Each module registers its
42
43
  custom element as a side effect, so a bare `import '…'` is enough.
43
44
 
44
45
  ### CDN
@@ -88,6 +89,14 @@ function toggleTheme() {
88
89
 
89
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.
90
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
+
91
100
  ---
92
101
 
93
102
  ## 2. Core Concepts
@@ -97,16 +106,17 @@ A single module-level `MutationObserver` watches `data-theme` on `<html>` and sy
97
106
  | Tag prefix | `glk-*` (analogous to the `glass-*` CSS prefix) |
98
107
  | Rendering | Shadow DOM (`mode: 'open'`) with `adoptedStyleSheets` |
99
108
  | 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 |
100
- | Theme sync | Global MutationObserver on `<html data-theme>` |
109
+ | Theme sync | Global MutationObserver on `<html data-theme>` and `<html data-density>` |
101
110
  | Events | Custom `glk-*` events, all `bubbles: true, composed: true` |
102
- | Form participation | `GlkFormElement` uses `ElementInternals` (`static formAssociated = true`) |
111
+ | Form participation | `GlkFormElement` uses `ElementInternals` (`static formAssociated = true`): value, reset and — since 1.20.0 — the inner field's validity |
112
+ | 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 |
103
113
  | API style | Declarative HTML attributes + reflected JS properties |
104
114
 
105
115
  Custom properties (`--gl-*`) defined on `:root` or `<html>` pass through shadow boundaries by inheritance, so custom theming works with a single global stylesheet.
106
116
 
107
117
  ### Building your own element (since 1.14.0)
108
118
 
109
- `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.
119
+ `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.
110
120
 
111
121
  ```js
112
122
  class DemoCounter extends GlkElement {
@@ -129,7 +139,7 @@ customElements.define('demo-counter', DemoCounter);
129
139
 
130
140
  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.
131
141
 
132
- 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`.
142
+ 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`.
133
143
 
134
144
  ### Light-DOM children (since 1.12.0)
135
145
 
@@ -152,6 +162,8 @@ listeners and its theme-sync registration are re-armed on every connect, so it k
152
162
  working. Before 1.12.0 they were torn down on disconnect and never restored: the
153
163
  element still looked right but no longer fired events.
154
164
 
165
+ 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.
166
+
155
167
  ### Branding (since 1.9.0)
156
168
 
157
169
  Override `--gl-*` tokens anywhere in the document — a brand stylesheet, a `<style>` block,
@@ -185,6 +197,23 @@ this module puts them on the document once, wrapped in `@layer glasskit-defaults
185
197
  If your app assigns `document.adoptedStyleSheets = [...]` wholesale at some later point,
186
198
  re-append the existing entries rather than replacing them, or the defaults are lost.
187
199
 
200
+ **Only the tokens, not `color-scheme` (since 1.20.0).** GlassKit's token blocks also set
201
+ `color-scheme`; on the document that switched the whole page to dark — unstyled text white,
202
+ links light blue, native controls dark — on pages that never asked for GlassKit's theme. The
203
+ layer now carries the `--gl-*` tokens only, and every element sets `color-scheme` on its own
204
+ theme wrapper, so native parts inside it (a select's list, a date picker) match the element.
205
+
206
+ **Opting out.** The token names are global, so a component of the page that reads
207
+ `var(--gl-…, fallback)` sees GlassKit's values. A page that declares the tokens itself, or
208
+ wants them in part of the page only, switches the defaults off — the attribute is watched:
209
+
210
+ ```html
211
+ <html data-glk-defaults="off">
212
+ ```
213
+
214
+ The elements then inherit the tokens from `glasskit.css` or from a block of the page's own,
215
+ for instance on a container around them.
216
+
188
217
  ### Passing icons (fixed in 1.10.0)
189
218
 
190
219
  Pass an icon as a **direct child** — the `<svg>` itself, not wrapped:
@@ -277,6 +306,9 @@ Default slot: icon SVG (24×24).
277
306
 
278
307
  Events: `glk-click` when clicked.
279
308
 
309
+ `aria-expanded`, `aria-haspopup` and `aria-pressed` set on the element are handed on to the
310
+ native button inside (since 1.20.0) — `<glk-popover>` sets `aria-expanded` on its trigger.
311
+
280
312
  ---
281
313
 
282
314
  ### 3.3 `<glk-tab-bar>` + `<glk-tab-item>`
@@ -521,10 +553,25 @@ Full-width glass button (56 px). Three variants, four sizes.
521
553
  | `variant` | String | — | `primary`, `secondary`, `tertiary` |
522
554
  | `size` | String | `md` | `sm`, `md`, `lg`, `auto` |
523
555
  | `disabled` | Boolean | `false` | Disabled state |
524
- | `type` | String | `button` | Native button type |
556
+ | `type` | String | `button` | `button`, `submit`, `reset` — submit and reset act on the element's form (since 1.20.0) |
525
557
 
526
558
  Events: `glk-click` (suppressed when disabled).
527
559
 
560
+ **Submitting (since 1.20.0).** The element is form-associated: `type="submit"` submits the
561
+ form it sits in and `type="reset"` resets it. Before 1.20.0 both did nothing — the native
562
+ button lives in the shadow root, outside every form. It acts once the click has finished
563
+ bubbling, so `preventDefault()` on the click stops it, as on a native button; invalid fields
564
+ stop the submit with the browser's message; the submit event's `submitter` is the element;
565
+ `.form` is its form; `element.click()` acts like a click. No implicit submission: Enter in a
566
+ `<glk-input>` does not submit the form.
567
+
568
+ **Upgrading from ≤ 1.19:** code that set `type="submit"` and did the work in a `glk-click`
569
+ handler now gets a real submit as well — handle the form's `submit` event (with
570
+ `preventDefault()` in an SPA) or drop `type="submit"`, or the browser reloads the page.
571
+
572
+ `aria-expanded`, `aria-haspopup` and `aria-pressed` set on the element are handed on to the
573
+ native button inside, which is what screen readers read (since 1.20.0).
574
+
528
575
  Default slot: button label (text or SVG + text).
529
576
 
530
577
  ---
@@ -545,9 +592,14 @@ Form-associated checkbox with glass styling.
545
592
  | `disabled` | Boolean | Disabled state |
546
593
  | `name` | String | Form field name |
547
594
  | `value` | String | Value submitted when checked (default `"on"`) |
595
+ | `required` | Boolean | The form does not submit until it is ticked (since 1.20.0) |
548
596
 
549
597
  Events: `glk-change` → `{ checked }`; also dispatches native `change`. Property: `.checked`.
550
598
 
599
+ `element.click()` and a `<label>` around the element or naming it tick it, as they do a
600
+ native checkbox (since 1.20.0). A press on the element's empty width (it is a block, the
601
+ control narrower) still does nothing.
602
+
551
603
  ---
552
604
 
553
605
  ### 3.12 `<glk-input>`
@@ -565,10 +617,10 @@ Text input with label + hint.
565
617
  | `label` | String | Label above the input |
566
618
  | `type` | String | Native input type (`text`, `email`, `password`, `number`, …) |
567
619
  | `placeholder` | String | Placeholder text |
568
- | `hint` | String | Helper text below the input |
569
- | `error` | Boolean | Error styling (red border + red hint) |
620
+ | `hint` | String | Helper text below the input, read as the field's description (`aria-describedby`) |
621
+ | `error` | Boolean | Error styling (red border + red hint) and `aria-invalid` on the field — it does not block the form |
570
622
  | `disabled` | Boolean | Disabled state |
571
- | `required` | Boolean | Required field |
623
+ | `required` | Boolean | Required field — the form does not submit while it is empty (since 1.20.0) |
572
624
  | `name` | String | Form field name |
573
625
  | `value` | String | Current value |
574
626
  | `min`, `max`, `step` | String | Passed to the inner field — range and step of the picker for dates, times and numbers (since 1.17.0) |
@@ -577,7 +629,18 @@ Text input with label + hint.
577
629
 
578
630
  Events: `glk-input` → `{ value }`, `glk-change` → `{ value }`. Native `input` / `change` also dispatched. Property: `.value`.
579
631
 
580
- The inner field's validity is not reported to the surrounding form: `required`, `pattern` and `min` constrain the field and its picker, but they do not block a submit — validate before sending.
632
+ **Validity (since 1.20.0).** The element reports its inner field's validity to the form:
633
+ `required`, `pattern`, `min`/`max`, `minlength`, `type="email"` … make `form.checkValidity()`
634
+ false and stop a submit at the first invalid field with the browser's own message;
635
+ `reportValidity()` focuses the field. `element.validity`, `.validationMessage`,
636
+ `.willValidate`, `.checkValidity()` and `.reportValidity()` answer on the element. Before
637
+ 1.20.0 none of this reached the form. `error` stays a marker: a server's verdict arrives that
638
+ way, and it must not block the next attempt.
639
+
640
+ **Label and focus (since 1.20.0).** The `label` is tied to the field: it names it for screen
641
+ readers, and a click on it focuses it — as do `element.focus()` and a `<label for>` naming the
642
+ element. An outer `<label for>` focuses, but cannot name, the field inside the shadow root;
643
+ use `label` for the name.
581
644
 
582
645
  ---
583
646
 
@@ -598,6 +661,7 @@ Radio button. Group multiple with the same `name`.
598
661
  | `value` | String | Value submitted when checked |
599
662
  | `checked` | Boolean | Checked state |
600
663
  | `disabled` | Boolean | Disabled state |
664
+ | `required` | Boolean | On any radio of the group: the group is required, any checked radio satisfies it (since 1.20.0) |
601
665
 
602
666
  Events: `glk-change` → `{ checked, value }`.
603
667
 
@@ -660,6 +724,7 @@ Search input with leading icon.
660
724
  | Attribute | Type | Description |
661
725
  |---|---|---|
662
726
  | `placeholder` | String | Placeholder text |
727
+ | `label` | String | Accessible name of the field — it has no visible label (since 1.20.0) |
663
728
  | `value` | String | Current value |
664
729
  | `name` | String | Form field name |
665
730
  | `disabled` | Boolean | Disabled state |
@@ -683,10 +748,11 @@ Dropdown select. Pass native `<option>` elements as children.
683
748
 
684
749
  | Attribute | Type | Description |
685
750
  |---|---|---|
686
- | `label` | String | Label text |
751
+ | `label` | String | Label text, tied to the field |
687
752
  | `name` | String | Form field name |
688
753
  | `value` | String | Selected value |
689
754
  | `disabled` | Boolean | Disabled state |
755
+ | `required` | Boolean | With an empty first option (`value=""`), the form does not submit until a choice is made (since 1.20.0) |
690
756
 
691
757
  Children: native `<option>` elements, read from the light DOM.
692
758
 
@@ -755,8 +821,17 @@ Switch-style toggle (form-associated).
755
821
  | `disabled` | Boolean | Disabled state |
756
822
  | `name` | String | Form field name |
757
823
  | `value` | String | Submitted value when checked (default `"on"`) |
824
+ | `required` | Boolean | Must be on before the form submits (since 1.20.0) |
825
+
826
+ Events: `glk-change` → `{ checked }`. Property: `.checked`.
758
827
 
759
- Events: `glk-change` → `{ checked }`. Property: `.checked`. Has `role="switch"` and syncs `aria-checked`.
828
+ **One control (since 1.20.0).** The switch is the native checkbox inside, with
829
+ `role="switch"`, named by `label`. The element itself no longer carries `role`,
830
+ `aria-checked` or `aria-disabled` — before 1.20.0 screen readers met a switch with a checkbox
831
+ inside. Select and style on `[checked]` / `[disabled]`; in tests,
832
+ `getByRole('switch', { name })` finds the input and `.click()`, `.check()` work on it.
833
+ `element.click()` and a `<label>` around the element or naming it toggle it, like a native
834
+ checkbox. Without `label` the element is exactly its track, 52 × 30 px (it was 64 × 34).
760
835
 
761
836
  ---
762
837
 
@@ -783,7 +858,7 @@ Modal dialog with overlay, title, body, and footer actions.
783
858
  | Attribute | Type | Description |
784
859
  |---|---|---|
785
860
  | `open` | Boolean | Visibility state (reflected as `.is-active` on the overlay) |
786
- | `title` | String | Header title |
861
+ | `title` | String | Header title; names the dialog |
787
862
 
788
863
  Slots:
789
864
  - default — modal body content
@@ -791,7 +866,17 @@ Slots:
791
866
 
792
867
  Methods: `.show()`, `.close()`. Property: `.open`. Events: `glk-close`.
793
868
 
794
- Closes automatically on overlay click and <kbd>Escape</kbd>.
869
+ Closes automatically on overlay click and <kbd>Escape</kbd>, with its fade, and emits
870
+ `glk-close` then; `.close()` and removing `open` do not emit.
871
+
872
+ **A native `<dialog>` (since 1.20.0).** The overlay is opened with `showModal()`: it lies in
873
+ the top layer, the page behind it is inert for pointer, keyboard and screen readers, focus
874
+ moves in and returns to where it came from on close, and the content is out of reach while
875
+ closed. Chrome focuses the first field of the content, Safari the first action — put
876
+ `autofocus` on the element that should get the focus, in the content or on an action.
877
+
878
+ While it is open, **everything outside is inert** — a `<glk-toast>` shown then is neither
879
+ clickable nor announced. Give feedback inside the modal, or close it first.
795
880
 
796
881
  ---
797
882
 
@@ -822,6 +907,9 @@ Slots:
822
907
  Methods: `.show()`, `.close()`, `.toggle()`. Property: `.open`.
823
908
  Events: `glk-open`, `glk-close`.
824
909
 
910
+ The trigger gets `aria-expanded`, kept in step with `open` and set again when the trigger is
911
+ swapped (since 1.20.0); `<glk-button>` and `<glk-pill>` hand it on to their native button.
912
+
825
913
  **Naming warning:** the toggle method is deliberately called `.toggle()`, **not** `.togglePopover()` — the latter collides with the native `HTMLElement.togglePopover()` API from the HTML Popover specification.
826
914
 
827
915
  ---
@@ -908,6 +996,10 @@ Collapsible sections.
908
996
 
909
997
  Default slot: body content. Events: `glk-toggle` → `{ open }`. Property: `.open`.
910
998
 
999
+ The trigger carries `aria-expanded` and `aria-controls`; a closed section is `inert`, so its
1000
+ links and fields are out of the tab order and the accessibility tree until it opens (since
1001
+ 1.20.0 — before, they were only squeezed to zero height and Tab still reached them).
1002
+
911
1003
  ---
912
1004
 
913
1005
  ### 3.24 `<glk-list>` + `<glk-list-item>`
@@ -1216,8 +1308,9 @@ Events: `glk-change { dataUrl, width, height, size }` after a pick, on remove wi
1216
1308
  <form id="contract-form">
1217
1309
  <glk-title>New Contract</glk-title>
1218
1310
 
1311
+ <!-- required stops the submit with the browser's message (since 1.20.0) -->
1219
1312
  <glk-input label="Contract Name" name="name" required></glk-input>
1220
- <glk-select label="Category" name="category">
1313
+ <glk-select label="Category" name="category" required>
1221
1314
  <option value="">Please select…</option>
1222
1315
  <option value="insurance">Insurance</option>
1223
1316
  <option value="rental">Rental</option>
@@ -1350,7 +1443,7 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
1350
1443
 
1351
1444
  ### Always follow
1352
1445
 
1353
- 1. **`data-theme` on `<html>`** — never on `<body>` or individual elements; the global MutationObserver only watches `<html>`.
1446
+ 1. **`data-theme` and `data-density` on `<html>`** — never on `<body>` or individual elements; the global MutationObserver only watches `<html>`.
1354
1447
  2. **Form elements belong inside a `<form>`** — otherwise `ElementInternals.setFormValue()` has no effect and `new FormData()` will be empty.
1355
1448
  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.
1356
1449
  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.
@@ -1369,6 +1462,7 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
1369
1462
  | `<glk-list>` inside `<glk-popover>` without `bare` | Add `bare` to the inner list so only the popover surface has glass. |
1370
1463
  | `document.querySelector('.glass-modal').classList.add('is-active')` | Set `<glk-modal>.open = true` instead — the element manages `.is-active` internally. |
1371
1464
  | `data-theme` directly on a `<glk-*>` element | Set it on `<html>`; theme propagates automatically through the MutationObserver. |
1465
+ | 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. |
1372
1466
  | Form element outside `<form>` | Wrap in a `<form>`; `ElementInternals` needs a form owner. |
1373
1467
  | 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. |
1374
1468
  | Forgetting `slot="trigger"` on the popover trigger element | Without it, the popover's internal click handler can't identify the trigger and never toggles. |
@@ -1376,6 +1470,9 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
1376
1470
  | `<glk-tab-bar>` without `.glass-bg--has-tab-bar` on the outer background | Tab bar covers bottom content. Add the modifier to the outer `.glass-bg` container. |
1377
1471
  | `<glk-tab-bar floating>` without `<glk-tab-dock>` wrapper | Floating bar relies on the dock for fixed centered positioning. Always wrap it in `<glk-tab-dock>`. |
1378
1472
  | `<glk-tab-dock>` without `.glass-bg--has-tab-bar-floating` on the outer background | Use `--has-tab-bar-floating` (not `--has-tab-bar`) for the floating variant — the padding budget differs. |
1473
+ | `<glk-toast>` shown while a `<glk-modal>` is open | Everything outside the open modal is inert — the toast is neither clickable nor announced. Show the message inside the modal, or close it first. |
1474
+ | `glk-toggle[aria-checked="true"]` / `[role="switch"]` on the host | Since 1.20.0 the host carries neither; use `glk-toggle[checked]`. The role sits on the native checkbox inside. |
1475
+ | Validating `<glk-input required>` by hand before submit | Not needed since 1.20.0 — the form's own validation stops the submit. |
1379
1476
 
1380
1477
  ---
1381
1478
 
@@ -1403,14 +1500,14 @@ All `glk-*` events bubble and are `composed: true`, so they pierce shadow bounda
1403
1500
  | `<glk-status>` | Content | `message` | — | — |
1404
1501
  | `<glk-title>` | Content | — | default | — |
1405
1502
  | `<glk-button>` | Buttons | `variant`, `size`, `disabled`, `type` | default | `glk-click` |
1406
- | `<glk-checkbox>` | Forms | `label`, `checked`, `disabled`, `name`, `value` | — | `glk-change` |
1503
+ | `<glk-checkbox>` | Forms | `label`, `checked`, `disabled`, `name`, `value`, `required` | — | `glk-change` |
1407
1504
  | `<glk-input>` | Forms | `label`, `type`, `placeholder`, `hint`, `error`, `disabled`, `required`, `name`, `value`, `min`, `max`, `step`, `minlength`, `maxlength`, `pattern`, `autocomplete`, `inputmode` | — | `glk-input`, `glk-change` |
1408
- | `<glk-radio>` | Forms | `label`, `name`, `value`, `checked`, `disabled` | — | `glk-change` |
1505
+ | `<glk-radio>` | Forms | `label`, `name`, `value`, `checked`, `disabled`, `required` | — | `glk-change` |
1409
1506
  | `<glk-range>` | Forms | `label`, `min`, `max`, `value`, `step`, `name`, `disabled` | — | `glk-input`, `glk-change` |
1410
- | `<glk-search>` | Forms | `placeholder`, `value`, `name`, `disabled` | — | `glk-input`, `glk-change` |
1411
- | `<glk-select>` | Forms | `label`, `name`, `value`, `disabled` | default (`<option>`) | `glk-change` |
1507
+ | `<glk-search>` | Forms | `placeholder`, `label`, `value`, `name`, `disabled` | — | `glk-input`, `glk-change` |
1508
+ | `<glk-select>` | Forms | `label`, `name`, `value`, `disabled`, `required` | default (`<option>`) | `glk-change` |
1412
1509
  | `<glk-textarea>` | Forms | `label`, `rows`, `placeholder`, `name`, `value`, `disabled`, `required` | — | `glk-input`, `glk-change` |
1413
- | `<glk-toggle>` | Forms | `label`, `checked`, `disabled`, `name`, `value` | — | `glk-change` |
1510
+ | `<glk-toggle>` | Forms | `label`, `checked`, `disabled`, `name`, `value`, `required` | — | `glk-change` |
1414
1511
  | `<glk-modal>` | Feedback | `open`, `title` | default, `actions` | `glk-close` |
1415
1512
  | `<glk-popover>` | Feedback | `open`, `placement` | `trigger`, default | `glk-open`, `glk-close` |
1416
1513
  | `<glk-progress>` | Feedback | `value`, `label`, `variant`, `size` | — | — |
@@ -1538,18 +1635,18 @@ See the class-based [GlassKit CSS `SKILL.md`](https://github.com/JUNGHERZ/GlassK
1538
1635
  |---|---|
1539
1636
  | Base class | `src/base.js` → `GlkElement`, `GlkFormElement` |
1540
1637
  | Theme sync | Single module-level `MutationObserver` in `base.js` |
1541
- | Adopted stylesheets | `glassSheet` (from `@jungherz-de/glasskit/glasskit-styles.js`) + module-level `hostSheet` / `inlineHostSheet` |
1638
+ | Adopted stylesheets | `componentsSheet` (from `@jungherz-de/glasskit/glasskit-styles.js`) + module-level `hostSheet` / `inlineHostSheet` (they also set `color-scheme` per theme) + an element's own `hostStyles` |
1542
1639
  | Per-component structure | One `.js` file per element in `src/components/{category}/glk-{name}.js` |
1543
1640
  | Barrel | `src/index.js` — exports and registers all 36 elements, and exports `GlkElement` / `GlkFormElement` (since 1.14.0) |
1544
1641
  | Build | Rollup → IIFE (`glasskit-elements.js`), minified IIFE, ESM (`glasskit-elements.esm.js`), and per-element ESM in `dist/components/` with `base.js` as a stable entry |
1545
1642
 
1546
1643
  Each element is a subclass of `GlkElement` (or `GlkFormElement` for form controls) and follows a consistent lifecycle:
1547
1644
 
1548
- 1. `constructor()` — attach open shadow, adopt GlassKit stylesheet + host display sheet
1645
+ 1. `constructor()` — attach open shadow (`delegatesFocus` for form elements with one field), adopt GlassKit stylesheet + host display sheet
1549
1646
  2. `connectedCallback()` — create theme wrapper, call `render()`, call `setupEvents()`, register for global theme sync
1550
1647
  3. `render()` — build shadow DOM tree (override per component)
1551
1648
  4. `setupEvents()` — attach DOM listeners (override per component)
1552
1649
  5. `attributeChangedCallback()` — call `onAttributeChanged()` (override per component)
1553
1650
  6. `disconnectedCallback()` — unregister, call `teardownEvents()`
1554
1651
 
1555
- The `GlkFormElement` subclass adds `ElementInternals` for native form participation via `this.setFormValue(value)`, plus `formResetCallback` → `resetValue()` and `formStateRestoreCallback` → `restoreValue()`.
1652
+ The `GlkFormElement` subclass adds `ElementInternals` for native form participation via `this.setFormValue(value)`, plus `formResetCallback` → `resetValue()` and `formStateRestoreCallback` → `restoreValue()`. Since 1.20.0 it mirrors the validity of the native field returned by `get _validityField()` onto the host (`syncValidity()`, run after every `setFormValue()` and attribute change); `<glk-radio>` overrides it with the group's verdict.