@jungherz-de/glasskit-elements 1.22.2 → 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.
@@ -258,6 +258,7 @@ class GlkElement extends HTMLElement {
258
258
  this._shadow.appendChild(this._wrapper);
259
259
 
260
260
  this.render();
261
+ if (this.constructor.formAssociated) this._syncDisabled();
261
262
  } else {
262
263
  // Back in the document. While it was out, the observer did not reach
263
264
  // it, so a theme or density switched in the meantime is caught up here
@@ -360,6 +361,28 @@ class GlkElement extends HTMLElement {
360
361
  */
361
362
  refresh() { this.projectLightDom(); }
362
363
 
364
+ /**
365
+ * Form-associated elements only. Such an element is disabled by its own
366
+ * `disabled` attribute or by a <fieldset disabled> around it: the browser
367
+ * counts both — :disabled, left out of the form data — and calls
368
+ * formDisabledCallback() on every change, also when the element moves into
369
+ * or out of such a fieldset. The native controls sit in the shadow root,
370
+ * out of every fieldset's reach, so the state is handed on to them through
371
+ * applyDisabled(). Until 1.22.2 only the element's own attribute reached
372
+ * them: inside a disabled fieldset every field stayed usable, and a submit
373
+ * button sent the form.
374
+ */
375
+ get _actuallyDisabled() { return this.matches(':disabled'); }
376
+
377
+ formDisabledCallback() {
378
+ if (this._initialized) this._syncDisabled();
379
+ }
380
+
381
+ _syncDisabled() { this.applyDisabled(this._actuallyDisabled); }
382
+
383
+ /** Form-associated subclasses override to disable their native controls. */
384
+ applyDisabled(disabled) {}
385
+
363
386
  // ── Utility Methods ──
364
387
 
365
388
  /**
@@ -437,6 +460,17 @@ class GlkFormElement extends GlkElement {
437
460
  if (this._initialized) this.syncValidity();
438
461
  }
439
462
 
463
+ // Reflects the attribute, as on a native control: a disabled fieldset
464
+ // around the element disables it without changing this.
465
+ get disabled() { return this.getBoolAttr('disabled'); }
466
+ set disabled(v) { this.setBoolAttr('disabled', v); }
467
+
468
+ _syncDisabled() {
469
+ super._syncDisabled();
470
+ // A disabled field is barred from validation; enabled again, it counts.
471
+ this.syncValidity();
472
+ }
473
+
440
474
  get form() { return this._internals.form; }
441
475
  get validationMessage() { return this._internals.validationMessage; }
442
476
  get validity() { return this._internals.validity; }
@@ -1520,12 +1554,13 @@ const FORWARDED_ARIA = ['aria-expanded', 'aria-haspopup', 'aria-pressed'];
1520
1554
 
1521
1555
  class GlkButton extends GlkElement {
1522
1556
  static get observedAttributes() {
1523
- return ['variant', 'size', 'disabled', 'type', ...FORWARDED_ARIA];
1557
+ return ['variant', 'size', 'type', ...FORWARDED_ARIA];
1524
1558
  }
1525
1559
 
1526
1560
  // The native button sits in the shadow root, outside every form. Form
1527
1561
  // association gives the element its form, so type="submit" and "reset"
1528
- // can act on it (see _activateForm).
1562
+ // can act on it (see _activateForm), and a disabled fieldset around it
1563
+ // disables it (since 1.22.3; before, it still sent the form).
1529
1564
  static formAssociated = true;
1530
1565
 
1531
1566
  constructor() {
@@ -1538,9 +1573,6 @@ class GlkButton extends GlkElement {
1538
1573
  type: this.getAttribute('type') || 'button'
1539
1574
  });
1540
1575
 
1541
- if (this.getBoolAttr('disabled')) {
1542
- this._btn.disabled = true;
1543
- }
1544
1576
  for (const attr of FORWARDED_ARIA) this._forward(attr);
1545
1577
 
1546
1578
  this._btn.appendChild(document.createElement('slot'));
@@ -1549,7 +1581,7 @@ class GlkButton extends GlkElement {
1549
1581
 
1550
1582
  setupEvents() {
1551
1583
  this._onClick = (e) => {
1552
- if (this.getBoolAttr('disabled')) {
1584
+ if (this._actuallyDisabled) {
1553
1585
  e.preventDefault();
1554
1586
  e.stopPropagation();
1555
1587
  return;
@@ -1608,9 +1640,6 @@ class GlkButton extends GlkElement {
1608
1640
  case 'size':
1609
1641
  this._btn.className = this._computeClasses().join(' ');
1610
1642
  break;
1611
- case 'disabled':
1612
- this._btn.disabled = this.getBoolAttr('disabled');
1613
- break;
1614
1643
  case 'type':
1615
1644
  this._btn.setAttribute('type', this.getAttribute('type') || 'button');
1616
1645
  break;
@@ -1619,6 +1648,8 @@ class GlkButton extends GlkElement {
1619
1648
  }
1620
1649
  }
1621
1650
 
1651
+ applyDisabled(disabled) { this._btn.disabled = disabled; }
1652
+
1622
1653
  _forward(attr) {
1623
1654
  const value = this.getAttribute(attr);
1624
1655
  if (value === null) this._btn.removeAttribute(attr);
@@ -1687,7 +1718,7 @@ affixSheet.replaceSync(`
1687
1718
 
1688
1719
  class GlkInput extends GlkFormElement {
1689
1720
  static get observedAttributes() {
1690
- return ['label', 'type', 'placeholder', 'error', 'hint', 'disabled', 'readonly', 'name', 'value', 'required', ...FORWARDED];
1721
+ return ['label', 'type', 'placeholder', 'error', 'hint', 'readonly', 'name', 'value', 'required', ...FORWARDED];
1691
1722
  }
1692
1723
 
1693
1724
  static get hostStyles() { return affixSheet; }
@@ -1718,7 +1749,6 @@ class GlkInput extends GlkFormElement {
1718
1749
  this._pendingValue = undefined;
1719
1750
  if (value) this._input.value = value;
1720
1751
 
1721
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
1722
1752
  if (this.getBoolAttr('readonly')) this._input.readOnly = true;
1723
1753
  if (this.getBoolAttr('required')) this._input.required = true;
1724
1754
  for (const attr of FORWARDED) this._forward(attr);
@@ -1808,9 +1838,6 @@ class GlkInput extends GlkFormElement {
1808
1838
  }
1809
1839
  this._applyDescription();
1810
1840
  break;
1811
- case 'disabled':
1812
- this._input.disabled = this.getBoolAttr('disabled');
1813
- break;
1814
1841
  case 'readonly':
1815
1842
  this._input.readOnly = this.getBoolAttr('readonly');
1816
1843
  break;
@@ -1829,6 +1856,8 @@ class GlkInput extends GlkFormElement {
1829
1856
  }
1830
1857
  }
1831
1858
 
1859
+ applyDisabled(disabled) { this._input.disabled = disabled; }
1860
+
1832
1861
  /** An affix shows, and makes room in the field, only while something is slotted. */
1833
1862
  _applyAffixes() {
1834
1863
  for (const affix of [this._prefix, this._suffix]) {
@@ -1894,9 +1923,6 @@ class GlkInput extends GlkFormElement {
1894
1923
  this._syncFormValue();
1895
1924
  }
1896
1925
 
1897
- get disabled() { return this.getBoolAttr('disabled'); }
1898
- set disabled(v) { this.setBoolAttr('disabled', v); }
1899
-
1900
1926
  get readOnly() { return this.getBoolAttr('readonly'); }
1901
1927
  set readOnly(v) { this.setBoolAttr('readonly', v); }
1902
1928
 
@@ -1908,7 +1934,7 @@ customElements.define('glk-input', GlkInput);
1908
1934
 
1909
1935
  class GlkTextarea extends GlkFormElement {
1910
1936
  static get observedAttributes() {
1911
- return ['label', 'placeholder', 'rows', 'disabled', 'readonly', 'name', 'value', 'required'];
1937
+ return ['label', 'placeholder', 'rows', 'readonly', 'name', 'value', 'required'];
1912
1938
  }
1913
1939
 
1914
1940
  render() {
@@ -1933,7 +1959,6 @@ class GlkTextarea extends GlkFormElement {
1933
1959
  this._pendingValue = undefined;
1934
1960
  if (value) this._textarea.value = value;
1935
1961
 
1936
- if (this.getBoolAttr('disabled')) this._textarea.disabled = true;
1937
1962
  if (this.getBoolAttr('readonly')) this._textarea.readOnly = true;
1938
1963
  if (this.getBoolAttr('required')) this._textarea.required = true;
1939
1964
 
@@ -1969,9 +1994,6 @@ class GlkTextarea extends GlkFormElement {
1969
1994
  case 'rows':
1970
1995
  this._textarea.setAttribute('rows', this.getAttribute('rows') || '');
1971
1996
  break;
1972
- case 'disabled':
1973
- this._textarea.disabled = this.getBoolAttr('disabled');
1974
- break;
1975
1997
  case 'readonly':
1976
1998
  this._textarea.readOnly = this.getBoolAttr('readonly');
1977
1999
  break;
@@ -1988,6 +2010,8 @@ class GlkTextarea extends GlkFormElement {
1988
2010
  }
1989
2011
  }
1990
2012
 
2013
+ applyDisabled(disabled) { this._textarea.disabled = disabled; }
2014
+
1991
2015
  get _validityField() { return this._textarea; }
1992
2016
 
1993
2017
  _syncFormValue() {
@@ -2009,9 +2033,6 @@ class GlkTextarea extends GlkFormElement {
2009
2033
  this._syncFormValue();
2010
2034
  }
2011
2035
 
2012
- get disabled() { return this.getBoolAttr('disabled'); }
2013
- set disabled(v) { this.setBoolAttr('disabled', v); }
2014
-
2015
2036
  get readOnly() { return this.getBoolAttr('readonly'); }
2016
2037
  set readOnly(v) { this.setBoolAttr('readonly', v); }
2017
2038
  }
@@ -2030,7 +2051,7 @@ customElements.define('glk-textarea', GlkTextarea);
2030
2051
 
2031
2052
  class GlkSelect extends GlkFormElement {
2032
2053
  static get observedAttributes() {
2033
- return ['label', 'disabled', 'name', 'value', 'required'];
2054
+ return ['label', 'name', 'value', 'required'];
2034
2055
  }
2035
2056
 
2036
2057
  static get observesLightDom() { return true; }
@@ -2049,7 +2070,6 @@ class GlkSelect extends GlkFormElement {
2049
2070
  const name = this.getAttribute('name');
2050
2071
  if (name) this._select.setAttribute('name', name);
2051
2072
 
2052
- if (this.getBoolAttr('disabled')) this._select.disabled = true;
2053
2073
  if (this.getBoolAttr('required')) this._select.required = true;
2054
2074
 
2055
2075
  group.appendChild(this._labelEl);
@@ -2123,9 +2143,6 @@ class GlkSelect extends GlkFormElement {
2123
2143
  case 'label':
2124
2144
  this._labelEl.textContent = this.getAttribute('label') || '';
2125
2145
  break;
2126
- case 'disabled':
2127
- this._select.disabled = this.getBoolAttr('disabled');
2128
- break;
2129
2146
  case 'name':
2130
2147
  this._select.setAttribute('name', this.getAttribute('name') || '');
2131
2148
  break;
@@ -2140,6 +2157,8 @@ class GlkSelect extends GlkFormElement {
2140
2157
  }
2141
2158
  }
2142
2159
 
2160
+ applyDisabled(disabled) { this._select.disabled = disabled; }
2161
+
2143
2162
  get _validityField() { return this._select; }
2144
2163
 
2145
2164
  _syncFormValue() {
@@ -2165,9 +2184,6 @@ class GlkSelect extends GlkFormElement {
2165
2184
  this._applyValue(this._wanted);
2166
2185
  this._syncFormValue();
2167
2186
  }
2168
-
2169
- get disabled() { return this.getBoolAttr('disabled'); }
2170
- set disabled(v) { this.setBoolAttr('disabled', v); }
2171
2187
  }
2172
2188
 
2173
2189
  customElements.define('glk-select', GlkSelect);
@@ -2176,7 +2192,7 @@ const SEARCH_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" s
2176
2192
 
2177
2193
  class GlkSearch extends GlkFormElement {
2178
2194
  static get observedAttributes() {
2179
- return ['placeholder', 'name', 'value', 'disabled', 'label'];
2195
+ return ['placeholder', 'name', 'value', 'label'];
2180
2196
  }
2181
2197
 
2182
2198
  render() {
@@ -2204,8 +2220,6 @@ class GlkSearch extends GlkFormElement {
2204
2220
  this._pendingValue = undefined;
2205
2221
  if (value) this._input.value = value;
2206
2222
 
2207
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
2208
-
2209
2223
  container.appendChild(icon);
2210
2224
  container.appendChild(this._input);
2211
2225
 
@@ -2239,15 +2253,14 @@ class GlkSearch extends GlkFormElement {
2239
2253
  this._input.value = this.getAttribute('value') || '';
2240
2254
  this._syncFormValue();
2241
2255
  break;
2242
- case 'disabled':
2243
- this._input.disabled = this.getBoolAttr('disabled');
2244
- break;
2245
2256
  case 'label':
2246
2257
  this._applyLabel();
2247
2258
  break;
2248
2259
  }
2249
2260
  }
2250
2261
 
2262
+ applyDisabled(disabled) { this._input.disabled = disabled; }
2263
+
2251
2264
  _applyLabel() {
2252
2265
  const label = this.getAttribute('label');
2253
2266
  if (label) this._input.setAttribute('aria-label', label);
@@ -2278,7 +2291,7 @@ customElements.define('glk-search', GlkSearch);
2278
2291
 
2279
2292
  class GlkToggle extends GlkFormElement {
2280
2293
  static get observedAttributes() {
2281
- return ['checked', 'disabled', 'label', 'name', 'value', 'required'];
2294
+ return ['checked', 'label', 'name', 'value', 'required'];
2282
2295
  }
2283
2296
 
2284
2297
  static get hostStyles() { return checkControlSheet; }
@@ -2310,7 +2323,6 @@ class GlkToggle extends GlkFormElement {
2310
2323
  label.appendChild(this._labelEl);
2311
2324
 
2312
2325
  if (this.getBoolAttr('checked')) this._input.checked = true;
2313
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
2314
2326
  if (this.getBoolAttr('required')) this._input.required = true;
2315
2327
 
2316
2328
  this._defaultChecked = this.getBoolAttr('checked');
@@ -2345,9 +2357,6 @@ class GlkToggle extends GlkFormElement {
2345
2357
  this._input.checked = this.getBoolAttr('checked');
2346
2358
  this._syncFormValue();
2347
2359
  break;
2348
- case 'disabled':
2349
- this._input.disabled = this.getBoolAttr('disabled');
2350
- break;
2351
2360
  case 'label':
2352
2361
  this._labelEl.textContent = this.getAttribute('label') || '';
2353
2362
  break;
@@ -2360,6 +2369,8 @@ class GlkToggle extends GlkFormElement {
2360
2369
  }
2361
2370
  }
2362
2371
 
2372
+ applyDisabled(disabled) { this._input.disabled = disabled; }
2373
+
2363
2374
  get _validityField() { return this._input; }
2364
2375
 
2365
2376
  _syncFormValue() {
@@ -2388,9 +2399,6 @@ class GlkToggle extends GlkFormElement {
2388
2399
  if (this._input) this._syncFormValue();
2389
2400
  }
2390
2401
 
2391
- get disabled() { return this.getBoolAttr('disabled'); }
2392
- set disabled(v) { this.setBoolAttr('disabled', v); }
2393
-
2394
2402
  get name() { return this.getAttribute('name'); }
2395
2403
  set name(v) { this.setAttribute('name', v); }
2396
2404
 
@@ -2407,7 +2415,7 @@ const CHECKMARK_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor
2407
2415
 
2408
2416
  class GlkCheckbox extends GlkFormElement {
2409
2417
  static get observedAttributes() {
2410
- return ['checked', 'disabled', 'label', 'name', 'value', 'required'];
2418
+ return ['checked', 'label', 'name', 'value', 'required'];
2411
2419
  }
2412
2420
 
2413
2421
  static get hostStyles() { return checkControlSheet; }
@@ -2433,7 +2441,6 @@ class GlkCheckbox extends GlkFormElement {
2433
2441
  label.appendChild(this._labelEl);
2434
2442
 
2435
2443
  if (this.getBoolAttr('checked')) this._input.checked = true;
2436
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
2437
2444
  if (this.getBoolAttr('required')) this._input.required = true;
2438
2445
 
2439
2446
  this._defaultChecked = this.getBoolAttr('checked');
@@ -2467,9 +2474,6 @@ class GlkCheckbox extends GlkFormElement {
2467
2474
  this._input.checked = this.getBoolAttr('checked');
2468
2475
  this._syncFormValue();
2469
2476
  break;
2470
- case 'disabled':
2471
- this._input.disabled = this.getBoolAttr('disabled');
2472
- break;
2473
2477
  case 'label':
2474
2478
  this._labelEl.textContent = this.getAttribute('label') || '';
2475
2479
  break;
@@ -2482,6 +2486,8 @@ class GlkCheckbox extends GlkFormElement {
2482
2486
  }
2483
2487
  }
2484
2488
 
2489
+ applyDisabled(disabled) { this._input.disabled = disabled; }
2490
+
2485
2491
  get _validityField() { return this._input; }
2486
2492
 
2487
2493
  _syncFormValue() {
@@ -2503,9 +2509,6 @@ class GlkCheckbox extends GlkFormElement {
2503
2509
  if (this._input) this._syncFormValue();
2504
2510
  }
2505
2511
 
2506
- get disabled() { return this.getBoolAttr('disabled'); }
2507
- set disabled(v) { this.setBoolAttr('disabled', v); }
2508
-
2509
2512
  get name() { return this.getAttribute('name'); }
2510
2513
  set name(v) { this.setAttribute('name', v); }
2511
2514
 
@@ -2534,7 +2537,7 @@ function ownerForm(el) {
2534
2537
 
2535
2538
  /** Only the selected radio is a tab stop; arrow keys move within the group. */
2536
2539
  function syncGroupTabIndex(group) {
2537
- const enabled = group.filter(el => !el.disabled);
2540
+ const enabled = group.filter(el => !el._actuallyDisabled);
2538
2541
  if (!enabled.length) return;
2539
2542
  const focusable = enabled.find(el => el.checked) || enabled[0];
2540
2543
  for (const el of group) {
@@ -2544,7 +2547,7 @@ function syncGroupTabIndex(group) {
2544
2547
 
2545
2548
  class GlkRadio extends GlkFormElement {
2546
2549
  static get observedAttributes() {
2547
- return ['checked', 'disabled', 'label', 'name', 'value', 'required'];
2550
+ return ['checked', 'label', 'name', 'value', 'required'];
2548
2551
  }
2549
2552
 
2550
2553
  static get hostStyles() { return checkControlSheet; }
@@ -2574,7 +2577,6 @@ class GlkRadio extends GlkFormElement {
2574
2577
  label.appendChild(this._labelEl);
2575
2578
 
2576
2579
  if (this.getBoolAttr('checked')) this._input.checked = true;
2577
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
2578
2580
 
2579
2581
  this._defaultChecked = this.getBoolAttr('checked');
2580
2582
  this._wrapper.appendChild(label);
@@ -2592,7 +2594,7 @@ class GlkRadio extends GlkFormElement {
2592
2594
  this._onKeyDown = (e) => {
2593
2595
  const dir = ARROW_KEYS[e.key];
2594
2596
  if (!dir || e.ctrlKey || e.metaKey || e.altKey) return;
2595
- const group = this._group().filter(el => !el.disabled);
2597
+ const group = this._group().filter(el => !el._actuallyDisabled);
2596
2598
  if (group.length < 2) return;
2597
2599
  e.preventDefault();
2598
2600
  const next = group[(group.indexOf(this) + dir + group.length) % group.length];
@@ -2674,10 +2676,6 @@ class GlkRadio extends GlkFormElement {
2674
2676
  this._syncFormValue();
2675
2677
  syncGroupTabIndex(this._group());
2676
2678
  break;
2677
- case 'disabled':
2678
- this._input.disabled = this.getBoolAttr('disabled');
2679
- syncGroupTabIndex(this._group());
2680
- break;
2681
2679
  case 'label':
2682
2680
  this._labelEl.textContent = this.getAttribute('label') || '';
2683
2681
  break;
@@ -2692,6 +2690,11 @@ class GlkRadio extends GlkFormElement {
2692
2690
  }
2693
2691
  }
2694
2692
 
2693
+ applyDisabled(disabled) {
2694
+ this._input.disabled = disabled;
2695
+ syncGroupTabIndex(this._group());
2696
+ }
2697
+
2695
2698
  _syncFormValue() {
2696
2699
  const val = this.getAttribute('value') || '';
2697
2700
  this.setFormValue(this._input.checked ? val : null);
@@ -2716,9 +2719,6 @@ class GlkRadio extends GlkFormElement {
2716
2719
  syncGroupTabIndex(this._group());
2717
2720
  }
2718
2721
 
2719
- get disabled() { return this.getBoolAttr('disabled'); }
2720
- set disabled(v) { this.setBoolAttr('disabled', v); }
2721
-
2722
2722
  get name() { return this.getAttribute('name'); }
2723
2723
  set name(v) { this.setAttribute('name', v); }
2724
2724
 
@@ -2730,7 +2730,7 @@ customElements.define('glk-radio', GlkRadio);
2730
2730
 
2731
2731
  class GlkRange extends GlkFormElement {
2732
2732
  static get observedAttributes() {
2733
- return ['label', 'min', 'max', 'value', 'step', 'name', 'disabled'];
2733
+ return ['label', 'min', 'max', 'value', 'step', 'name'];
2734
2734
  }
2735
2735
 
2736
2736
  render() {
@@ -2761,8 +2761,6 @@ class GlkRange extends GlkFormElement {
2761
2761
  const name = this.getAttribute('name');
2762
2762
  if (name) this._input.setAttribute('name', name);
2763
2763
 
2764
- if (this.getBoolAttr('disabled')) this._input.disabled = true;
2765
-
2766
2764
  // The reset value is the attribute's; a value set as a property before
2767
2765
  // render() has waited for it and comes on top.
2768
2766
  this._defaultValue = this._input.value;
@@ -2814,12 +2812,11 @@ class GlkRange extends GlkFormElement {
2814
2812
  case 'name':
2815
2813
  this._input.setAttribute('name', this.getAttribute('name') || '');
2816
2814
  break;
2817
- case 'disabled':
2818
- this._input.disabled = this.getBoolAttr('disabled');
2819
- break;
2820
2815
  }
2821
2816
  }
2822
2817
 
2818
+ applyDisabled(disabled) { this._input.disabled = disabled; }
2819
+
2823
2820
  _updateValueDisplay() {
2824
2821
  this._valueEl.textContent = `${this._input.value}%`;
2825
2822
  }
@@ -2854,8 +2851,11 @@ customElements.define('glk-range', GlkRange);
2854
2851
  // Attributes: options (JSON [{value,label,tone?,disabled?}]), value, full,
2855
2852
  // overflow ("scroll" | "wrap": what happens when the options
2856
2853
  // do not fit — without it the row stays one line and runs past
2857
- // the edge), label (aria-label of the group), name (form field)
2858
- // Properties: value, options (array or JSON text), full, overflow, label
2854
+ // the edge), label (aria-label of the group), name (form field),
2855
+ // disabled (the whole row; since 1.22.3, as is a disabled
2856
+ // fieldset around it)
2857
+ // Properties: value, options (array or JSON text), full, overflow, label,
2858
+ // disabled
2859
2859
  // Event: glk-change { value } — only on a change made by the user
2860
2860
  // Part: group
2861
2861
  //
@@ -2908,13 +2908,21 @@ class GlkSegmented extends GlkFormElement {
2908
2908
  button.classList.add(`glass-segmented__item--${option.tone}`);
2909
2909
  button.appendChild(this.createElement('span', ['glass-segmented__dot']));
2910
2910
  }
2911
- if (option.disabled) button.disabled = true;
2912
2911
  button.appendChild(document.createTextNode(option.label ?? value));
2913
2912
  this._group.appendChild(button);
2914
2913
  }
2914
+ this.applyDisabled(this._actuallyDisabled);
2915
2915
  this._syncValue();
2916
2916
  }
2917
2917
 
2918
+ /** The whole row is disabled, or an option by its own `disabled`. */
2919
+ applyDisabled(disabled) {
2920
+ const options = this.options;
2921
+ this._group.querySelectorAll('button[data-value]').forEach((button, i) => {
2922
+ button.disabled = disabled || Boolean(options[i]?.disabled);
2923
+ });
2924
+ }
2925
+
2918
2926
  _syncValue() {
2919
2927
  const value = this.value;
2920
2928
  for (const button of this._group.querySelectorAll('button[data-value]')) {
@@ -3017,8 +3025,9 @@ customElements.define('glk-segmented', GlkSegmented);
3017
3025
  // the page language, else the browser's), week-start (0 = Sunday … 6 = Saturday;
3018
3026
  // default from the locale, Monday where the browser cannot say),
3019
3027
  // label (aria-label of the day group), prev-label / next-label
3020
- // (names of the nav buttons, English by default)
3021
- // Properties: month, value, marks, locale, label
3028
+ // (names of the nav buttons, English by default), disabled
3029
+ // (since 1.22.3, as is a disabled fieldset around it)
3030
+ // Properties: month, value, marks, locale, label, disabled
3022
3031
  // Form: associated — a surrounding <form> receives name=value, reset
3023
3032
  // restores the initial value, like <glk-segmented>
3024
3033
  // Events: glk-change { value } — on a pick by the user
@@ -3110,9 +3119,16 @@ class GlkCalendar extends GlkFormElement {
3110
3119
  day.append(num, dots);
3111
3120
  this._grid.appendChild(day);
3112
3121
  }
3122
+ this.applyDisabled(this._actuallyDisabled);
3113
3123
  this._syncValue();
3114
3124
  }
3115
3125
 
3126
+ // Disabled as a whole: the arrows and every day. The days of another month
3127
+ // are built again, so _build() hands the state on to them too.
3128
+ applyDisabled(disabled) {
3129
+ for (const button of [this._prev, this._next, ...this._days()]) button.disabled = disabled;
3130
+ }
3131
+
3116
3132
  _days() { return [...this._grid.querySelectorAll('button[data-value]')]; }
3117
3133
  _dayFor(iso) { return this._grid.querySelector(`button[data-value="${iso}"]`); }
3118
3134