@fusion.dev/helix-headless 0.143.0 → 0.144.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/custom-elements.json +8551 -375
  2. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  3. package/dist/area-chart/root/area-chart-root.js +24 -7
  4. package/dist/avatar/root/avatar-root.d.ts +14 -0
  5. package/dist/avatar/root/avatar-root.js +23 -9
  6. package/dist/badge/root/badge-root.d.ts +11 -7
  7. package/dist/badge/root/badge-root.js +1 -1
  8. package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
  9. package/dist/bar-chart/root/bar-chart-root.js +17 -10
  10. package/dist/button/root/button-root.d.ts +47 -1
  11. package/dist/button/root/button-root.js +38 -4
  12. package/dist/callout/root/callout-root.d.ts +18 -1
  13. package/dist/callout/root/callout-root.js +15 -4
  14. package/dist/card/root/card-root.d.ts +30 -1
  15. package/dist/card/root/card-root.js +38 -5
  16. package/dist/checkbox/root/checkbox-root.js +2 -0
  17. package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
  18. package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
  19. package/dist/code-block/lines.d.ts +13 -0
  20. package/dist/code-block/lines.js +2 -0
  21. package/dist/code-block/root/code-block-root.d.ts +30 -0
  22. package/dist/code-block/root/code-block-root.js +23 -2
  23. package/dist/color-input/root/color-input-root.d.ts +34 -1
  24. package/dist/color-input/root/color-input-root.js +23 -4
  25. package/dist/color-picker/channels.d.ts +26 -0
  26. package/dist/color-picker/channels.js +84 -0
  27. package/dist/color-picker/root/color-picker-root.d.ts +81 -5
  28. package/dist/color-picker/root/color-picker-root.js +103 -10
  29. package/dist/combobox/root/combobox-root.d.ts +38 -3
  30. package/dist/combobox/root/combobox-root.js +64 -2
  31. package/dist/command/root/command-root.d.ts +30 -4
  32. package/dist/command/root/command-root.js +33 -3
  33. package/dist/copy-button/from.d.ts +22 -0
  34. package/dist/copy-button/from.js +28 -0
  35. package/dist/copy-button/root/copy-button-root.d.ts +27 -1
  36. package/dist/copy-button/root/copy-button-root.js +43 -7
  37. package/dist/date-input/root/date-input-root.d.ts +73 -4
  38. package/dist/date-input/root/date-input-root.js +93 -43
  39. package/dist/date-picker/root/date-picker-root.d.ts +145 -1
  40. package/dist/date-picker/root/date-picker-root.js +239 -48
  41. package/dist/dialog/description.d.ts +9 -0
  42. package/dist/dialog/description.js +14 -0
  43. package/dist/dialog/root/dialog-root.d.ts +64 -1
  44. package/dist/dialog/root/dialog-root.js +49 -23
  45. package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
  46. package/dist/donut-chart/root/donut-chart-root.js +21 -6
  47. package/dist/drawer/root/drawer-root.d.ts +63 -2
  48. package/dist/drawer/root/drawer-root.js +95 -19
  49. package/dist/drawer/swipe-start.d.ts +9 -0
  50. package/dist/drawer/swipe-start.js +12 -0
  51. package/dist/element-events.generated.js +8 -5
  52. package/dist/empty/root/empty-root.d.ts +1 -1
  53. package/dist/file-input/root/file-input-root.d.ts +47 -0
  54. package/dist/file-input/root/file-input-root.js +60 -22
  55. package/dist/file-input/screen.d.ts +35 -0
  56. package/dist/file-input/screen.js +49 -0
  57. package/dist/filter-input/root/filter-input-root.d.ts +38 -4
  58. package/dist/filter-input/root/filter-input-root.js +67 -3
  59. package/dist/filter-input/suggest.d.ts +17 -0
  60. package/dist/filter-input/suggest.js +23 -0
  61. package/dist/format-date/options.d.ts +33 -0
  62. package/dist/format-date/options.js +42 -0
  63. package/dist/format-date/root/format-date-root.d.ts +51 -0
  64. package/dist/format-date/root/format-date-root.js +58 -38
  65. package/dist/heatmap/root/heatmap-root.d.ts +23 -6
  66. package/dist/heatmap/root/heatmap-root.js +24 -13
  67. package/dist/icon/index.d.ts +2 -0
  68. package/dist/icon/index.js +2 -0
  69. package/dist/icon/registry.d.ts +37 -0
  70. package/dist/icon/registry.js +83 -0
  71. package/dist/icon/root/icon-root.d.ts +30 -4
  72. package/dist/icon/root/icon-root.js +41 -6
  73. package/dist/icon/root/index.d.ts +2 -0
  74. package/dist/icon/root/index.js +2 -0
  75. package/dist/icon/set.d.ts +1 -0
  76. package/dist/icon/set.js +3 -0
  77. package/dist/image/root/image-root.d.ts +14 -0
  78. package/dist/image/root/image-root.js +15 -2
  79. package/dist/input/root/input-root.d.ts +11 -0
  80. package/dist/input/root/input-root.js +15 -0
  81. package/dist/kbd/root/kbd-root.d.ts +20 -13
  82. package/dist/kbd/root/kbd-root.js +35 -37
  83. package/dist/kbd/shortcut.d.ts +18 -0
  84. package/dist/kbd/shortcut.js +35 -0
  85. package/dist/legend/hidden-series.d.ts +8 -0
  86. package/dist/legend/hidden-series.js +13 -0
  87. package/dist/legend/root/legend-root.d.ts +56 -0
  88. package/dist/legend/root/legend-root.js +54 -5
  89. package/dist/lightbox/item/lightbox-item.d.ts +15 -0
  90. package/dist/lightbox/item/lightbox-item.js +10 -1
  91. package/dist/lightbox/root/lightbox-root.d.ts +61 -1
  92. package/dist/lightbox/root/lightbox-root.js +94 -4
  93. package/dist/line-chart/root/line-chart-root.d.ts +11 -3
  94. package/dist/line-chart/root/line-chart-root.js +19 -5
  95. package/dist/listbox/range.d.ts +17 -0
  96. package/dist/listbox/range.js +5 -0
  97. package/dist/listbox/root/listbox-root.d.ts +74 -3
  98. package/dist/listbox/root/listbox-root.js +87 -9
  99. package/dist/loader/busy.d.ts +17 -0
  100. package/dist/loader/busy.js +24 -0
  101. package/dist/loader/root/loader-root.d.ts +37 -0
  102. package/dist/loader/root/loader-root.js +52 -3
  103. package/dist/meter/root/meter-root.d.ts +7 -0
  104. package/dist/meter/root/meter-root.js +6 -3
  105. package/dist/notifications/feed-keys.d.ts +10 -0
  106. package/dist/notifications/feed-keys.js +14 -0
  107. package/dist/notifications/notification/notification.d.ts +17 -0
  108. package/dist/notifications/notification/notification.js +21 -2
  109. package/dist/notifications/root/notifications-root.d.ts +42 -0
  110. package/dist/notifications/root/notifications-root.js +60 -1
  111. package/dist/number-input/root/number-input-root.d.ts +47 -0
  112. package/dist/number-input/root/number-input-root.js +58 -22
  113. package/dist/object-inspector/root/object-inspector-root.d.ts +1 -0
  114. package/dist/object-inspector/root/object-inspector-root.js +1 -1
  115. package/dist/option/root/option-root.d.ts +54 -5
  116. package/dist/option/root/option-root.js +34 -5
  117. package/dist/pin-input/format.d.ts +7 -0
  118. package/dist/pin-input/format.js +16 -0
  119. package/dist/pin-input/root/pin-input-root.d.ts +21 -0
  120. package/dist/pin-input/root/pin-input-root.js +29 -2
  121. package/dist/popover/root/popover-root.d.ts +62 -4
  122. package/dist/popover/root/popover-root.js +53 -5
  123. package/dist/progress/root/progress-root.d.ts +7 -0
  124. package/dist/progress/root/progress-root.js +9 -3
  125. package/dist/qr-code/export.d.ts +31 -0
  126. package/dist/qr-code/export.js +10 -0
  127. package/dist/qr-code/root/qr-code-root.d.ts +26 -0
  128. package/dist/qr-code/root/qr-code-root.js +49 -2
  129. package/dist/radio/group/radio-group.d.ts +23 -0
  130. package/dist/radio/group/radio-group.js +18 -0
  131. package/dist/rating/root/rating-root.d.ts +25 -0
  132. package/dist/rating/root/rating-root.js +45 -3
  133. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  134. package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
  135. package/dist/select/root/select-root.d.ts +24 -0
  136. package/dist/select/root/select-root.js +20 -6
  137. package/dist/skeleton/root/skeleton-root.d.ts +25 -0
  138. package/dist/skeleton/root/skeleton-root.js +17 -2
  139. package/dist/slider/root/slider-root.d.ts +39 -0
  140. package/dist/slider/root/slider-root.js +45 -9
  141. package/dist/stat/root/stat-root.d.ts +2 -0
  142. package/dist/stat/root/stat-root.js +1 -1
  143. package/dist/steps/step/step.d.ts +1 -0
  144. package/dist/steps/step/step.js +1 -1
  145. package/dist/switch/root/switch-root.d.ts +12 -2
  146. package/dist/switch/root/switch-root.js +6 -1
  147. package/dist/tag/group/tag-group.d.ts +107 -0
  148. package/dist/tag/group/tag-group.js +157 -0
  149. package/dist/tag/index.d.ts +1 -0
  150. package/dist/tag/index.js +1 -0
  151. package/dist/tag/root/tag-root.d.ts +60 -0
  152. package/dist/tag/root/tag-root.js +74 -18
  153. package/dist/tag/selection.d.ts +10 -0
  154. package/dist/tag/selection.js +12 -0
  155. package/dist/textarea/grow.d.ts +22 -0
  156. package/dist/textarea/grow.js +20 -0
  157. package/dist/textarea/root/textarea-root.d.ts +9 -0
  158. package/dist/textarea/root/textarea-root.js +9 -1
  159. package/dist/time-input/root/time-input-root.d.ts +44 -5
  160. package/dist/time-input/root/time-input-root.js +69 -39
  161. package/dist/time-picker/root/time-picker-root.d.ts +54 -8
  162. package/dist/time-picker/root/time-picker-root.js +121 -91
  163. package/dist/toast/hotkey.d.ts +22 -0
  164. package/dist/toast/hotkey.js +23 -0
  165. package/dist/toast/item/toast-item.d.ts +16 -1
  166. package/dist/toast/item/toast-item.js +41 -13
  167. package/dist/toast/root/toast-root.d.ts +42 -8
  168. package/dist/toast/root/toast-root.js +64 -4
  169. package/dist/toggle/group/toggle-group.d.ts +57 -15
  170. package/dist/toggle/group/toggle-group.js +69 -9
  171. package/dist/toggle/root/toggle-root.d.ts +12 -1
  172. package/dist/toggle/root/toggle-root.js +11 -3
  173. package/dist/tooltip/focus-target.d.ts +6 -0
  174. package/dist/tooltip/focus-target.js +9 -0
  175. package/dist/tooltip/root/tooltip-root.d.ts +42 -17
  176. package/dist/tooltip/root/tooltip-root.js +50 -9
  177. package/dist/tour/naming.d.ts +30 -0
  178. package/dist/tour/naming.js +10 -0
  179. package/dist/tour/root/tour-root.d.ts +35 -0
  180. package/dist/tour/root/tour-root.js +56 -5
  181. package/dist/tour/stop/tour-stop.d.ts +53 -8
  182. package/dist/tour/stop/tour-stop.js +45 -15
  183. package/dist/tour/wait.d.ts +11 -0
  184. package/dist/tour/wait.js +19 -0
  185. package/dist/trend/root/trend-root.d.ts +13 -0
  186. package/dist/trend/root/trend-root.js +14 -9
  187. package/lint.json +1 -1
  188. package/package.json +5 -5
  189. package/types/jsx.d.ts +4862 -254
  190. package/types/svelte.d.ts +1 -0
  191. package/types/vue.d.ts +1149 -67
