@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 +36 -1
- package/README.md +4 -4
- package/SKILL.md +22 -7
- package/dist/components/base.js +34 -0
- package/dist/components/glk-button.js +6 -9
- package/dist/components/glk-calendar.js +10 -2
- package/dist/components/glk-checkbox.js +3 -8
- package/dist/components/glk-input.js +3 -8
- package/dist/components/glk-radio.js +8 -11
- package/dist/components/glk-range.js +3 -6
- package/dist/components/glk-search.js +3 -6
- package/dist/components/glk-segmented.js +14 -3
- package/dist/components/glk-select.js +3 -8
- package/dist/components/glk-textarea.js +3 -8
- package/dist/components/glk-toggle.js +3 -8
- package/dist/glasskit-elements.esm.js +95 -79
- package/dist/glasskit-elements.js +95 -79
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/base.js +34 -0
- package/src/components/buttons/glk-button.js +6 -9
- package/src/components/forms/glk-calendar.js +10 -2
- package/src/components/forms/glk-checkbox.js +3 -8
- package/src/components/forms/glk-input.js +3 -8
- package/src/components/forms/glk-radio.js +8 -11
- package/src/components/forms/glk-range.js +3 -6
- package/src/components/forms/glk-search.js +3 -6
- package/src/components/forms/glk-segmented.js +14 -3
- package/src/components/forms/glk-select.js +3 -8
- package/src/components/forms/glk-textarea.js +3 -8
- package/src/components/forms/glk-toggle.js +3 -8
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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`.
|
|
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
|
|
package/dist/components/base.js
CHANGED
|
@@ -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', '
|
|
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.
|
|
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
|
-
//
|
|
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', '
|
|
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', '
|
|
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.
|
|
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', '
|
|
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.
|
|
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'
|
|
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', '
|
|
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
|
-
//
|
|
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', '
|
|
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);
|