@jungherz-de/glasskit-elements 1.22.1 → 1.22.3
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 +28 -1
- package/README.md +2 -2
- package/SKILL.md +11 -8
- 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 +20 -14
- 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 +112 -85
- package/dist/glasskit-elements.js +112 -85
- 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 +20 -14
- 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,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.22.3] – 2026-10-08
|
|
11
|
+
|
|
12
|
+
GlassKit is unchanged; the peer dependency stays `>=1.22.2`.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **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))
|
|
17
|
+
- **`<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))
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## [1.22.2] – 2026-10-08
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **In a `<glk-input>` affix, only what can be used takes the click.** Since 1.22.0 `::slotted(*)` gave pointer events back to everything slotted into `prefix` and `suffix`. A unit or an icon caught the click instead of the field. A disabled button took it, and focus delegation then moved the focus into the field — also into a read-only field whose container locks it with `pointer-events: none`, because the explicit `auto` outweighed the inherited `none`. Now the slots follow GlassKit: enabled controls, `<glk-button>`, `<glk-select>` and the other form elements included, links and elements with `tabindex` take the click; text, icons and disabled controls let it through to the field. A control has to sit in the slot itself: one wrapped in a slotted element needs `pointer-events: auto` from the page. A control passed on through another component's `<slot>` counts as slotted. Measured in Chromium and WebKit on the issue's page and 21 more cases: the unit, the icon and the disabled button hand the click to the field; in the locked container nothing reacts and the focus stays out; an enabled button, a link, a `<glk-button>` and a `<glk-select>` take their own. Docs, showcase and landing pages render pixel-identical to 1.22.1. Rebuilt against GlassKit 1.22.2, which applies the same rule to plain markup; the peer dependency moves to `>=1.22.2`. ([#14](https://github.com/JUNGHERZ/GlassKit-Elements/issues/14))
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
10
35
|
## [1.22.1] – 2026-10-08
|
|
11
36
|
|
|
12
37
|
### Fixed
|
|
@@ -799,7 +824,9 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
|
|
|
799
824
|
|
|
800
825
|
---
|
|
801
826
|
|
|
802
|
-
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.22.
|
|
827
|
+
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.22.3...HEAD
|
|
828
|
+
[1.22.3]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.3
|
|
829
|
+
[1.22.2]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.2
|
|
803
830
|
[1.22.1]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.1
|
|
804
831
|
[1.22.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.22.0
|
|
805
832
|
[1.21.2]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.21.2
|
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.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.22.3-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
|
|
|
@@ -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.
|
|
3
|
+
description: GlassKit Elements is a vanilla-JS Web Components library (v1.22.3) 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.
|
|
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.1)
|
|
|
18
18
|
npm install @jungherz-de/glasskit-elements @jungherz-de/glasskit
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Peer dependency `@jungherz-de/glasskit >=1.22.
|
|
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.
|
|
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
|
|
|
@@ -624,7 +625,7 @@ Text input with label + hint.
|
|
|
624
625
|
|
|
625
626
|
Events: `glk-input` → `{ value }`, `glk-change` → `{ value }`. Native `input` / `change` also dispatched. Property: `.value`.
|
|
626
627
|
|
|
627
|
-
**Slots `prefix` and `suffix` (since 1.22.0)** put an icon, a currency, a unit or a button inside the field's box, at its start and end (they swap sides under `dir="rtl"`). Each shows only while something is slotted; its width is measured and the field's padding grows by it, so the text keeps clear of a unit of any length. A click on slotted text
|
|
628
|
+
**Slots `prefix` and `suffix` (since 1.22.0)** put an icon, a currency, a unit or a button inside the field's box, at its start and end (they swap sides under `dir="rtl"`). Each shows only while something is slotted; its width is measured and the field's padding grows by it, so the text keeps clear of a unit of any length. A click on slotted text, an icon or a disabled control goes through to the field; enabled controls (`<glk-button>`, `<glk-select>` … included — they are form-associated), links and elements with `tabindex` take their own (since 1.22.2 — before, every slotted element took the click). Slot the control itself: one wrapped in a slotted element needs `pointer-events: auto` from the page. A container locked with `pointer-events: none` does not hold a control; lock it with `inert`, or disable the control. Their text is read as part of the field's description. Parts: `box`, `prefix`, `suffix`. Built on GlassKit's `.glass-input-wrap` (GlassKit 1.22.0).
|
|
628
629
|
|
|
629
630
|
```html
|
|
630
631
|
<glk-input label="Price" inputmode="decimal">
|
|
@@ -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
|
|
|
@@ -1086,8 +1087,9 @@ A small, exclusive choice as one control (since 1.15.0) — traffic light, morni
|
|
|
1086
1087
|
| `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
1088
|
| `label` | String | `aria-label` of the group (no default — set it) |
|
|
1088
1089
|
| `name` | String | Form field name |
|
|
1090
|
+
| `disabled` | Boolean | The whole row (since 1.22.3); a `<fieldset disabled>` around it disables it too |
|
|
1089
1091
|
|
|
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`.
|
|
1092
|
+
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
1093
|
|
|
1092
1094
|
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
1095
|
|
|
@@ -1202,8 +1204,9 @@ One month with a day to pick (since 1.16.0). The days are buttons in a `role="gr
|
|
|
1202
1204
|
| `label` | String | `aria-label` of the day group |
|
|
1203
1205
|
| `prev-label`, `next-label` | String | Names of the nav buttons ("Previous month" / "Next month") |
|
|
1204
1206
|
| `name` | String | Form field name |
|
|
1207
|
+
| `disabled` | Boolean | Arrows and days (since 1.22.3); a `<fieldset disabled>` around it disables it too |
|
|
1205
1208
|
|
|
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`.
|
|
1209
|
+
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
1210
|
|
|
1208
1211
|
---
|
|
1209
1212
|
|
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
|
|
|
@@ -10,19 +10,30 @@ const FORWARDED = ['min', 'max', 'step', 'minlength', 'maxlength', 'pattern', 'a
|
|
|
10
10
|
// Prefix and suffix (since 1.22.0): two slots inside the field's box, on
|
|
11
11
|
// GlassKit's .glass-input-wrap. An affix shows only while something is
|
|
12
12
|
// slotted into it, and its width is measured, so the text keeps clear of a
|
|
13
|
-
// unit of any length. GlassKit
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// unit of any length. As in GlassKit, a click on text, an icon or a disabled
|
|
14
|
+
// control goes through to the field, and what can be used takes its own: an
|
|
15
|
+
// enabled control (<glk-button>, <glk-select> … are form-associated, so they
|
|
16
|
+
// count), a link, an element with tabindex. GlassKit's own rule cannot reach
|
|
17
|
+
// slotted elements, hence this one. ::slotted() sees the slotted element,
|
|
18
|
+
// not what is inside it: a button wrapped in a slotted <span> needs
|
|
19
|
+
// pointer-events: auto from the page. Until 1.22.1 every slotted element took
|
|
20
|
+
// the pointer — a unit caught the click meant for the field, a disabled
|
|
21
|
+
// button took it and moved the focus into the field, read-only and locked
|
|
22
|
+
// or not. Simple selectors only inside ::slotted(), so that no browser drops
|
|
23
|
+
// the rule.
|
|
17
24
|
const affixSheet = new CSSStyleSheet();
|
|
18
25
|
affixSheet.replaceSync(`
|
|
19
|
-
.glass-input-wrap__prefix ::slotted(
|
|
20
|
-
.glass-input-
|
|
26
|
+
.glass-input-wrap__prefix ::slotted(:enabled),
|
|
27
|
+
.glass-input-wrap__prefix ::slotted(a[href]),
|
|
28
|
+
.glass-input-wrap__prefix ::slotted([tabindex]:not(:disabled)),
|
|
29
|
+
.glass-input-wrap__suffix ::slotted(:enabled),
|
|
30
|
+
.glass-input-wrap__suffix ::slotted(a[href]),
|
|
31
|
+
.glass-input-wrap__suffix ::slotted([tabindex]:not(:disabled)) { pointer-events: auto; }
|
|
21
32
|
`);
|
|
22
33
|
|
|
23
34
|
class GlkInput extends GlkFormElement {
|
|
24
35
|
static get observedAttributes() {
|
|
25
|
-
return ['label', 'type', 'placeholder', 'error', 'hint', '
|
|
36
|
+
return ['label', 'type', 'placeholder', 'error', 'hint', 'readonly', 'name', 'value', 'required', ...FORWARDED];
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
static get hostStyles() { return affixSheet; }
|
|
@@ -53,7 +64,6 @@ class GlkInput extends GlkFormElement {
|
|
|
53
64
|
this._pendingValue = undefined;
|
|
54
65
|
if (value) this._input.value = value;
|
|
55
66
|
|
|
56
|
-
if (this.getBoolAttr('disabled')) this._input.disabled = true;
|
|
57
67
|
if (this.getBoolAttr('readonly')) this._input.readOnly = true;
|
|
58
68
|
if (this.getBoolAttr('required')) this._input.required = true;
|
|
59
69
|
for (const attr of FORWARDED) this._forward(attr);
|
|
@@ -143,9 +153,6 @@ class GlkInput extends GlkFormElement {
|
|
|
143
153
|
}
|
|
144
154
|
this._applyDescription();
|
|
145
155
|
break;
|
|
146
|
-
case 'disabled':
|
|
147
|
-
this._input.disabled = this.getBoolAttr('disabled');
|
|
148
|
-
break;
|
|
149
156
|
case 'readonly':
|
|
150
157
|
this._input.readOnly = this.getBoolAttr('readonly');
|
|
151
158
|
break;
|
|
@@ -164,6 +171,8 @@ class GlkInput extends GlkFormElement {
|
|
|
164
171
|
}
|
|
165
172
|
}
|
|
166
173
|
|
|
174
|
+
applyDisabled(disabled) { this._input.disabled = disabled; }
|
|
175
|
+
|
|
167
176
|
/** An affix shows, and makes room in the field, only while something is slotted. */
|
|
168
177
|
_applyAffixes() {
|
|
169
178
|
for (const affix of [this._prefix, this._suffix]) {
|
|
@@ -229,9 +238,6 @@ class GlkInput extends GlkFormElement {
|
|
|
229
238
|
this._syncFormValue();
|
|
230
239
|
}
|
|
231
240
|
|
|
232
|
-
get disabled() { return this.getBoolAttr('disabled'); }
|
|
233
|
-
set disabled(v) { this.setBoolAttr('disabled', v); }
|
|
234
|
-
|
|
235
241
|
get readOnly() { return this.getBoolAttr('readonly'); }
|
|
236
242
|
set readOnly(v) { this.setBoolAttr('readonly', v); }
|
|
237
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);
|