@@ -11,25 +11,40 @@ let HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = '';
14
+ this.loading = false;
15
+ }
16
+ get wraps() {
17
+ return (this.childrenKnown &&
18
+ [...this.childNodes].some((node) => node.nodeType === 1 || (node.textContent ?? '').trim() !== ''));
14
19
  }
15
20
  willUpdate(changed) {
16
21
  super.willUpdate(changed);
22
+ this.internals.ariaBusy = this.loading ? 'true' : null;
17
23
  if (this.label) {
18
24
  hostAria(this.internals, { label: this.label, role: 'progressbar' });
19
25
  this.internals.ariaHidden = null;
20
26
  }
21
27
  else {
22
28
  hostAria(this.internals, { label: '', role: '' });
23
- this.internals.ariaHidden = 'true';
29
+ this.internals.ariaHidden = this.wraps ? null : 'true';
24
30
  }
25
31
  }
32
+ firstUpdated(changed) {
33
+ super.firstUpdated(changed);
34
+ this.finishHydration();
35
+ }
26
36
  render() {
27
- return html `<span part="skeleton"></span>`;
37
+ return html `<span part="content" ?inert=${this.loading}
38
+ ><slot @slotchange=${() => this.requestUpdate()}></slot></span
39
+ ><span part="skeleton"></span>`;
28
40
  }
