@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
@@ -4,13 +4,25 @@ 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, CommandController, customElement, HelixElement, hostAria, lockScroll, MixinControllerOpen, unlockScroll, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, CommandController, customElement, drawDismiss, drawDismissGlyph, HelixElement, headingsIn, hostAria, lockScroll, MixinControllerOpen, OpenPhaseController, reflectElements, unlockScroll, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ import { wordsIn } from '../description.js';
10
11
  let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(HelixElement) {
11
12
  constructor() {
12
13
  super(...arguments);
14
+ this.alert = false;
15
+ this.dismissible = false;
16
+ this.dismissLabel = 'Close';
13
17
  this.label = '';
18
+ this.phases = new OpenPhaseController(this, {
19
+ mark: {
20
+ entering: (on) => this.toggleState('entering', on),
21
+ exiting: (on) => this.toggleState('exiting', on),
22
+ },
23
+ open: () => this.open,
24
+ surface: () => this.dialog,
25
+ });
14
26
  this.aria = new AriaForwardController(this, { target: () => this.dialog });
15
27
  this.commands = new CommandController(this, {
16
28
  commands: {
@@ -48,6 +60,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
48
60
  this.close(submitter?.hasAttribute('value') ? submitter.value : undefined);
49
61
  };
50
62
  this.namedByHeader = false;
63
+ this.describedByBody = false;
51
64
  this.handleBackdrop = (event) => {
52
65
  const panel = this.dialog;
53
66
  if (!panel || event.target !== panel) {
@@ -93,20 +106,16 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
93
106
  }
94
107
  }
95
108
  nameByHeader() {
96
- const dialog = this.dialog;
97
- if (!dialog || !('ariaLabelledByElements' in dialog)) {
98
- return;
99
- }
100
109
  const slot = this.renderRoot.querySelector('slot[name="header"]');
101
- const heading = this.label || this.hasAttribute('aria-labelledby') ? [] : headingsIn(slot);
102
- if (heading.length > 0) {
103
- dialog.ariaLabelledByElements = heading;
104
- this.namedByHeader = true;
105
- }
106
- else if (this.namedByHeader) {
107
- dialog.ariaLabelledByElements = null;
108
- this.namedByHeader = false;
109
- }
110
+ const heading = this.label || this.hasAttribute('aria-labelledby')
111
+ ? []
112
+ : headingsIn(slot?.assignedElements({ flatten: true }) ?? []);
113
+ this.namedByHeader = reflectElements(this.dialog, 'ariaLabelledByElements', heading, this.namedByHeader);
114
+ }
115
+ describeByBody() {
116
+ const slot = this.renderRoot.querySelector('slot:not([name])');
117
+ const words = this.hasAttribute('aria-describedby') ? [] : wordsIn(slot);
118
+ this.describedByBody = reflectElements(this.dialog, 'ariaDescribedByElements', words, this.describedByBody);
110
119
  }
111
120
  willUpdate(changed) {
112
121
  super.willUpdate(changed);
@@ -124,6 +133,7 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
124
133
  updated(changed) {
125
134
  super.updated(changed);
126
135
  this.nameByHeader();
136
+ this.describeByBody();
127
137
  if (this.open) {
128
138
  lockScroll(this);
129
139
  }
@@ -169,9 +179,15 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
169
179
  signal: this.refusal.signal,
170
180
  });
171
181
  }
182
+ renderDismissIcon() {
183
+ return html `<slot name="dismiss-icon">
184
+ ${drawDismissGlyph()}
185
+ </slot>`;
186
+ }
172
187
  render() {
173
188
  return html `<dialog
174
189
  part="dialog"
190
+ role=${this.alert ? 'alertdialog' : nothing}
175
191
  closedby=${this.closedBy ?? nothing}
176
192
  aria-label=${this.label || 'Dialog'}
177
193
  @cancel=${this.handleCancel}
@@ -179,15 +195,31 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
179
195
  @close=${this.handleClose}
180
196
  >
181
197
  <slot name="icon"></slot>
182
- <slot name="header" @slotchange=${this.nameByHeader}></slot>
183
- <slot></slot>
184
- <slot name="footer"></slot>
198
+ <slot name="header" part="header" @slotchange=${this.nameByHeader}></slot>
199
+ <slot part="body" @slotchange=${this.describeByBody}></slot>
200
+ <slot name="footer" part="footer"></slot>
201
+ ${this.dismissible
202
+ ? drawDismiss({
203
+ label: this.dismissLabel,
204
+ mark: this.renderDismissIcon(),
205
+ onDismiss: () => this.close(),
206
+ })
207
+ : nothing}
185
208
  </dialog>`;
186
209
  }
187
210
  };
211
+ __decorate([
212
+ property({ type: Boolean })
213
+ ], HelixHeadlessDialog.prototype, "alert", void 0);
188
214
  __decorate([
189
215
  property({ attribute: 'closedby', reflect: true })
190
216
  ], HelixHeadlessDialog.prototype, "closedBy", void 0);
217
+ __decorate([
218
+ property({ reflect: true, type: Boolean })
219
+ ], HelixHeadlessDialog.prototype, "dismissible", void 0);
220
+ __decorate([
221
+ property({ attribute: 'dismiss-label' })
222
+ ], HelixHeadlessDialog.prototype, "dismissLabel", void 0);
191
223
  __decorate([
192
224
  property()
193
225
  ], HelixHeadlessDialog.prototype, "label", void 0);
@@ -198,9 +230,3 @@ HelixHeadlessDialog = __decorate([
198
230
  customElement('helix-headless-dialog')
199
231
  ], HelixHeadlessDialog);
200
232
  export { HelixHeadlessDialog };
201
- const headingsIn = (slot) => {
202
- const assigned = slot?.assignedElements({ flatten: true }) ?? [];
203
- const heading = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
204
- const found = assigned.flatMap((one) => one.matches(heading) ? [one] : [...one.querySelectorAll(heading)].slice(0, 1));
205
- return found.length > 0 ? found.slice(0, 1) : assigned;
206
- };
@@ -24,9 +24,15 @@ declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args:
24
24
  * **Negative values are skipped.** An arc cannot show a negative number, so
25
25
  * those rows are left out and the total is the sum of the remaining rows.
26
26
  *
27
- * The ring is `aria-hidden`. The `summary` part names the chart, from `label`
28
- * or the table's caption, and gives each slice's share, and the table beside
29
- * the chart has every amount.
27
+ * The ring is hidden from screen readers. The `summary` part names the chart,
28
+ * from `label` or the table's caption, and gives each slice's share, and the
29
+ * table beside the chart has every amount.
30
+ *
31
+ * **The ring is read by key.** The plot takes focus, named by `label` or the
32
+ * caption. Every arrow moves from slice to slice, the right and down arrows to
33
+ * the next, and Home and End go to the first and the last. The tooltip
34
+ * follows, the `live` part says the slice in words, and Escape puts the
35
+ * tooltip away.
30
36
  *
31
37
  * @since 0.1.0
32
38
  * @status stable
@@ -37,8 +43,9 @@ declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args:
37
43
  * @slot - Content in the middle of the ring, usually a total. It is read by screen readers, while the ring itself is hidden from them.
38
44
  *
39
45
  * @csspart center - The middle of the ring, holding whatever is slotted.
46
+ * @csspart live - The readout: a polite live region that says the slice the keys moved to. Hide it visually.
40
47
  * @csspart mark - The invisible point the tooltip is anchored to.
41
- * @csspart plot - The drawing.
48
+ * @csspart plot - The drawing, which takes focus to be read by key.
42
49
  * @csspart segment - One slice.
43
50
  * @csspart segment-active - The slice under the pointer. Set alongside `segment`.
44
51
  * @csspart segment-edge - One slice as a closed shape, used to draw its outline.
@@ -52,6 +59,7 @@ declare const HelixHeadlessDonutChart_base: typeof HelixElement & (new (...args:
52
59
  * @csspart tooltip-arrow-top - The point while the bubble is above the reading, on its bottom edge. Set alongside `tooltip-arrow`.
53
60
  * @csspart tooltip-bottom - The bubble while it is below the reading. Set alongside `tooltip`.
54
61
  * @csspart tooltip-cell - One column's name and value inside the tooltip.
62
+ * @csspart tooltip-cell-current - The cell the series keys rest on, in the charts whose readings hold several series. A ring draws one, so it is never set here.
55
63
  * @csspart tooltip-label - The slice's own name inside the tooltip.
56
64
  * @csspart tooltip-left - The bubble while it is left of the reading. Set alongside `tooltip`.
57
65
  * @csspart tooltip-name - A column's name inside the tooltip.
@@ -154,6 +162,19 @@ export declare class HelixHeadlessDonutChart extends HelixHeadlessDonutChart_bas
154
162
  * the table's rows named the row before the one under the pointer.
155
163
  */
156
164
  private get shares();
165
+ /**
166
+ * The palette slot for a cell, which a ring gives its first plotted column
167
+ * alone: it draws that column and no other, so a second numeric column is
168
+ * no series the keys could move to.
169
+ */
170
+ seriesOf(cell: number): number | undefined;
171
+ /**
172
+ * The number of slices, which the keys walk.
173
+ *
174
+ * The mixin counts the table's rows, and a row with no amount draws no
175
+ * slice, so a key stepping by rows would land on a slice that is not there.
176
+ */
177
+ get readings(): number;
157
178
  protected render(): TemplateResult;
158
179
  /**
159
180
  * What a reader who cannot see the ring is told: the chart's name and each
@@ -49,7 +49,9 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
49
49
  get shares() {
50
50
  const series = this.series;
51
51
  const points = series[0]?.points ?? [];
52
- const values = points.map((point) => Number.isFinite(point.value) && point.value > 0 ? point.value : 0);
52
+ const values = points.map((point) => Number.isFinite(point.value) && point.value > 0 && this.showsSeries(point.label)
53
+ ? point.value
54
+ : 0);
53
55
  const total = values.reduce((sum, one) => sum + one, 0);
54
56
  if (total === 0) {
55
57
  return [];
@@ -62,21 +64,34 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
62
64
  return { fraction, row: points[index]?.at ?? index, start: at };
63
65
  });
64
66
  }
67
+ seriesOf(cell) {
68
+ return cell === this.plottedColumns[0] ? seriesIndex(0) : undefined;
69
+ }
70
+ get readings() {
71
+ return this.shares.length;
72
+ }
65
73
  render() {
66
74
  const shares = this.shares;
67
75
  return html `${this.renderPlot(shares)}${shares.length === 0 ? nothing : this.renderSummary(shares)}`;
68
76
  }
69
77
  renderSummary(shares) {
70
78
  const rows = this.sourceRows;
71
- const parts = shares.map((share) => `${rows[share.row]?.label ?? ''} ${percentIn(this.locale).format(share.fraction)}`);
79
+ const parts = shares
80
+ .filter((share) => this.showsSeries(rows[share.row]?.label ?? ''))
81
+ .map((share) => `${rows[share.row]?.label ?? ''} ${percentIn(this.locale).format(share.fraction)}`);
72
82
  const named = this.named ? `${this.named}: ` : '';
73
- return html `<p part="summary">${named}${parts.join(', ')}.</p>`;
83
+ return html `<p id="helix-chart-summary" part="summary">${named}${parts.join(', ')}.</p>`;
74
84
  }
75
85
  renderPlot(shares) {
76
86
  return html `<div
87
+ aria-describedby=${this.named ? `helix-chart-summary${this.hole === 'none' ? '' : ' helix-chart-center'}` : nothing}
88
+ aria-label=${this.named || nothing}
89
+ aria-labelledby=${this.named ? nothing : 'helix-chart-summary'}
77
90
  id=${CHART_PLOT}
78
91
  part="plot"
92
+ role="application"
79
93
  style="--donut-chart-stroke: ${this.thickness}"
94
+ tabindex="0"
80
95
  >
81
96
  <svg
82
97
  aria-hidden="true"
@@ -95,7 +110,7 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
95
110
  />
96
111
  ${shares.map((share, index) => this.renderSegment(share, index))}
97
112
  </svg>
98
- ${this.hole === 'none' ? nothing : html `<div part="center"><slot></slot></div>`}
113
+ ${this.hole === 'none' ? nothing : html `<div id="helix-chart-center" part="center"><slot></slot></div>`}
99
114
  ${this.renderTooltip()}
100
115
  </div>`;
101
116
  }
@@ -163,7 +178,7 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
163
178
  const at = this.pointer.at;
164
179
  const share = this.shares[at];
165
180
  if (!share) {
166
- return drawTooltip(this.sourceRows, at, this.tag('tooltip'));
181
+ return drawTooltip(this.sourceRows, -1, this.tag('tooltip'), undefined, undefined, this.pointer);
167
182
  }
168
183
  const angle = (share.start + share.fraction / 2) * 2 * Math.PI * this.span + this.origin;
169
184
  const edge = EDGE;
@@ -184,7 +199,7 @@ let HelixHeadlessDonutChart = class HelixHeadlessDonutChart extends MixinChartSo
184
199
  : 'top',
185
200
  x: `${x}%`,
186
201
  y: `${y}%`,
187
- }, () => seriesIndex(at));
202
+ }, () => seriesIndex(at), this.pointer);
188
203
  }
189
204
  };
190
205
  __decorate([
@@ -1,4 +1,4 @@
1
- import { AriaForwardController, CommandController, HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement, OpenPhaseController, SwipeController } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
@@ -17,7 +17,15 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
17
17
  * - the page behind does not scroll
18
18
  *
19
19
  * A drawer with `modal="false"` leaves the page usable and still closes on
20
- * Escape.
20
+ * Escape, in a browser without `closedby` as well.
21
+ *
22
+ * **A touch or a pen swipes it away**, toward the edge it came from, resolved
23
+ * against the writing direction. A swipe past halfway, or a quick one, closes
24
+ * it through the same request as every other way out. `snap-points` lists the
25
+ * fractions of the panel a reader can leave on screen, such as `0.5 1` for a
26
+ * sheet that rests half open: it opens at the smallest, and a swipe settles at
27
+ * the nearest. While a swipe moves the panel, the distance is written on it as
28
+ * `--drawer-swipe-x` and `--drawer-swipe-y`. `swipe="false"` turns swiping off.
21
29
  *
22
30
  * The panel's name is `label` when you set it. Otherwise it is the heading in
23
31
  * the `header` slot, or the elements an `aria-labelledby` on the host names.
@@ -43,6 +51,7 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
43
51
  * @documentation https://fusion.dev/helix/components/drawer
44
52
  *
45
53
  * @slot - The drawer's content.
54
+ * @slot footer - Content pinned below the scrolling area, such as the drawer's actions.
46
55
  * @slot header - Content pinned above the scrolling area.
47
56
  *
48
57
  * @event {Event} close - Sent from the host after the drawer closes, because the native `close` event does not leave the shadow root.
@@ -51,9 +60,15 @@ declare const HelixHeadlessDrawer_base: (new (...args: any[]) => import("@fusion
51
60
  * @event {ToggleEvent} toggle - Sent when the drawer opens or closes, with `newState` and `oldState`.
52
61
  *
53
62
  * @csspart content - The scrolling content region.
63
+ * @csspart footer - The footer region.
54
64
  * @csspart header - The header region.
55
65
  * @csspart panel - The sliding panel.
56
66
  *
67
+ * @cssproperty --drawer-swipe-x - How far a swipe, or a snap point, has moved the panel across, in pixels, toward the right.
68
+ * @cssproperty --drawer-swipe-y - How far a swipe, or a snap point, has moved the panel down, in pixels.
69
+ *
70
+ * @cssstate entering - From the moment the drawer opens until its entrance has finished.
71
+ * @cssstate exiting - From the moment the drawer closes until its exit has finished.
57
72
  * @cssstate open - While the drawer is open.
58
73
  */
59
74
  export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
@@ -100,6 +115,21 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
100
115
  * guarantees is the absence, and a sheet selects on it.
101
116
  */
102
117
  placement?: 'start' | 'end' | 'top' | 'bottom';
118
+ /**
119
+ * The fractions of the panel a reader can leave on screen, as a list such as
120
+ * `0.5 1`. The drawer opens at the smallest, and a swipe comes to rest at the
121
+ * nearest, or closes the drawer below the smallest.
122
+ *
123
+ * Empty is `1`: the whole panel, and a swipe either closes it or puts it
124
+ * back.
125
+ */
126
+ snapPoints: string;
127
+ /**
128
+ * Whether a touch or a pen can swipe the drawer away. `swipe="false"` turns
129
+ * it off, for a panel whose content has its own use for the gesture, such as
130
+ * a map.
131
+ */
132
+ swipe: boolean;
103
133
  /**
104
134
  * What the dialog closed with, the way `<dialog>` reports it.
105
135
  *
@@ -120,6 +150,37 @@ export declare class HelixHeadlessDrawer extends HelixHeadlessDrawer_base {
120
150
  set returnValue(value: string);
121
151
  /** What a value set before the element existed is kept in. */
122
152
  private returned;
153
+ protected readonly phases: OpenPhaseController;
154
+ /** How far the panel rests toward its edge, in pixels, at the snap point it settled on. */
155
+ private rest;
156
+ /** The edge the panel leaves through, from its placement and the writing direction. */
157
+ private get edge();
158
+ /** The panel's size along the swipe, in pixels. */
159
+ private get extent();
160
+ protected readonly swipes: SwipeController;
161
+ /**
162
+ * Writes how far the panel sits toward its edge, as the two properties the
163
+ * styled tier translates it by. Nothing is written at rest.
164
+ */
165
+ private place;
166
+ /** Brings a released swipe to rest: at a snap point, or closed through the request. */
167
+ private settle;
168
+ /** Opens the drawer. */
169
+ show(): void;
170
+ /**
171
+ * Requests that the drawer close, and takes what it closed with, the way
172
+ * `Dialog.close(value)` does. A `helix-before-close` listener can refuse it,
173
+ * and then the value is not kept.
174
+ */
175
+ close(returnValue?: string): void;
176
+ /**
177
+ * Escape on a drawer that is not modal, in a browser without `closedby`.
178
+ *
179
+ * `show()` gives a `<dialog>` no close watcher, and `closedby="closerequest"`
180
+ * is what asks for one. Where the browser does not know the attribute, the
181
+ * key is read here, and comes through the same request.
182
+ */
183
+ private readonly handleKeydown;
123
184
  /** The panel, which is a real `<dialog>`. */
124
185
  private get panel();
125
186
  /**
@@ -4,9 +4,10 @@ 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, CommandController, customElement, HelixElement, lockScroll, MixinControllerOpen, onUnlessFalse, unlockScroll, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, CommandController, customElement, edgeOffset, HelixElement, headingsIn, isRtl, lockScroll, MixinControllerOpen, OpenPhaseController, onUnlessFalse, parseSnapPoints, reflectElements, SwipeController, settleSwipe, snapTravel, swipeAxis, swipeEdge, towardEdge, unlockScroll, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ import { startsSwipe } from '../swipe-start.js';
10
11
  let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(HelixElement) {
11
12
  constructor() {
12
13
  super(...arguments);
@@ -20,7 +21,36 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
20
21
  this.label = '';
21
22
  this.aria = new AriaForwardController(this, { target: () => this.panel });
22
23
  this.modal = true;
24
+ this.snapPoints = '';
25
+ this.swipe = true;
23
26
  this.returned = '';
27
+ this.phases = new OpenPhaseController(this, {
28
+ mark: {
29
+ entering: (on) => this.toggleState('entering', on),
30
+ exiting: (on) => this.toggleState('exiting', on),
31
+ },
32
+ open: () => this.open,
33
+ surface: () => this.panel,
34
+ });
35
+ this.rest = 0;
36
+ this.swipes = new SwipeController(this, {
37
+ axis: () => (this.open && this.swipe ? swipeAxis(this.edge) : undefined),
38
+ end: (dx, dy, vx, vy) => this.settle(dx, dy, vx, vy),
39
+ move: (dx, dy) => this.place(Math.max(0, this.rest + towardEdge(this.edge, dx, dy))),
40
+ start: (target, event) => startsSwipe(target, event.pointerType, swipeAxis(this.edge), this.partNamed('content')),
41
+ surface: () => this.panel,
42
+ });
43
+ this.handleKeydown = (event) => {
44
+ if (event.key !== 'Escape' ||
45
+ event.defaultPrevented ||
46
+ this.modal ||
47
+ !this.open ||
48
+ closedByKnown()) {
49
+ return;
50
+ }
51
+ event.preventDefault();
52
+ this.requestOpen(false);
53
+ };
24
54
  this.handleCancel = (event) => {
25
55
  event.preventDefault();
26
56
  this.requestOpen(false);
@@ -57,25 +87,62 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
57
87
  panel.returnValue = value;
58
88
  }
59
89
  }
60
- get panel() {
61
- return this.partNamed('panel');
90
+ get edge() {
91
+ return swipeEdge(this.placement ?? 'end', isRtl(this));
62
92
  }
63
- nameByHeader() {
93
+ get extent() {
94
+ const box = this.panel?.getBoundingClientRect();
95
+ return (swipeAxis(this.edge) === 'x' ? box?.width : box?.height) ?? 0;
96
+ }
97
+ place(travel) {
64
98
  const panel = this.panel;
65
- if (!panel || !('ariaLabelledByElements' in panel)) {
99
+ if (!panel) {
66
100
  return;
67
101
  }
68
- const slot = this.renderRoot.querySelector('slot[name="header"]');
69
- const heading = this.label || this.hasAttribute('aria-labelledby') ? [] : headingsIn(slot);
70
- if (heading.length > 0) {
71
- panel.ariaLabelledByElements = heading;
72
- this.namedByHeader = true;
102
+ const { x, y } = edgeOffset(this.edge, travel);
103
+ const px = (value) => (value === 0 ? '' : `${Math.round(value)}px`);
104
+ panel.style.setProperty('--drawer-swipe-x', px(x));
105
+ panel.style.setProperty('--drawer-swipe-y', px(y));
106
+ }
107
+ settle(dx, dy, vx, vy) {
108
+ const edge = this.edge;
109
+ const size = this.extent;
110
+ const shown = settleSwipe({
111
+ points: parseSnapPoints(this.snapPoints),
112
+ size,
113
+ travel: Math.max(0, this.rest + towardEdge(edge, dx, dy)),
114
+ velocity: towardEdge(edge, vx, vy),
115
+ });
116
+ if (shown === 0 && this.requestOpen(false)) {
117
+ return;
118
+ }
119
+ if (shown !== 0) {
120
+ this.rest = snapTravel(size, shown);
121
+ }
122
+ this.place(this.rest);
123
+ }
124
+ show() {
125
+ this.requestOpen(true);
126
+ }
127
+ close(returnValue) {
128
+ const previous = this.returnValue;
129
+ if (returnValue !== undefined) {
130
+ this.returnValue = returnValue;
73
131
  }
74
- else if (this.namedByHeader) {
75
- panel.ariaLabelledByElements = null;
76
- this.namedByHeader = false;
132
+ if (!this.requestOpen(false) && returnValue !== undefined) {
133
+ this.returnValue = previous;
77
134
  }
78
135
  }
136
+ get panel() {
137
+ return this.partNamed('panel');
138
+ }
139
+ nameByHeader() {
140
+ const slot = this.renderRoot.querySelector('slot[name="header"]');
141
+ const heading = this.label || this.hasAttribute('aria-labelledby')
142
+ ? []
143
+ : headingsIn(slot?.assignedElements({ flatten: true }) ?? []);
144
+ this.namedByHeader = reflectElements(this.panel, 'ariaLabelledByElements', heading, this.namedByHeader);
145
+ }
79
146
  disconnectedCallback() {
80
147
  super.disconnectedCallback();
81
148
  unlockScroll(this);
@@ -83,6 +150,10 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
83
150
  updated(changed) {
84
151
  super.updated(changed);
85
152
  this.nameByHeader();
153
+ if (changed.has('open') && !this.open) {
154
+ this.rest = 0;
155
+ this.place(0);
156
+ }
86
157
  if (this.open && this.modal) {
87
158
  lockScroll(this);
88
159
  }
@@ -100,6 +171,8 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
100
171
  else {
101
172
  panel.show();
102
173
  }
174
+ this.rest = snapTravel(this.extent, parseSnapPoints(this.snapPoints)[0] ?? 1);
175
+ this.place(this.rest);
103
176
  }
104
177
  else if (!this.open && panel.open) {
105
178
  panel.close();
@@ -114,9 +187,11 @@ let HelixHeadlessDrawer = class HelixHeadlessDrawer extends MixinControllerOpen(
114
187
  @cancel=${this.handleCancel}
115
188
  @click=${this.handleBackdrop}
116
189
  @close=${this.handleClose}
190
+ @keydown=${this.handleKeydown}
117
191
  >
118
192
  <div part="header"><slot name="header" @slotchange=${this.nameByHeader}></slot></div>
119
193
  <div part="content"><slot></slot></div>
194
+ <div part="footer"><slot name="footer"></slot></div>
120
195
  </dialog>`;
121
196
  }
122
197
  };
@@ -132,13 +207,14 @@ __decorate([
132
207
  __decorate([
133
208
  property({ reflect: true })
134
209
  ], HelixHeadlessDrawer.prototype, "placement", void 0);
210
+ __decorate([
211
+ property({ attribute: 'snap-points' })
212
+ ], HelixHeadlessDrawer.prototype, "snapPoints", void 0);
213
+ __decorate([
214
+ property({ converter: onUnlessFalse })
215
+ ], HelixHeadlessDrawer.prototype, "swipe", void 0);
135
216
  HelixHeadlessDrawer = __decorate([
136
217
  customElement('helix-headless-drawer')
137
218
  ], HelixHeadlessDrawer);
138
219
  export { HelixHeadlessDrawer };
139
- const headingsIn = (slot) => {
140
- const assigned = slot?.assignedElements({ flatten: true }) ?? [];
141
- const heading = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
142
- const found = assigned.flatMap((one) => one.matches(heading) ? [one] : [...one.querySelectorAll(heading)].slice(0, 1));
143
- return found.length > 0 ? found.slice(0, 1) : assigned;
144
- };
220
+ const closedByKnown = () => typeof HTMLDialogElement !== 'undefined' && 'closedBy' in HTMLDialogElement.prototype;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether a press may start swiping a drawer away.
3
+ *
4
+ * Not a mouse, which drags to select text. Not a press on a field or on
5
+ * editable text, which drags to move its caret. And not a press inside a
6
+ * scroller that can scroll along the swipe, which drags to scroll, so a bottom
7
+ * sheet holding a long list scrolls the list.
8
+ */
9
+ export declare const startsSwipe: (target: Element, pointerType: string, axis: 'x' | 'y', scroller: Element | null | undefined) => boolean;
@@ -0,0 +1,12 @@
1
+ const EDITABLE = 'input, select, textarea, [contenteditable]:not([contenteditable="false"])';
2
+ export const startsSwipe = (target, pointerType, axis, scroller) => {
3
+ if (pointerType === 'mouse' || target.closest(EDITABLE)) {
4
+ return false;
5
+ }
6
+ if (!scroller || !(scroller === target || scroller.contains(target))) {
7
+ return true;
8
+ }
9
+ return axis === 'x'
10
+ ? scroller.scrollWidth <= scroller.clientWidth
11
+ : scroller.scrollHeight <= scroller.clientHeight;
12
+ };
@@ -4,6 +4,7 @@ registerElementEvents({
4
4
  'helix-headless-announcement': ['helix-dismiss'],
5
5
  'helix-headless-app-header': ['helix-scrolled'],
6
6
  'helix-headless-avatar': ['error', 'load'],
7
+ 'helix-headless-callout': ['helix-dismiss'],
7
8
  'helix-headless-canvas': [
8
9
  'helix-before-move',
9
10
  'helix-before-reconnect',
@@ -58,7 +59,7 @@ registerElementEvents({
58
59
  'helix-select',
59
60
  'toggle',
60
61
  ],
61
- 'helix-headless-copy-button': ['helix-copy'],
62
+ 'helix-headless-copy-button': ['helix-copy', 'helix-copy-error'],
62
63
  'helix-headless-data-table': [
63
64
  'helix-activate',
64
65
  'helix-filter',
@@ -84,7 +85,7 @@ registerElementEvents({
84
85
  'helix-headless-drawer': ['close', 'helix-before-close', 'helix-before-open', 'toggle'],
85
86
  'helix-headless-drilldown': ['helix-select'],
86
87
  'helix-headless-drilldown-item': ['helix-select'],
87
- 'helix-headless-file-input': ['change', 'helix-before-change', 'input'],
88
+ 'helix-headless-file-input': ['change', 'helix-before-change', 'helix-reject', 'input'],
88
89
  'helix-headless-filter-chip': ['helix-dismiss'],
89
90
  'helix-headless-filter-input': [
90
91
  'change',
@@ -110,10 +111,11 @@ registerElementEvents({
110
111
  'helix-graph-zoom',
111
112
  ],
112
113
  'helix-headless-heatmap': ['helix-resize'],
113
- 'helix-headless-icon': ['error'],
114
+ 'helix-headless-icon': ['error', 'load'],
114
115
  'helix-headless-image': ['error', 'load'],
115
- 'helix-headless-input': ['change', 'helix-before-change', 'input'],
116
+ 'helix-headless-input': ['change', 'helix-before-change', 'helix-clear', 'input'],
116
117
  'helix-headless-kbd': ['helix-shortcut'],
118
+ 'helix-headless-legend': ['change', 'helix-before-change'],
117
119
  'helix-headless-lightbox': [
118
120
  'close',
119
121
  'helix-activate',
@@ -163,7 +165,7 @@ registerElementEvents({
163
165
  'helix-headless-qr-code': ['error'],
164
166
  'helix-headless-radio': ['helix-select'],
165
167
  'helix-headless-radio-group': ['change', 'helix-before-change', 'helix-select', 'input'],
166
- 'helix-headless-rating': ['change', 'helix-before-change', 'input'],
168
+ 'helix-headless-rating': ['change', 'helix-before-change', 'helix-clear', 'input'],
167
169
  'helix-headless-select': [
168
170
  'change',
169
171
  'helix-before-change',
@@ -195,6 +197,7 @@ registerElementEvents({
195
197
  'helix-headless-table-column': ['helix-before-resize'],
196
198
  'helix-headless-tabs': ['helix-activate', 'helix-before-activate', 'helix-select'],
197
199
  'helix-headless-tag': ['helix-dismiss'],
200
+ 'helix-headless-tag-group': ['change', 'helix-before-change', 'helix-dismiss'],
198
201
  'helix-headless-textarea': ['change', 'helix-before-change', 'input'],
199
202
  'helix-headless-time-input': [
200
203
  'change',
@@ -25,7 +25,7 @@ import { type TemplateResult } from 'lit';
25
25
  *
26
26
  * @slot - The message: why the region is empty, and what to do about it.
27
27
  * @slot actions - The action that would put something in the region.
28
- * @slot heading - The heading, which names what is missing.
28
+ * @slot heading - The heading, which names what is missing. Slot a heading element, such as an `<h3>`, so a reader can find it in the page's outline.
29
29
  * @slot icon - An icon above the heading.
30
30
  *
31
31
  * @csspart live - A copy of the heading and the message, in a live region that screen readers announce when the view appears. Visually hidden in the styled tier.