@jungherz-de/glasskit-elements 1.22.2 → 1.23.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 CHANGED
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.23.0] – 2026-10-14
11
+
12
+ Versions realign with GlassKit at 1.23.0, which this release is built against (peer dependency `>=1.23.0`).
13
+
14
+ ### Added
15
+
16
+ - **List text in `<glk-list-item>` can wrap, decided once for a whole app.** Title and subtitle were cut on a phone — one line with an ellipsis, `wrap` clamped at three lines — and the rules sat in the shadow root, out of a project's reach. GlassKit 1.23.0's list tokens reach into the element by inheritance: `--gl-list-title-white-space` and `--gl-list-subtitle-white-space` (`normal` wraps) and `--gl-list-subtitle-lines` (the clamp of `wrap`, `none` shows every line), set on `:root`, one `<glk-list>` or one row. Unset, every row renders as before. A workaround through the `hostStyles` hook can go. ([GlassKit #10](https://github.com/JUNGHERZ/GlassKit/issues/10))
17
+
18
+ ### Fixed
19
+
20
+ - **A long word in a `wrap` subtitle breaks instead of being clipped, and a `wrap` row without a subtitle keeps its title centred** — both from GlassKit 1.23.0. An e-mail address ran 310 px wide out of a 198 px column; an empty subtitle hidden with `hidden` still took a line's gap. ([GlassKit #10](https://github.com/JUNGHERZ/GlassKit/issues/10))
21
+
22
+ Measured in Chromium, WebKit and Firefox at a 360 px viewport: `<glk-list-item>` gives the same line counts and row heights as plain GlassKit markup, with and without the tokens. Docs, showcase and landing pages render pixel-identical to 1.22.3.
23
+
24
+ ---
25
+
26
+ ## [1.22.3] – 2026-10-08
27
+
28
+ GlassKit is unchanged; the peer dependency stays `>=1.22.2`.
29
+
30
+ ### Fixed
31
+
32
+ - **A `<fieldset disabled>` disables the form elements inside it.** The browser already counted them as disabled — `:disabled`, left out of `FormData` — but their native controls live in the shadow root, where no fieldset reaches, and stayed enabled: every field was reachable with Tab, typing and clicks changed the values, and `<glk-button type="submit">` fired `glk-click` and sent the form, empty. The elements now take the state from `formDisabledCallback`, which the browser calls for the element's own `disabled` attribute and for a fieldset alike, also when an element moves into or out of one. Inside a disabled fieldset they look disabled, drop out of the tab order, take no input and send nothing; a required empty field is barred from validation and counts again once the fieldset is enabled. The `disabled` property still reflects the attribute only, as on a native control. Radios skip a fieldset-disabled member of their group with the arrow keys, and the tab stop moves off it. ([#15](https://github.com/JUNGHERZ/GlassKit-Elements/issues/15))
33
+ - **`<glk-segmented disabled>` and `<glk-calendar disabled>` work.** Neither handled the attribute: the browser counted them as disabled and left them out of the form data, but a click still changed the value. Now the segmented row and the calendar's arrows and days are disabled; an option's own `disabled` stays as it is. ([#15](https://github.com/JUNGHERZ/GlassKit-Elements/issues/15))
34
+
35
+ ### Added
36
+
37
+ - **A `disabled` property on every form element.** `<glk-search>`, `<glk-range>`, `<glk-segmented>` and `<glk-calendar>` had none; it sits on `GlkFormElement` now. A subclass of `GlkFormElement` overrides `applyDisabled(disabled)` to hand the state to its native controls; it runs after the first render and on every change.
38
+
39
+ Measured in Chromium and WebKit with every form element inside `<form><fieldset disabled>`, 21 checks per engine: all native controls disabled with GlassKit's disabled look, Tab passes them, clicks and typing change nothing, no submit, no `glk-click`; enabled again, everything works and the button submits once; own attribute and fieldset combine in every order; an element moved in or created inside is disabled from the start; a calendar month shown while disabled stays disabled. Docs, showcase and landing pages render pixel-identical to 1.22.2.
40
+
41
+ ---
42
+
10
43
  ## [1.22.2] – 2026-10-08
11
44
 
12
45
  ### Fixed
@@ -807,7 +840,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
807
840
 
808
841
  ---
809
842
 
810
- [Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.22.2...HEAD
843
+ [Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.23.0...HEAD
844
+ [1.23.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.23.0
845
+ [1.22.3]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.3
811
846
  [1.22.2]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.2
812
847
  [1.22.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.1
813
848
  [1.22.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.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.22.2-lightgrey?style=flat-square" alt="Changelog"></a>
8
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.23.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
 
@@ -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.22/glasskit.min.css">
69
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.23/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.22/glasskit.min.css">
107
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.23/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>
@@ -152,7 +152,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
152
152
 
153
153
  ### Form Elements
154
154
 
155
- All form components support `name`, `value`, `disabled` and participate in native `<form>` submission via `ElementInternals` — except `<glk-image-picker>`, which hands its result to `glk-change` instead: a data URL is no form value. Since 1.20.0 they also report their validity to the form (`required`, `pattern`, `min`, `type="email"` … stop a submit with the browser's own message), their `label` names the field for screen readers, and a click on it, `element.focus()` or a `<label for>` lands in the field.
155
+ All form components support `name`, `value`, `disabled` and participate in native `<form>` submission via `ElementInternals` — except `<glk-image-picker>`, which hands its result to `glk-change` instead: a data URL is no form value. Since 1.20.0 they also report their validity to the form (`required`, `pattern`, `min`, `type="email"` … stop a submit with the browser's own message), their `label` names the field for screen readers, and a click on it, `element.focus()` or a `<label for>` lands in the field. Since 1.22.3 a `<fieldset disabled>` around them disables them, as it does native controls.
156
156
 
157
157
  | Component | Description | Key Attributes |
158
158
  |---|---|---|
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.22.2) wrapping GlassKit CSS v1.22.2. 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.23.0) wrapping GlassKit CSS v1.23.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.22.2)
18
18
  npm install @jungherz-de/glasskit-elements @jungherz-de/glasskit
19
19
  ```
20
20
 
21
- Peer dependency `@jungherz-de/glasskit >=1.22.2` is required — 1.22.2 hands a click on a disabled control in an input affix to the field, 1.22.1 lets date and time fields shrink in narrow columns, 1.22.0 brings the input affixes behind the `prefix` / `suffix` slots, 1.21.2 gives disabled controls their look, 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.
21
+ Peer dependency `@jungherz-de/glasskit >=1.23.0` is required — 1.23.0 lets list text wrap app-wide through tokens, 1.22.2 hands a click on a disabled control in an input affix to the field, 1.22.1 lets date and time fields shrink in narrow columns, 1.22.0 brings the input affixes behind the `prefix` / `suffix` slots, 1.21.2 gives disabled controls their look, 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
 
@@ -123,6 +123,7 @@ addEventListener('afterprint', () => {
123
123
  | Events | Custom `glk-*` events, all `bubbles: true, composed: true` |
124
124
  | Form participation | `GlkFormElement` uses `ElementInternals` (`static formAssociated = true`): value, reset and — since 1.20.0 — the inner field's validity |
125
125
  | 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 |
126
+ | Disabled | Form elements follow their own `disabled` and a `<fieldset disabled>` around them, as native controls do (since 1.22.3); the `disabled` property reflects the attribute |
126
127
  | API style | Declarative HTML attributes + reflected JS properties |
127
128
 
128
129
  Custom properties (`--gl-*`) defined on `:root` or `<html>` pass through shadow boundaries by inheritance, so custom theming works with a single global stylesheet.
@@ -152,7 +153,7 @@ customElements.define('demo-counter', DemoCounter);
152
153
 
153
154
  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.
154
155
 
155
- 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`.
156
+ 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()`, a `disabled` property and `static formAssociated = true`. Override `applyDisabled(disabled)` to disable your native controls (since 1.22.3): it runs after the first render and whenever the element's own `disabled` or a `<fieldset disabled>` around it changes — no fieldset reaches into the shadow root by itself.
156
157
 
157
158
  ### Light-DOM children (since 1.12.0)
158
159
 
@@ -706,7 +707,7 @@ Slider input.
706
707
  | `name` | String | Form field name |
707
708
  | `disabled` | Boolean | Disabled state |
708
709
 
709
- Events: `glk-input`, `glk-change`. Property: `.value`.
710
+ Events: `glk-input`, `glk-change`. Properties: `.value`, `.disabled` (since 1.22.3).
710
711
 
711
712
  ---
712
713
 
@@ -726,7 +727,7 @@ Search input with leading icon.
726
727
  | `name` | String | Form field name |
727
728
  | `disabled` | Boolean | Disabled state |
728
729
 
729
- Events: `glk-input`, `glk-change`.
730
+ Events: `glk-input`, `glk-change`. Properties: `.value`, `.disabled` (since 1.22.3).
730
731
 
731
732
  ---
732
733
 
@@ -1058,6 +1059,18 @@ iOS-style grouped settings list. Items carry a leading icon, title + optional su
1058
1059
  | `leading` | Icon slot (24×24 SVG recommended, or 32×32 with `leading-lg`; use `stroke: currentColor`) |
1059
1060
  | `trailing` | Chevron, badge, or button — rendered after `detail` value |
1060
1061
 
1062
+ #### Wrapping text (since 1.23.0)
1063
+
1064
+ Title and subtitle keep to one line, and `wrap` clamps the subtitle at three lines. GlassKit's list tokens change that for a whole app, one list or one row; they are inherited into the element's shadow root: `--gl-list-title-white-space` and `--gl-list-subtitle-white-space` (`nowrap` by default, `normal` wraps) and `--gl-list-subtitle-lines` (the clamp of `wrap`, `3` unless set, `none` = every line). A word wider than the column (an e-mail address) breaks instead of running out of the row. For breaks at syllables set `hyphens: auto; -webkit-hyphens: auto` on `glk-list` or the page; it is inherited too.
1065
+
1066
+ ```css
1067
+ :root {
1068
+ --gl-list-title-white-space: normal;
1069
+ --gl-list-subtitle-white-space: normal;
1070
+ --gl-list-subtitle-lines: none;
1071
+ }
1072
+ ```
1073
+
1061
1074
  #### Events
1062
1075
 
1063
1076
  `<glk-list-item>` emits `glk-click` when clicked, but only if `interactive` is set.
@@ -1086,8 +1099,9 @@ A small, exclusive choice as one control (since 1.15.0) — traffic light, morni
1086
1099
  | `overflow` | String | What happens when the options do not fit: `scroll` — one row that scrolls sideways, the chosen option kept in view; `wrap` — breaks into lines. Without it the row stays one line (since 1.17.0) |
1087
1100
  | `label` | String | `aria-label` of the group (no default — set it) |
1088
1101
  | `name` | String | Form field name |
1102
+ | `disabled` | Boolean | The whole row (since 1.22.3); a `<fieldset disabled>` around it disables it too |
1089
1103
 
1090
- Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`. Events: `glk-change` `{ value }` — only on a change made by the user, not on `.value = …`. Part: `group`.
1104
+ Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`, `disabled`. Events: `glk-change` `{ value }` — only on a change made by the user, not on `.value = …`. Part: `group`.
1091
1105
 
1092
1106
  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:
1093
1107
 
@@ -1202,8 +1216,9 @@ One month with a day to pick (since 1.16.0). The days are buttons in a `role="gr
1202
1216
  | `label` | String | `aria-label` of the day group |
1203
1217
  | `prev-label`, `next-label` | String | Names of the nav buttons ("Previous month" / "Next month") |
1204
1218
  | `name` | String | Form field name |
1219
+ | `disabled` | Boolean | Arrows and days (since 1.22.3); a `<fieldset disabled>` around it disables it too |
1205
1220
 
1206
- Events: `glk-change { value }` on a pick by the user; `glk-month { month }` when the user moves to another month. Properties: `month`, `value`, `marks`, `locale`, `label`. Parts: `calendar`, `head`, `title`, `grid`.
1221
+ Events: `glk-change { value }` on a pick by the user; `glk-month { month }` when the user moves to another month. Properties: `month`, `value`, `marks`, `locale`, `label`, `disabled`. Parts: `calendar`, `head`, `title`, `grid`.
1207
1222
 
1208
1223
  ---
1209
1224
 
@@ -238,6 +238,7 @@ class GlkElement extends HTMLElement {
238
238
  this._shadow.appendChild(this._wrapper);
239
239
 
240
240
  this.render();
241
+ if (this.constructor.formAssociated) this._syncDisabled();
241
242
  } else {
242
243
  // Back in the document. While it was out, the observer did not reach
243
244
  // it, so a theme or density switched in the meantime is caught up here
@@ -340,6 +341,28 @@ class GlkElement extends HTMLElement {
340
341
  */
341
342
  refresh() { this.projectLightDom(); }
342
343
 
344
+ /**
345
+ * Form-associated elements only. Such an element is disabled by its own
346
+ * `disabled` attribute or by a <fieldset disabled> around it: the browser
347
+ * counts both — :disabled, left out of the form data — and calls
348
+ * formDisabledCallback() on every change, also when the element moves into
349
+ * or out of such a fieldset. The native controls sit in the shadow root,
350
+ * out of every fieldset's reach, so the state is handed on to them through
351
+ * applyDisabled(). Until 1.22.2 only the element's own attribute reached
352
+ * them: inside a disabled fieldset every field stayed usable, and a submit
353
+ * button sent the form.
354
+ */
355
+ get _actuallyDisabled() { return this.matches(':disabled'); }
356
+
357
+ formDisabledCallback() {
358
+ if (this._initialized) this._syncDisabled();
359
+ }
360
+
361
+ _syncDisabled() { this.applyDisabled(this._actuallyDisabled); }
362
+
363
+ /** Form-associated subclasses override to disable their native controls. */
364
+ applyDisabled(disabled) {}
365
+
343
366
  // ── Utility Methods ──
344
367
 
345
368
  /**
@@ -417,6 +440,17 @@ class GlkFormElement extends GlkElement {
417
440
  if (this._initialized) this.syncValidity();
418
441
  }
419
442
 
443
+ // Reflects the attribute, as on a native control: a disabled fieldset
444
+ // around the element disables it without changing this.
445
+ get disabled() { return this.getBoolAttr('disabled'); }
446
+ set disabled(v) { this.setBoolAttr('disabled', v); }
447
+
448
+ _syncDisabled() {
449
+ super._syncDisabled();
450
+ // A disabled field is barred from validation; enabled again, it counts.
451
+ this.syncValidity();
452
+ }
453
+
420
454
  get form() { return this._internals.form; }
421
455
  get validationMessage() { return this._internals.validationMessage; }
422
456
  get validity() { return this._internals.validity; }
@@ -11,12 +11,13 @@ const FORWARDED_ARIA = ['aria-expanded', 'aria-haspopup', 'aria-pressed'];
11
11
 
12
12
  class GlkButton extends GlkElement {
13
13
  static get observedAttributes() {
14
- return ['variant', 'size', 'disabled', 'type', ...FORWARDED_ARIA];
14
+ return ['variant', 'size', 'type', ...FORWARDED_ARIA];
15
15
  }
16
16
 
17
17
  // The native button sits in the shadow root, outside every form. Form
18
18
  // association gives the element its form, so type="submit" and "reset"
19
- // can act on it (see _activateForm).
19
+ // can act on it (see _activateForm), and a disabled fieldset around it
20
+ // disables it (since 1.22.3; before, it still sent the form).
20
21
  static formAssociated = true;
21
22
 
22
23
  constructor() {
@@ -29,9 +30,6 @@ class GlkButton extends GlkElement {
29
30
  type: this.getAttribute('type') || 'button'
30
31
  });
31
32
 
32
- if (this.getBoolAttr('disabled')) {
33
- this._btn.disabled = true;
34
- }
35
33
  for (const attr of FORWARDED_ARIA) this._forward(attr);
36
34
 
37
35
  this._btn.appendChild(document.createElement('slot'));
@@ -40,7 +38,7 @@ class GlkButton extends GlkElement {
40
38
 
41
39
  setupEvents() {
42
40
  this._onClick = (e) => {
43
- if (this.getBoolAttr('disabled')) {
41
+ if (this._actuallyDisabled) {
44
42
  e.preventDefault();
45
43
  e.stopPropagation();
46
44
  return;
@@ -99,9 +97,6 @@ class GlkButton extends GlkElement {
99
97
  case 'size':
100
98
  this._btn.className = this._computeClasses().join(' ');
101
99
  break;
102
- case 'disabled':
103
- this._btn.disabled = this.getBoolAttr('disabled');
104
- break;
105
100
  case 'type':
106
101
  this._btn.setAttribute('type', this.getAttribute('type') || 'button');
107
102
  break;
@@ -110,6 +105,8 @@ class GlkButton extends GlkElement {
110
105
  }
111
106
  }
112
107
 
108
+ applyDisabled(disabled) { this._btn.disabled = disabled; }
109
+
113
110
  _forward(attr) {
114
111
  const value = this.getAttribute(attr);
115
112
  if (value === null) this._btn.removeAttribute(attr);
@@ -11,8 +11,9 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
11
11
  // the page language, else the browser's), week-start (0 = Sunday … 6 = Saturday;
12
12
  // default from the locale, Monday where the browser cannot say),
13
13
  // label (aria-label of the day group), prev-label / next-label
14
- // (names of the nav buttons, English by default)
15
- // Properties: month, value, marks, locale, label
14
+ // (names of the nav buttons, English by default), disabled
15
+ // (since 1.22.3, as is a disabled fieldset around it)
16
+ // Properties: month, value, marks, locale, label, disabled
16
17
  // Form: associated — a surrounding <form> receives name=value, reset
17
18
  // restores the initial value, like <glk-segmented>
18
19
  // Events: glk-change { value } — on a pick by the user
@@ -104,9 +105,16 @@ class GlkCalendar extends GlkFormElement {
104
105
  day.append(num, dots);
105
106
  this._grid.appendChild(day);
106
107
  }
108
+ this.applyDisabled(this._actuallyDisabled);
107
109
  this._syncValue();
108
110
  }
109
111
 
112
+ // Disabled as a whole: the arrows and every day. The days of another month
113
+ // are built again, so _build() hands the state on to them too.
114
+ applyDisabled(disabled) {
115
+ for (const button of [this._prev, this._next, ...this._days()]) button.disabled = disabled;
116
+ }
117
+
110
118
  _days() { return [...this._grid.querySelectorAll('button[data-value]')]; }
111
119
  _dayFor(iso) { return this._grid.querySelector(`button[data-value="${iso}"]`); }
112
120
 
@@ -5,7 +5,7 @@ const CHECKMARK_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor
5
5
 
6
6
  class GlkCheckbox extends GlkFormElement {
7
7
  static get observedAttributes() {
8
- return ['checked', 'disabled', 'label', 'name', 'value', 'required'];
8
+ return ['checked', 'label', 'name', 'value', 'required'];
9
9
  }
10
10
 
11
11
  static get hostStyles() { return checkControlSheet; }
@@ -31,7 +31,6 @@ class GlkCheckbox extends GlkFormElement {
31
31
  label.appendChild(this._labelEl);
32
32
 
33
33
  if (this.getBoolAttr('checked')) this._input.checked = true;
34
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
35
34
  if (this.getBoolAttr('required')) this._input.required = true;
36
35
 
37
36
  this._defaultChecked = this.getBoolAttr('checked');
@@ -65,9 +64,6 @@ class GlkCheckbox extends GlkFormElement {
65
64
  this._input.checked = this.getBoolAttr('checked');
66
65
  this._syncFormValue();
67
66
  break;
68
- case 'disabled':
69
- this._input.disabled = this.getBoolAttr('disabled');
70
- break;
71
67
  case 'label':
72
68
  this._labelEl.textContent = this.getAttribute('label') || '';
73
69
  break;
@@ -80,6 +76,8 @@ class GlkCheckbox extends GlkFormElement {
80
76
  }
81
77
  }
82
78
 
79
+ applyDisabled(disabled) { this._input.disabled = disabled; }
80
+
83
81
  get _validityField() { return this._input; }
84
82
 
85
83
  _syncFormValue() {
@@ -101,9 +99,6 @@ class GlkCheckbox extends GlkFormElement {
101
99
  if (this._input) this._syncFormValue();
102
100
  }
103
101
 
104
- get disabled() { return this.getBoolAttr('disabled'); }
105
- set disabled(v) { this.setBoolAttr('disabled', v); }
106
-
107
102
  get name() { return this.getAttribute('name'); }
108
103
  set name(v) { this.setAttribute('name', v); }
109
104
 
@@ -33,7 +33,7 @@ affixSheet.replaceSync(`
33
33
 
34
34
  class GlkInput extends GlkFormElement {
35
35
  static get observedAttributes() {
36
- return ['label', 'type', 'placeholder', 'error', 'hint', 'disabled', 'readonly', 'name', 'value', 'required', ...FORWARDED];
36
+ return ['label', 'type', 'placeholder', 'error', 'hint', 'readonly', 'name', 'value', 'required', ...FORWARDED];
37
37
  }
38
38
 
39
39
  static get hostStyles() { return affixSheet; }
@@ -64,7 +64,6 @@ class GlkInput extends GlkFormElement {
64
64
  this._pendingValue = undefined;
65
65
  if (value) this._input.value = value;
66
66
 
67
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
68
67
  if (this.getBoolAttr('readonly')) this._input.readOnly = true;
69
68
  if (this.getBoolAttr('required')) this._input.required = true;
70
69
  for (const attr of FORWARDED) this._forward(attr);
@@ -154,9 +153,6 @@ class GlkInput extends GlkFormElement {
154
153
  }
155
154
  this._applyDescription();
156
155
  break;
157
- case 'disabled':
158
- this._input.disabled = this.getBoolAttr('disabled');
159
- break;
160
156
  case 'readonly':
161
157
  this._input.readOnly = this.getBoolAttr('readonly');
162
158
  break;
@@ -175,6 +171,8 @@ class GlkInput extends GlkFormElement {
175
171
  }
176
172
  }
177
173
 
174
+ applyDisabled(disabled) { this._input.disabled = disabled; }
175
+
178
176
  /** An affix shows, and makes room in the field, only while something is slotted. */
179
177
  _applyAffixes() {
180
178
  for (const affix of [this._prefix, this._suffix]) {
@@ -240,9 +238,6 @@ class GlkInput extends GlkFormElement {
240
238
  this._syncFormValue();
241
239
  }
242
240
 
243
- get disabled() { return this.getBoolAttr('disabled'); }
244
- set disabled(v) { this.setBoolAttr('disabled', v); }
245
-
246
241
  get readOnly() { return this.getBoolAttr('readonly'); }
247
242
  set readOnly(v) { this.setBoolAttr('readonly', v); }
248
243
 
@@ -20,7 +20,7 @@ function ownerForm(el) {
20
20
 
21
21
  /** Only the selected radio is a tab stop; arrow keys move within the group. */
22
22
  function syncGroupTabIndex(group) {
23
- const enabled = group.filter(el => !el.disabled);
23
+ const enabled = group.filter(el => !el._actuallyDisabled);
24
24
  if (!enabled.length) return;
25
25
  const focusable = enabled.find(el => el.checked) || enabled[0];
26
26
  for (const el of group) {
@@ -30,7 +30,7 @@ function syncGroupTabIndex(group) {
30
30
 
31
31
  class GlkRadio extends GlkFormElement {
32
32
  static get observedAttributes() {
33
- return ['checked', 'disabled', 'label', 'name', 'value', 'required'];
33
+ return ['checked', 'label', 'name', 'value', 'required'];
34
34
  }
35
35
 
36
36
  static get hostStyles() { return checkControlSheet; }
@@ -60,7 +60,6 @@ class GlkRadio extends GlkFormElement {
60
60
  label.appendChild(this._labelEl);
61
61
 
62
62
  if (this.getBoolAttr('checked')) this._input.checked = true;
63
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
64
63
 
65
64
  this._defaultChecked = this.getBoolAttr('checked');
66
65
  this._wrapper.appendChild(label);
@@ -78,7 +77,7 @@ class GlkRadio extends GlkFormElement {
78
77
  this._onKeyDown = (e) => {
79
78
  const dir = ARROW_KEYS[e.key];
80
79
  if (!dir || e.ctrlKey || e.metaKey || e.altKey) return;
81
- const group = this._group().filter(el => !el.disabled);
80
+ const group = this._group().filter(el => !el._actuallyDisabled);
82
81
  if (group.length < 2) return;
83
82
  e.preventDefault();
84
83
  const next = group[(group.indexOf(this) + dir + group.length) % group.length];
@@ -160,10 +159,6 @@ class GlkRadio extends GlkFormElement {
160
159
  this._syncFormValue();
161
160
  syncGroupTabIndex(this._group());
162
161
  break;
163
- case 'disabled':
164
- this._input.disabled = this.getBoolAttr('disabled');
165
- syncGroupTabIndex(this._group());
166
- break;
167
162
  case 'label':
168
163
  this._labelEl.textContent = this.getAttribute('label') || '';
169
164
  break;
@@ -178,6 +173,11 @@ class GlkRadio extends GlkFormElement {
178
173
  }
179
174
  }
180
175
 
176
+ applyDisabled(disabled) {
177
+ this._input.disabled = disabled;
178
+ syncGroupTabIndex(this._group());
179
+ }
180
+
181
181
  _syncFormValue() {
182
182
  const val = this.getAttribute('value') || '';
183
183
  this.setFormValue(this._input.checked ? val : null);
@@ -202,9 +202,6 @@ class GlkRadio extends GlkFormElement {
202
202
  syncGroupTabIndex(this._group());
203
203
  }
204
204
 
205
- get disabled() { return this.getBoolAttr('disabled'); }
206
- set disabled(v) { this.setBoolAttr('disabled', v); }
207
-
208
205
  get name() { return this.getAttribute('name'); }
209
206
  set name(v) { this.setAttribute('name', v); }
210
207
 
@@ -3,7 +3,7 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
3
3
 
4
4
  class GlkRange extends GlkFormElement {
5
5
  static get observedAttributes() {
6
- return ['label', 'min', 'max', 'value', 'step', 'name', 'disabled'];
6
+ return ['label', 'min', 'max', 'value', 'step', 'name'];
7
7
  }
8
8
 
9
9
  render() {
@@ -34,8 +34,6 @@ class GlkRange extends GlkFormElement {
34
34
  const name = this.getAttribute('name');
35
35
  if (name) this._input.setAttribute('name', name);
36
36
 
37
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
38
-
39
37
  // The reset value is the attribute's; a value set as a property before
40
38
  // render() has waited for it and comes on top.
41
39
  this._defaultValue = this._input.value;
@@ -87,12 +85,11 @@ class GlkRange extends GlkFormElement {
87
85
  case 'name':
88
86
  this._input.setAttribute('name', this.getAttribute('name') || '');
89
87
  break;
90
- case 'disabled':
91
- this._input.disabled = this.getBoolAttr('disabled');
92
- break;
93
88
  }
94
89
  }
95
90
 
91
+ applyDisabled(disabled) { this._input.disabled = disabled; }
92
+
96
93
  _updateValueDisplay() {
97
94
  this._valueEl.textContent = `${this._input.value}%`;
98
95
  }
@@ -5,7 +5,7 @@ const SEARCH_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" s
5
5
 
6
6
  class GlkSearch extends GlkFormElement {
7
7
  static get observedAttributes() {
8
- return ['placeholder', 'name', 'value', 'disabled', 'label'];
8
+ return ['placeholder', 'name', 'value', 'label'];
9
9
  }
10
10
 
11
11
  render() {
@@ -33,8 +33,6 @@ class GlkSearch extends GlkFormElement {
33
33
  this._pendingValue = undefined;
34
34
  if (value) this._input.value = value;
35
35
 
36
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
37
-
38
36
  container.appendChild(icon);
39
37
  container.appendChild(this._input);
40
38
 
@@ -68,15 +66,14 @@ class GlkSearch extends GlkFormElement {
68
66
  this._input.value = this.getAttribute('value') || '';
69
67
  this._syncFormValue();
70
68
  break;
71
- case 'disabled':
72
- this._input.disabled = this.getBoolAttr('disabled');
73
- break;
74
69
  case 'label':
75
70
  this._applyLabel();
76
71
  break;
77
72
  }
78
73
  }
79
74
 
75
+ applyDisabled(disabled) { this._input.disabled = disabled; }
76
+
80
77
  _applyLabel() {
81
78
  const label = this.getAttribute('label');
82
79
  if (label) this._input.setAttribute('aria-label', label);
@@ -8,8 +8,11 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
8
8
  // Attributes: options (JSON [{value,label,tone?,disabled?}]), value, full,
9
9
  // overflow ("scroll" | "wrap": what happens when the options
10
10
  // do not fit — without it the row stays one line and runs past
11
- // the edge), label (aria-label of the group), name (form field)
12
- // Properties: value, options (array or JSON text), full, overflow, label
11
+ // the edge), label (aria-label of the group), name (form field),
12
+ // disabled (the whole row; since 1.22.3, as is a disabled
13
+ // fieldset around it)
14
+ // Properties: value, options (array or JSON text), full, overflow, label,
15
+ // disabled
13
16
  // Event: glk-change { value } — only on a change made by the user
14
17
  // Part: group
15
18
  //
@@ -62,13 +65,21 @@ class GlkSegmented extends GlkFormElement {
62
65
  button.classList.add(`glass-segmented__item--${option.tone}`);
63
66
  button.appendChild(this.createElement('span', ['glass-segmented__dot']));
64
67
  }
65
- if (option.disabled) button.disabled = true;
66
68
  button.appendChild(document.createTextNode(option.label ?? value));
67
69
  this._group.appendChild(button);
68
70
  }
71
+ this.applyDisabled(this._actuallyDisabled);
69
72
  this._syncValue();
70
73
  }
71
74
 
75
+ /** The whole row is disabled, or an option by its own `disabled`. */
76
+ applyDisabled(disabled) {
77
+ const options = this.options;
78
+ this._group.querySelectorAll('button[data-value]').forEach((button, i) => {
79
+ button.disabled = disabled || Boolean(options[i]?.disabled);
80
+ });
81
+ }
82
+
72
83
  _syncValue() {
73
84
  const value = this.value;
74
85
  for (const button of this._group.querySelectorAll('button[data-value]')) {
@@ -13,7 +13,7 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
13
13
 
14
14
  class GlkSelect extends GlkFormElement {
15
15
  static get observedAttributes() {
16
- return ['label', 'disabled', 'name', 'value', 'required'];
16
+ return ['label', 'name', 'value', 'required'];
17
17
  }
18
18
 
19
19
  static get observesLightDom() { return true; }
@@ -32,7 +32,6 @@ class GlkSelect extends GlkFormElement {
32
32
  const name = this.getAttribute('name');
33
33
  if (name) this._select.setAttribute('name', name);
34
34
 
35
- if (this.getBoolAttr('disabled')) this._select.disabled = true;
36
35
  if (this.getBoolAttr('required')) this._select.required = true;
37
36
 
38
37
  group.appendChild(this._labelEl);
@@ -106,9 +105,6 @@ class GlkSelect extends GlkFormElement {
106
105
  case 'label':
107
106
  this._labelEl.textContent = this.getAttribute('label') || '';
108
107
  break;
109
- case 'disabled':
110
- this._select.disabled = this.getBoolAttr('disabled');
111
- break;
112
108
  case 'name':
113
109
  this._select.setAttribute('name', this.getAttribute('name') || '');
114
110
  break;
@@ -123,6 +119,8 @@ class GlkSelect extends GlkFormElement {
123
119
  }
124
120
  }
125
121
 
122
+ applyDisabled(disabled) { this._select.disabled = disabled; }
123
+
126
124
  get _validityField() { return this._select; }
127
125
 
128
126
  _syncFormValue() {
@@ -148,9 +146,6 @@ class GlkSelect extends GlkFormElement {
148
146
  this._applyValue(this._wanted);
149
147
  this._syncFormValue();
150
148
  }
151
-
152
- get disabled() { return this.getBoolAttr('disabled'); }
153
- set disabled(v) { this.setBoolAttr('disabled', v); }
154
149
  }
155
150
 
156
151
  customElements.define('glk-select', GlkSelect);