29
41
  };
30
42
  __decorate([
31
43
  property()
32
44
  ], HelixHeadlessSkeleton.prototype, "label", void 0);
45
+ __decorate([
46
+ property({ reflect: true, type: Boolean })
47
+ ], HelixHeadlessSkeleton.prototype, "loading", void 0);
33
48
  HelixHeadlessSkeleton = __decorate([
34
49
  customElement('helix-headless-skeleton')
35
50
  ], HelixHeadlessSkeleton);
@@ -20,6 +20,13 @@ declare const HelixHeadlessSlider_base: (new (...args: any[]) => import("@fusion
20
20
  *
21
21
  * A range with two thumbs is two inputs stacked on one track, because there is
22
22
  * no native two-thumb range. Each thumb keeps its own value.
23
+ * `min-steps-between-thumbs` keeps them that many steps apart.
24
+ *
25
+ * `format-options` writes the value with `Intl.NumberFormat` in `locale`, as a
26
+ * currency, a percent or a unit, and `formatter` writes it with a function of
27
+ * your own, such as words for a scale. Either one is what a screen reader
28
+ * hears, what the readout shows and what the marker labels say. `value-text`
29
+ * still wraps it in a sentence, with the formatted value in its place.
23
30
  *
24
31
  * `required` comes with the field mixin and cannot fail. A range is never
25
32
  * empty: the platform shows its midpoint when no value is set, and `willUpdate`
@@ -62,6 +69,27 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
62
69
  protected readonly aria: AriaForwardController;
63
70
  /** Typed or dragged, so every edit is announced. See `announcesInput`. */
64
71
  protected readonly announcesInput = true;
72
+ /**
73
+ * `Intl.NumberFormat` options the value is written with: a currency, a
74
+ * percent, a unit, or a number of decimals. Written as JSON in the
75
+ * attribute.
76
+ *
77
+ * ```html
78
+ * <helix-slider format-options='{"style":"currency","currency":"USD"}'></helix-slider>
79
+ * ```
80
+ */
81
+ formatOptions?: Intl.NumberFormatOptions;
82
+ /**
83
+ * Writes a value as text, in place of `format-options`: words for a scale,
84
+ * or anything `Intl.NumberFormat` cannot say. A property, since a function
85
+ * cannot be written in an attribute.
86
+ */
87
+ formatter?: (value: number) => string;
88
+ /**
89
+ * The locale `format-options` writes the value in, such as `de-DE`. Empty is
90
+ * the reader's own.
91
+ */
92
+ locale: string;
65
93
  /**
66
94
  * Shows the value at each mark.
67
95
  */
@@ -102,6 +130,11 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
102
130
  min: number;
103
131
  /** The name of a range's lower thumb, read after the slider's label. */
104
132
  minimumLabel: string;
133
+ /**
134
+ * How many steps a range's two thumbs stay apart, as Zag's
135
+ * `minStepsBetweenThumbs`. A thumb pushed closer stops there.
136
+ */
137
+ minStepsBetweenThumbs: number;
105
138
  /**
106
139
  * The direction the track runs. `vertical` stands it on end.
107
140
  *
@@ -223,6 +256,12 @@ export declare class HelixHeadlessSlider extends HelixHeadlessSlider_base {
223
256
  * step, or for a range, two of them in order.
224
257
  */
225
258
  private sanitize;
259
+ /** How far apart a range's thumbs stay, in the slider's own units. */
260
+ private get gap();
261
+ /** Whether the value is written by `formatter` or `format-options`. */
262
+ private get formats();
263
+ /** A value as the reader sees it: formatted where asked, the number otherwise. */
264
+ private said;
226
265
  /** What a screen reader says for one thumb, where `value-text` asks. */
227
266
  private spoken;
228
267
  protected updated(changed: PropertyValues): void;
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, fillTemplate, HelixFormElement, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, fillTemplate, formatNumber, HelixFormElement, MixinControllerValue, MixinField, restoreValue, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue(MixinField(HelixFormElement)) {
@@ -14,12 +14,14 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
14
14
  target: () => this.partNamed('control'),
15
15
  });
16
16
  this.announcesInput = true;
17
+ this.locale = '';
17
18
  this.markerLabels = false;
18
19
  this.markers = 0;
19
20
  this.max = 100;
20
21
  this.maximumLabel = 'maximum';
21
22
  this.min = 0;
22
23
  this.minimumLabel = 'minimum';
24
+ this.minStepsBetweenThumbs = 0;
23
25
  this.range = false;
24
26
  this.showValue = false;
25
27
  this.step = 1;
@@ -29,9 +31,10 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
29
31
  if (this.range) {
30
32
  const [start, end] = this.bounds;
31
33
  const moved = Number(control.value);
34
+ const gap = this.gap;
32
35
  const pair = control.dataset.thumb === 'start'
33
- ? [Math.min(moved, end), end]
34
- : [start, Math.max(moved, start)];
36
+ ? [Math.min(moved, end - gap), end]
37
+ : [start, Math.max(moved, start + gap)];
35
38
  this.requestValue(pair.join(','));
36
39
  restoreValue(control, String(control.dataset.thumb === 'start' ? this.bounds[0] : this.bounds[1]));
37
40
  return;
@@ -47,7 +50,7 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
47
50
  this.valueText = '';
48
51
  }
49
52
  get reserve() {
50
- const widest = Math.max(String(this.min).length, String(this.max).length);
53
+ const widest = Math.max(this.said(this.min).length, this.said(this.max).length);
51
54
  return this.range ? widest * 2 + ' to '.length : widest;
52
55
  }
53
56
  get precision() {
@@ -141,8 +144,27 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
141
144
  const at = Number.isFinite(parsed) ? parsed : this.min + (this.max - this.min) / 2;
142
145
  return String(this.settled(at));
143
146
  }
147
+ get gap() {
148
+ return Math.max(0, this.minStepsBetweenThumbs) * this.step;
149
+ }
150
+ get formats() {
151
+ return this.formatter !== undefined || this.formatOptions !== undefined;
152
+ }
153
+ said(at) {
154
+ const number = typeof at === 'number' ? at : Number.parseFloat(at);
155
+ if (!Number.isFinite(number)) {
156
+ return String(at);
157
+ }
158
+ if (this.formatter) {
159
+ return this.formatter(number);
160
+ }
161
+ return this.formatOptions ? formatNumber(number, this.locale, this.formatOptions) : String(at);
162
+ }
144
163
  spoken(at) {
145
- return this.valueText ? fillTemplate(this.valueText, { value: at }) : nothing;
164
+ if (this.valueText) {
165
+ return fillTemplate(this.valueText, { value: this.said(at) });
166
+ }
167
+ return this.formats ? this.said(at) : nothing;
146
168
  }
147
169
  updated(changed) {
148
170
  super.updated(changed);
@@ -161,8 +183,8 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
161
183
  step=${this.step}
162
184
  .value=${String(at)}
163
185
  aria-labelledby=${this.labeled ? `${this.labelId} ${which}-name` : `${which}-name`}
164
- aria-valuemin=${which === 'end' ? start : nothing}
165
- aria-valuemax=${which === 'start' ? end : nothing}
186
+ aria-valuemin=${which === 'end' ? start + this.gap : nothing}
187
+ aria-valuemax=${which === 'start' ? end - this.gap : nothing}
166
188
  aria-valuetext=${this.spoken(at)}
167
189
  aria-describedby=${this.describedBy ?? nothing}
168
190
  aria-invalid=${this.invalidity ?? nothing}
@@ -207,7 +229,9 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
207
229
  ${this.markerValues.map((at) => html `<span
208
230
  part="marker"
209
231
  style="--slider-marker-at: ${((at - this.min) / (this.max - this.min)) * 100}%"
210
- >${this.markerLabels ? html `<span part="marker-label">${at}</span>` : nothing}</span
232
+ >${this.markerLabels
233
+ ? html `<span part="marker-label">${this.said(at)}</span>`
234
+ : nothing}</span
211
235
  >`)}
212
236
  </div>`
213
237
  : nothing}
@@ -217,13 +241,22 @@ let HelixHeadlessSlider = class HelixHeadlessSlider extends MixinControllerValue
217
241
  for=${this.range ? 'control control-end' : 'control'}
218
242
  aria-hidden="true"
219
243
  style=${`--_slider-output-reserve: ${this.reserve}ch`}
220
- >${this.range ? `${start} to ${end}` : this.value}</output
244
+ >${this.range ? `${this.said(start)} to ${this.said(end)}` : this.said(this.value)}</output
221
245
  >`
222
246
  : nothing}
223
247
  </div>
224
248
  ${this.renderHint()}${this.renderFeedback()}`;
225
249
  }
226
250
  };
251
+ __decorate([
252
+ property({ attribute: 'format-options', type: Object })
253
+ ], HelixHeadlessSlider.prototype, "formatOptions", void 0);
254
+ __decorate([
255
+ property({ attribute: false })
256
+ ], HelixHeadlessSlider.prototype, "formatter", void 0);
257
+ __decorate([
258
+ property()
259
+ ], HelixHeadlessSlider.prototype, "locale", void 0);
227
260
  __decorate([
228
261
  property({ attribute: 'marker-labels', reflect: true, type: Boolean })
229
262
  ], HelixHeadlessSlider.prototype, "markerLabels", void 0);
@@ -242,6 +275,9 @@ __decorate([
242
275
  __decorate([
243
276
  property({ attribute: 'minimum-label' })
244
277
  ], HelixHeadlessSlider.prototype, "minimumLabel", void 0);
278
+ __decorate([
279
+ property({ attribute: 'min-steps-between-thumbs', type: Number })
280
+ ], HelixHeadlessSlider.prototype, "minStepsBetweenThumbs", void 0);
245
281
  __decorate([
246
282
  property({ reflect: true })
247
283
  ], HelixHeadlessSlider.prototype, "orientation", void 0);
@@ -21,6 +21,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
21
21
  * @slot - The value.
22
22
  * @slot label - What the value measures.
23
23
  * @slot unit - The unit, shown beside the value.
24
+ *
25
+ * @csspart value - The slot the value is written in. A slot draws no box, so it takes the inherited properties, such as color and font, and passes them to the value.
24
26
  */
25
27
  export declare class HelixHeadlessStat extends HelixElement {
26
28
  /**
@@ -23,7 +23,7 @@ let HelixHeadlessStat = class HelixHeadlessStat extends HelixElement {
23
23
  }
24
24
  render() {
25
25
  return html `<slot name="label"></slot>
26
- <slot></slot>
26
+ <slot part="value"></slot>
27
27
  <slot name="unit"></slot>`;
28
28
  }
29
29
  };
@@ -38,6 +38,7 @@ import '../../button/index.js';
38
38
  * @csspart description - The line under the name.
39
39
  * @csspart label - The step's name, which is a control.
40
40
  * @csspart label-button - The button inside the label.
41
+ * @csspart label-live - The live region inside that button, rendered only from the first time the button is pending.
41
42
  * @csspart label-text - The words on that button, trimmed to their capitals and baseline.
42
43
  * @csspart mark - The glyph inside the marker: a tick, a dot, or an empty ring.
43
44
  * @csspart marker - The shape carrying the number or the check.
@@ -40,7 +40,7 @@ let HelixHeadlessStep = class HelixHeadlessStep extends HelixElement {
40
40
  renderLabel() {
41
41
  return html `<${this.tag('button')}
42
42
  part="label"
43
- exportparts="button: label-button, text: label-text"
43
+ exportparts="button: label-button, live: label-live, text: label-text"
44
44
  emphasis="ghost"
45
45
  ?disabled=${this.disabled}
46
46
  @click=${this.select}
@@ -1,5 +1,5 @@
1
1
  import { AriaForwardController, HelixFormElement, HostClickController } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessSwitch_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerCheckedInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
4
4
  /**
5
5
  * @summary A control that turns something on or off.
@@ -40,7 +40,9 @@ declare const HelixHeadlessSwitch_base: (new (...args: any[]) => import("@fusion
40
40
  * @csspart track - The track. Draw the thumb on the track itself, with
41
41
  * `::part(track)::after` in your stylesheet. This tier ships no CSS, so it
42
42
  * renders no separate thumb element, which would be an empty child that costs
43
- * your layout as much as any other child. `ListItem` measured that cost.
43
+ * your layout as much as any other child. `ListItem` measured that cost. A
44
+ * subclass that draws one returns it from `renderThumb`, as the styled switch
45
+ * does.
44
46
  *
45
47
  * @cssstate checked - While it is on.
46
48
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
@@ -70,6 +72,14 @@ export declare class HelixHeadlessSwitch extends HelixHeadlessSwitch_base {
70
72
  * rendered, which React reports on hydration and does not patch up.
71
73
  */
72
74
  private markEmpty;
75
+ /**
76
+ * The thumb, drawn inside the track after the input: nothing on this tier.
77
+ *
78
+ * A seam for a tier that draws the thumb as an element, so it can be a part
79
+ * and hold icons. An element here would be an empty child on every page that
80
+ * loads only this tier, which is the cost the `track` part notes.
81
+ */
82
+ protected renderThumb(): TemplateResult | typeof nothing;
73
83
  protected render(): TemplateResult;
74
84
  }
75
85
  declare global {
@@ -43,6 +43,9 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
43
43
  markEmpty() {
44
44
  markRegion(this.renderRoot, 'label', ['']);
45
45
  }
46
+ renderThumb() {
47
+ return nothing;
48
+ }
46
49
  render() {
47
50
  return html `<label part="field">
48
51
  <span part="track">
@@ -55,9 +58,11 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
55
58
  ?required=${this.required}
56
59
  aria-describedby=${this.describedBy ?? nothing}
57
60
  aria-invalid=${this.invalidity ?? nothing}
61
+ aria-readonly=${this.readOnly ? 'true' : nothing}
58
62
  @change=${this.handleChange}
63
+ @click=${this.holdReadOnly}
59
64
  @input=${this.stopInner}
60
- />
65
+ />${this.renderThumb()}
61
66
  </span>
62
67
  <span part="label"><slot>${this.label}</slot></span>
63
68
  </label>
@@ -0,0 +1,107 @@
1
+ import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
+ import { HelixHeadlessTag } from '../root/tag-root.js';
4
+ import { type TagSelectionMode } from '../selection.js';
5
+ /**
6
+ * @summary A set of tags that the arrow keys move through, and that can be removed and selected from the keyboard.
7
+ *
8
+ * @description
9
+ * A part of `Tag` that holds tags as one control: a grid of rows, one tab stop
10
+ * for the whole set. The arrow keys move between tags, Home and End go to the
11
+ * first and the last, and a typed letter goes to the next tag starting with
12
+ * it. Delete or Backspace removes the focused tag where it is `dismissible`,
13
+ * by sending its `helix-dismiss`: remove it from your list in the handler, and
14
+ * focus moves to the next tag, or the previous one after the last.
15
+ *
16
+ * `selection-mode` lets a reader select tags, with Space, Enter or a press:
17
+ * `single` selects one at a time and `multiple` any number. `value` holds the
18
+ * selected tags' values, separated by commas, and a change asks with
19
+ * `helix-before-change` and tells with `change`. Enter on a tag with an
20
+ * `href` follows its link. A `disabled` tag is passed over.
21
+ *
22
+ * Name the set with `label`.
23
+ *
24
+ * @since 0.1.0
25
+ * @status stable
26
+ * @aliases Tag list, Chip group, Token list
27
+ * @documentation https://fusion.dev/helix/components/tag
28
+ *
29
+ * @slot - The tags, in the order they are read.
30
+ *
31
+ * @event {Event} change - Fired once a press has changed which tags are selected.
32
+ * @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press changes which tags are selected, with the `value` it would leave. Cancelable.
33
+ * @event {HelixDismissEvent} helix-dismiss - A tag's remove button was pressed, or Delete or Backspace on the focused tag. Bubbles from that tag, which is its `target`; remove the tag in response. Cancelable.
34
+ */
35
+ export declare class HelixHeadlessTagGroup extends HelixElement {
36
+ /**
37
+ * The name of the set, such as "Filters", which a screen reader announces
38
+ * before its tags.
39
+ */
40
+ label: string;
41
+ /**
42
+ * How many tags a reader can select: `single`, `multiple`, or none.
43
+ *
44
+ * Absent selects nothing, and the tags carry no selected state at all.
45
+ */
46
+ selectionMode?: TagSelectionMode;
47
+ /**
48
+ * The selected tags' values, separated by commas, since a value may hold a
49
+ * space.
50
+ *
51
+ * Set it to start with tags selected. A tag's value is its `value`, or its
52
+ * words where it has none.
53
+ */
54
+ value: string;
55
+ /** The tag the reader was last on, which keeps the tab stop. */
56
+ private last;
57
+ /**
58
+ * The arrow keys, Home, End and typed letters, moving focus and choosing
59
+ * nothing. From the focused tag rather than the selection, since a reader
60
+ * moves through selected and unselected tags alike, and without wrapping, as
61
+ * a list of tags has a first and a last.
62
+ */
63
+ protected readonly keys: KeyboardNavigationController;
64
+ /** The tags the group holds, in order. */
65
+ get tags(): HelixHeadlessTag[];
66
+ /** The tags a key can land on, which are the ones that are not disabled. */
67
+ private get reachable();
68
+ /** A tag as the keyboard controller sees it: an item it moves focus to and never chooses. */
69
+ private navigable;
70
+ /**
71
+ * What a tag stands for in `value`: its own `value`, or its words.
72
+ *
73
+ * The words read off the tag's children here rather than through its
74
+ * `words`, which answers nothing until the tag's own first update: the
75
+ * group's first pass runs before that, and would match no tag.
76
+ */
77
+ private valueFor;
78
+ /**
79
+ * Tells every tag its place: whether it holds the one tab stop and whether
80
+ * it is selected. Through `whenHydrated`, so a tag a server rendered draws
81
+ * its served markup first.
82
+ */
83
+ private sync;
84
+ /** The tag a press or a key came from, read off the event's path. */
85
+ private tagOf;
86
+ /**
87
+ * Selects the tag, or lets it go, asking first.
88
+ *
89
+ * `helix-before-change` carries the `value` the press would leave, and a
90
+ * listener that refuses it keeps the selection. `change` follows once it
91
+ * has moved.
92
+ */
93
+ protected press(tag: HelixHeadlessTag): void;
94
+ private readonly handleKeydown;
95
+ private readonly handleClick;
96
+ private readonly handleFocusin;
97
+ connectedCallback(): void;
98
+ disconnectedCallback(): void;
99
+ protected willUpdate(changed: PropertyValues<this>): void;
100
+ protected updated(changed: PropertyValues<this>): void;
101
+ protected render(): TemplateResult;
102
+ }
103
+ declare global {
104
+ interface HTMLElementTagNameMap {
105
+ 'helix-headless-tag-group': HelixHeadlessTagGroup;
106
+ }
107
+ }
@@ -0,0 +1,157 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { commaList, customElement, HelixBeforeChangeEvent, HelixElement, hostAria, KeyboardNavigationController, requestChange, rovingStop, slottedWords, whenHydrated, } from '@fusion.dev/helix-core';
8
+ import { html } from 'lit';
9
+ import { property } from 'lit/decorators.js';
10
+ import { HelixHeadlessTag } from '../root/tag-root.js';
11
+ import { pressedValue } from '../selection.js';
12
+ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.label = '';
16
+ this.value = '';
17
+ this.keys = new KeyboardNavigationController(this, {
18
+ items: () => this.reachable.map((tag) => this.navigable(tag)),
19
+ origin: 'focus',
20
+ selects: () => false,
21
+ typeahead: (item) => item.words,
22
+ wrap: false,
23
+ });
24
+ this.handleKeydown = (event) => {
25
+ const tag = this.tagOf(event);
26
+ if (!tag || tag.disabled || event.altKey || event.ctrlKey || event.metaKey) {
27
+ return;
28
+ }
29
+ if (event.key === 'Delete' || event.key === 'Backspace') {
30
+ if (tag.dismissible) {
31
+ event.preventDefault();
32
+ tag.dismiss();
33
+ }
34
+ return;
35
+ }
36
+ if (event.key === 'Enter' && tag.href !== '') {
37
+ event.preventDefault();
38
+ tag.renderRoot.querySelector('[part~="link"]')?.click();
39
+ return;
40
+ }
41
+ if ((event.key === ' ' || event.key === 'Enter') && this.selectionMode) {
42
+ event.preventDefault();
43
+ this.press(tag);
44
+ }
45
+ };
46
+ this.handleClick = (event) => {
47
+ const tag = this.tagOf(event);
48
+ const control = event
49
+ .composedPath()
50
+ .some((node) => node instanceof Element &&
51
+ /(^| )(dismiss|link)( |$)/.test(node.getAttribute('part') ?? ''));
52
+ if (tag && !control) {
53
+ this.press(tag);
54
+ }
55
+ };
56
+ this.handleFocusin = (event) => {
57
+ const tag = this.tagOf(event);
58
+ if (tag && tag !== this.last) {
59
+ this.last = tag;
60
+ this.sync();
61
+ }
62
+ };
63
+ }
64
+ get tags() {
65
+ return [...(this.children ?? [])].filter((child) => child instanceof HelixHeadlessTag);
66
+ }
67
+ get reachable() {
68
+ return this.tags.filter((tag) => !tag.disabled);
69
+ }
70
+ navigable(tag) {
71
+ return {
72
+ contains: (node) => tag.contains(node),
73
+ disabled: tag.disabled,
74
+ focusItem: () => tag.focusItem(),
75
+ select: () => { },
76
+ selected: false,
77
+ words: tag.words,
78
+ };
79
+ }
80
+ valueFor(tag) {
81
+ return tag.value || slottedWords(tag, ['start', '']);
82
+ }
83
+ sync() {
84
+ const mode = this.selectionMode ?? 'none';
85
+ const chosen = new Set(commaList(this.value));
86
+ const tags = this.tags;
87
+ const items = tags.map((tag) => ({
88
+ disabled: tag.disabled,
89
+ selected: chosen.has(this.valueFor(tag)),
90
+ tag,
91
+ }));
92
+ const holder = rovingStop(items, items.find((item) => item.tag === this.last), { disabled: (item) => item.disabled, selected: (item) => item.selected })?.tag;
93
+ for (const tag of tags) {
94
+ whenHydrated(tag, () => {
95
+ tag.focusable = tag === holder;
96
+ tag.selected = mode === 'none' ? undefined : chosen.has(this.valueFor(tag));
97
+ });
98
+ }
99
+ }
100
+ tagOf(event) {
101
+ return event
102
+ .composedPath()
103
+ .find((node) => node instanceof HelixHeadlessTag);
104
+ }
105
+ press(tag) {
106
+ const mode = this.selectionMode ?? 'none';
107
+ if (mode === 'none' || tag.disabled) {
108
+ return;
109
+ }
110
+ requestChange({
111
+ apply: (value) => {
112
+ this.value = value;
113
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
114
+ },
115
+ event: (value) => new HelixBeforeChangeEvent({ value }),
116
+ host: this,
117
+ next: pressedValue(mode, this.value, this.valueFor(tag)),
118
+ });
119
+ }
120
+ connectedCallback() {
121
+ super.connectedCallback();
122
+ this.addEventListener('keydown', this.handleKeydown);
123
+ this.addEventListener('click', this.handleClick);
124
+ this.addEventListener('focusin', this.handleFocusin);
125
+ }
126
+ disconnectedCallback() {
127
+ super.disconnectedCallback();
128
+ this.removeEventListener('keydown', this.handleKeydown);
129
+ this.removeEventListener('click', this.handleClick);
130
+ this.removeEventListener('focusin', this.handleFocusin);
131
+ }
132
+ willUpdate(changed) {
133
+ super.willUpdate(changed);
134
+ hostAria(this.internals, { label: this.label, role: 'grid' });
135
+ this.internals.ariaMultiSelectable = this.selectionMode === 'multiple' ? 'true' : null;
136
+ }
137
+ updated(changed) {
138
+ super.updated(changed);
139
+ this.sync();
140
+ }
141
+ render() {
142
+ return html `<slot @slotchange=${() => this.sync()}></slot>`;
143
+ }
144
+ };
145
+ __decorate([
146
+ property()
147
+ ], HelixHeadlessTagGroup.prototype, "label", void 0);
148
+ __decorate([
149
+ property({ attribute: 'selection-mode' })
150
+ ], HelixHeadlessTagGroup.prototype, "selectionMode", void 0);
151
+ __decorate([
152
+ property()
153
+ ], HelixHeadlessTagGroup.prototype, "value", void 0);
154
+ HelixHeadlessTagGroup = __decorate([
155
+ customElement('helix-headless-tag-group')
156
+ ], HelixHeadlessTagGroup);
157
+ export { HelixHeadlessTagGroup };
@@ -1 +1,2 @@
1
+ export * from './group/tag-group.js';
1
2
  export * from './root/tag-root.js';
package/dist/tag/index.js CHANGED
@@ -1 +1,2 @@
1
+ export * from './group/tag-group.js';
1
2
  export * from './root/tag-root.js';