@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,11 +4,11 @@ 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 { anchorElement, customElement, elementById, HelixElement, hostAria, MixinControllerOpen, PopoverController, reportRuntime, } from '@fusion.dev/helix-core';
7
+ import { anchorElement, arrowReach, customElement, elementById, HelixElement, hostAria, linkDescription, MixinControllerOpen, PopoverController, reportRuntime, sideOf, unlinkDescription, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
+ import { focusTarget } from '../focus-target.js';
10
11
  const GENERIC = new Set(['div', 'p', 'section', 'span']);
11
- const SKIP_DELAY = 300;
12
12
  const WARMTH = Symbol.for('fusion.helix.tooltip.warmth');
13
13
  const warmth = () => {
14
14
  const globals = globalThis;
@@ -26,22 +26,20 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
26
26
  this.offset = 8;
27
27
  this.persist = false;
28
28
  this.placement = 'top';
29
+ this.skipDelay = 300;
29
30
  this.within = '';
30
31
  this.holds = new Set();
31
32
  this.bubbleLayer = new PopoverController(this, {
32
33
  anchor: () => this.trigger,
33
34
  arrow: () => this.partNamed('arrow'),
34
35
  boundary: () => [this, this.named],
35
- offset: () => {
36
- const point = this.partNamed('arrow');
37
- return this.offset + (point?.offsetWidth ?? 0) / Math.SQRT2;
38
- },
36
+ offset: () => this.offset + arrowReach(this.partNamed('arrow')),
39
37
  onDismiss: () => {
40
38
  this.holds.clear();
41
39
  this.hide();
42
40
  },
43
41
  onPlace: (placement) => {
44
- const side = placement.split('-')[0];
42
+ const side = sideOf(placement);
45
43
  this.toggleState('placed-bottom', side === 'bottom');
46
44
  this.toggleState('placed-left', side === 'left');
47
45
  this.toggleState('placed-right', side === 'right');
@@ -56,11 +54,14 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
56
54
  this.requestUpdate();
57
55
  };
58
56
  this.showLater = () => {
57
+ if (this.showOn === 'focus') {
58
+ return;
59
+ }
59
60
  this.holds.add('pointer');
60
61
  clearTimeout(this.timer);
61
62
  const warm = warmth();
62
63
  const others = [...warm.showing].some((one) => one !== this);
63
- if (others || performance.now() - warm.hidAt < SKIP_DELAY) {
64
+ if (others || performance.now() - warm.hidAt < this.skipDelay) {
64
65
  this.show();
65
66
  return;
66
67
  }
@@ -102,6 +103,10 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
102
103
  this.holds.clear();
103
104
  this.hide();
104
105
  };
106
+ this.linkOnArrival = (event) => {
107
+ const first = event.composedPath()[0];
108
+ this.link(true, event.type === 'focusin' && first instanceof Element ? first : undefined);
109
+ };
105
110
  this.audited = false;
106
111
  }
107
112
  get trigger() {
@@ -179,14 +184,41 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
179
184
  return;
180
185
  }
181
186
  this.unbind();
187
+ target.addEventListener('pointerenter', this.linkOnArrival);
188
+ target.addEventListener('focusin', this.linkOnArrival);
182
189
  target.addEventListener('pointerenter', this.showLater);
183
190
  target.addEventListener('pointerleave', this.hideLater);
184
191
  target.addEventListener('focusin', this.showNow);
185
192
  target.addEventListener('focusout', this.releaseFocus);
186
193
  target.addEventListener('click', this.handleActivate);
187
194
  this.bound = target;
195
+ this.link(false);
196
+ const drawn = this.trigger.updateComplete;
197
+ void drawn?.then(() => {
198
+ if (this.bound === target) {
199
+ this.link(false);
200
+ }
201
+ });
202
+ }
203
+ link(light, target = focusTarget(this.trigger)) {
204
+ if (this.linked === target) {
205
+ return;
206
+ }
207
+ if (this.linked) {
208
+ unlinkDescription(this.linked, this);
209
+ this.linked = undefined;
210
+ }
211
+ if (linkDescription(target, this, light)) {
212
+ this.linked = target;
213
+ }
188
214
  }
189
215
  unbind() {
216
+ if (this.linked) {
217
+ unlinkDescription(this.linked, this);
218
+ this.linked = undefined;
219
+ }
220
+ this.bound?.removeEventListener('pointerenter', this.linkOnArrival);
221
+ this.bound?.removeEventListener('focusin', this.linkOnArrival);
190
222
  this.bound?.removeEventListener('pointerenter', this.showLater);
191
223
  this.bound?.removeEventListener('pointerleave', this.hideLater);
192
224
  this.bound?.removeEventListener('click', this.handleActivate);
@@ -199,6 +231,9 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
199
231
  return;
200
232
  }
201
233
  this.audited = true;
234
+ if (this.linked) {
235
+ return;
236
+ }
202
237
  if (this.for === '') {
203
238
  if (this.triggerSays(this.describedText)) {
204
239
  return;
@@ -293,7 +328,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
293
328
  }
294
329
  hostAria(this.internals, {
295
330
  describedBy: this.hasContent ? [bubble] : [],
296
- label: this.for === '' ? '' : this.describedText,
331
+ label: this.describedText,
297
332
  });
298
333
  }
299
334
  render() {
@@ -339,6 +374,12 @@ __decorate([
339
374
  __decorate([
340
375
  property()
341
376
  ], HelixHeadlessTooltip.prototype, "placement", void 0);
377
+ __decorate([
378
+ property({ attribute: 'show-on' })
379
+ ], HelixHeadlessTooltip.prototype, "showOn", void 0);
380
+ __decorate([
381
+ property({ attribute: 'skip-delay', type: Number })
382
+ ], HelixHeadlessTooltip.prototype, "skipDelay", void 0);
342
383
  __decorate([
343
384
  property()
344
385
  ], HelixHeadlessTooltip.prototype, "within", void 0);
@@ -0,0 +1,30 @@
1
+ /** What names and describes a stop's panel. */
2
+ export interface StopNaming {
3
+ /** The elements that describe the panel, in reading order. */
4
+ describedBy: Element[];
5
+ /** The elements that name the panel, or none where a label names it. */
6
+ labelledBy: Element[];
7
+ }
8
+ /** Where a stop's words are. */
9
+ export interface StopWords {
10
+ /** The elements in the stop's default slot. */
11
+ content: readonly Element[];
12
+ /** The elements in its `description` slot. */
13
+ description: readonly Element[];
14
+ /** The elements in its `heading` slot. */
15
+ heading: readonly Element[];
16
+ /** Whether the stop has a `label` of its own. */
17
+ labeled: boolean;
18
+ /** The element that says the stop's place in the tour, such as "2 of 5". */
19
+ progress: Element | null;
20
+ }
21
+ /**
22
+ * What names a tour stop's panel and what describes it.
23
+ *
24
+ * Named by the `heading` slot, then by the first heading in the content, then
25
+ * by the stop's own `label`, and where there is none of those, by its place in
26
+ * the tour, so no stop is an unnamed group. Described by the `description`
27
+ * slot and by its place, which is how a screen reader hears where it is as
28
+ * focus moves from one stop to the next.
29
+ */
30
+ export declare const stopNaming: ({ content, description, heading, labeled, progress, }: StopWords) => StopNaming;
@@ -0,0 +1,10 @@
1
+ import { headingIn } from '@fusion.dev/helix-core';
2
+ export const stopNaming = ({ content, description, heading, labeled, progress, }) => {
3
+ const first = heading.length > 0 ? null : headingIn(content);
4
+ const found = heading.length > 0 ? [...heading] : first ? [first] : [];
5
+ const place = progress ? [progress] : [];
6
+ if (found.length === 0 && !labeled) {
7
+ return { describedBy: [...description], labelledBy: place };
8
+ }
9
+ return { describedBy: [...description, ...place], labelledBy: found };
10
+ };
@@ -22,6 +22,13 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
22
22
  * `toggle` work as they do on `Dialog`. Setting `open` starts the run, and
23
23
  * `skip()` and `requestOpen(false)` end it early.
24
24
  *
25
+ * Each stop is told its place in the run, from `progress-label`, such as "2 of
26
+ * 5". It describes the stop's panel, so a screen reader says it at every stop,
27
+ * and `progress` draws it in the panel as well. A dialog or a floating stop,
28
+ * which has nothing to light, is shown over the `backdrop` part. At a `wait`
29
+ * stop the run waits for that stop's target to be on the page, and moves on by
30
+ * itself.
31
+ *
25
32
  * **Focus follows the run.** Opening moves focus to the first stop's panel,
26
33
  * and moving on from a control in a stop moves it to the next one. Ending the
27
34
  * tour puts focus back on the element that had it when the tour opened, unless
@@ -45,6 +52,7 @@ declare const HelixHeadlessTour_base: (new (...args: any[]) => import("@fusion.d
45
52
  * @event {HelixTourAskEvent} helix-tour-ask - Fired when a stop's control is pressed to move the tour. The stop sends it, and the tour handles it.
46
53
  * @event {ToggleEvent} toggle - Sent when the tour starts or ends, with `newState` and `oldState`.
47
54
  *
55
+ * @csspart backdrop - Behind a dialog or a floating stop, which lights nothing. It draws no box until you give it one.
48
56
  * @csspart spotlight - The box over the current target. It also draws the dimmed area around the target.
49
57
  *
50
58
  * @cssstate open - While the tour is running.
@@ -86,6 +94,15 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
86
94
  * Label for the control that goes on.
87
95
  */
88
96
  nextLabel: string;
97
+ /**
98
+ * Draws each stop's place in the run in its panel, as well as saying it.
99
+ */
100
+ progress: boolean;
101
+ /**
102
+ * Each stop's place in the run, with `{index}` and `{count}` filled in.
103
+ * Translate it with the rest of your text.
104
+ */
105
+ progressLabel: string;
89
106
  /**
90
107
  * Label for the control that leaves the tour.
91
108
  */
@@ -160,6 +177,24 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
160
177
  private settle;
161
178
  /** The current stop's target while the run is open, which is what the light falls on. */
162
179
  private get lit();
180
+ /** The stop being shown, or nothing where there are no stops. */
181
+ private get current();
182
+ /** Stops waiting, where a `wait` stop is waiting. */
183
+ private unwait;
184
+ /** The `wait` stop being waited on, so a render that changed nothing starts nothing. */
185
+ private waiting;
186
+ /**
187
+ * Waits at a `wait` stop for its target to be on the page, then moves on.
188
+ *
189
+ * On the stop's own root, which is where its `for` resolves.
190
+ */
191
+ private wait;
192
+ /**
193
+ * Whether the current stop is shown over the backdrop: a dialog or a floating
194
+ * stop, or one with no target, which have nothing to light. Read with the
195
+ * light, after the stops are known, since the first render cannot know them.
196
+ */
197
+ private veiled;
163
198
  /** Reads the current target's box, which is where the light falls. */
164
199
  private light;
165
200
  /** Keeps `sizer` on the lit target, and on nothing once there is none. */
@@ -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 { customElement, deepActiveElement, deepContains, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, hostAria, MixinControllerOpen, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, deepContains, fillTemplate, HelixActivateEvent, HelixBeforeActivateEvent, HelixCompleteEvent, HelixElement, hostAria, MixinControllerOpen, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property, queryAssignedElements, state } from 'lit/decorators.js';
10
+ import { onPage, waitFor } from '../wait.js';
10
11
  let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(HelixElement) {
11
12
  constructor() {
12
13
  super(...arguments);
@@ -16,6 +17,8 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
16
17
  this.indexState = undefined;
17
18
  this.label = '';
18
19
  this.nextLabel = '';
20
+ this.progress = false;
21
+ this.progressLabel = '{index} of {count}';
19
22
  this.skipLabel = '';
20
23
  this.spotlightPadding = 8;
21
24
  this.sizer = undefined;
@@ -42,6 +45,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
42
45
  this.light();
43
46
  };
44
47
  this.followed = false;
48
+ this.veiled = false;
45
49
  }
46
50
  get index() {
47
51
  return this.indexState ?? this.defaultIndex;
@@ -108,6 +112,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
108
112
  window.removeEventListener('scroll', this.handleViewport, { capture: true });
109
113
  this.sizer?.disconnect();
110
114
  this.sized = undefined;
115
+ this.unwait?.();
116
+ this.unwait = undefined;
117
+ this.waiting = undefined;
111
118
  }
112
119
  willUpdate(changed) {
113
120
  super.willUpdate(changed);
@@ -121,6 +128,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
121
128
  this.settle();
122
129
  this.light();
123
130
  this.follow(changed);
131
+ this.wait();
124
132
  }
125
133
  follow(changed) {
126
134
  if (!this.followed) {
@@ -168,6 +176,11 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
168
176
  stop.current = this.open && index === at;
169
177
  stop.first = index === 0;
170
178
  stop.last = index === stops.length - 1;
179
+ stop.position = fillTemplate(this.progressLabel, {
180
+ count: stops.length,
181
+ index: index + 1,
182
+ });
183
+ stop.progress = this.progress;
171
184
  if (this.backLabel !== '') {
172
185
  stop.backLabel = this.backLabel;
173
186
  }
@@ -184,12 +197,41 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
184
197
  });
185
198
  }
186
199
  get lit() {
200
+ const stop = this.current;
201
+ return this.open && stop?.type === undefined ? stop?.target : undefined;
202
+ }
203
+ get current() {
187
204
  const stops = this.stops;
188
- return this.open
189
- ? stops[Math.max(0, Math.min(this.index, stops.length - 1))]?.target
190
- : undefined;
205
+ return stops[Math.max(0, Math.min(this.index, stops.length - 1))];
206
+ }
207
+ wait() {
208
+ const stop = this.open ? this.current : undefined;
209
+ const next = stop?.type === 'wait' ? stop : undefined;
210
+ if (next === this.waiting) {
211
+ return;
212
+ }
213
+ this.unwait?.();
214
+ this.unwait = undefined;
215
+ this.waiting = next;
216
+ if (!next) {
217
+ return;
218
+ }
219
+ this.unwait = waitFor(next.getRootNode(), () => onPage(next.target), () => {
220
+ this.waiting = undefined;
221
+ this.unwait = undefined;
222
+ queueMicrotask(() => {
223
+ if (this.open && this.current === next) {
224
+ this.next();
225
+ }
226
+ });
227
+ });
191
228
  }
192
229
  light() {
230
+ const stop = this.open ? this.current : undefined;
231
+ const veiled = stop !== undefined && stop.type !== 'wait' && (stop.type !== undefined || !stop.target);
232
+ if (veiled !== this.veiled) {
233
+ this.veiled = veiled;
234
+ }
193
235
  const target = this.lit;
194
236
  this.watch(target);
195
237
  if (!target || typeof target.getBoundingClientRect !== 'function') {
@@ -227,7 +269,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
227
269
  }
228
270
  render() {
229
271
  const spot = this.spot;
230
- return html `${spot === undefined
272
+ return html `${this.veiled ? html `<div part="backdrop" aria-hidden="true"></div>` : ''}${spot === undefined
231
273
  ? ''
232
274
  : html `<div
233
275
  part="spotlight"
@@ -255,6 +297,12 @@ __decorate([
255
297
  __decorate([
256
298
  property({ attribute: 'next-label' })
257
299
  ], HelixHeadlessTour.prototype, "nextLabel", void 0);
300
+ __decorate([
301
+ property({ type: Boolean })
302
+ ], HelixHeadlessTour.prototype, "progress", void 0);
303
+ __decorate([
304
+ property({ attribute: 'progress-label' })
305
+ ], HelixHeadlessTour.prototype, "progressLabel", void 0);
258
306
  __decorate([
259
307
  property({ attribute: 'skip-label' })
260
308
  ], HelixHeadlessTour.prototype, "skipLabel", void 0);
@@ -267,6 +315,9 @@ __decorate([
267
315
  __decorate([
268
316
  state()
269
317
  ], HelixHeadlessTour.prototype, "spot", void 0);
318
+ __decorate([
319
+ state()
320
+ ], HelixHeadlessTour.prototype, "veiled", void 0);
270
321
  HelixHeadlessTour = __decorate([
271
322
  customElement('helix-headless-tour')
272
323
  ], HelixHeadlessTour);
@@ -1,5 +1,5 @@
1
1
  import { HelixElement, type Placement } from '@fusion.dev/helix-core';
2
- import type { PropertyValues, TemplateResult } from 'lit';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../button/index.js';
4
4
  /**
5
5
  * @summary One stop on a tour: the element it points at, and a panel of content about it.
@@ -15,7 +15,16 @@ import '../../button/index.js';
15
15
  * - **Visibility.** The stop shows when it is `current`, which the root sets.
16
16
  *
17
17
  * Put any content in the stop, such as a `Card`, or a heading and a line of
18
- * text. There are no fixed title or body parts.
18
+ * text. The `heading` and `description` slots are optional places for the
19
+ * title and the line under it, which then name and describe the panel.
20
+ *
21
+ * **A stop has a type.** Without one it is a bubble pointing at its target.
22
+ * `type="dialog"` is a panel in the middle of the window, over a backdrop, for
23
+ * a welcome or a summary with nothing to point at. `type="floating"` is a
24
+ * panel in a corner of the window, which the styled tier puts at the end of
25
+ * the line, for a note that points at nothing in particular. `type="wait"` shows nothing: the tour waits
26
+ * for the element `for` names to be on the page, such as a menu the reader has
27
+ * to open, and moves on by itself.
19
28
  *
20
29
  * **The buttons are slot fallback.** With nothing in the `actions` slot, you
21
30
  * get Back, Next, and Skip, already wired. Slot your own `actions` and drive
@@ -25,9 +34,12 @@ import '../../button/index.js';
25
34
  * on the page from markup. It resolves against the stop's root node, so a stop
26
35
  * inside a shadow tree points at a target in that tree.
27
36
  *
28
- * The panel is a `group`, named by the first heading in the stop's content,
29
- * and only while the stop is shown. `focus()` moves focus to the panel, which
30
- * is what the tour does when it opens or moves on.
37
+ * The panel is a `group`, and a `dialog` for a dialog stop, only while the
38
+ * stop is shown. It is named by the `heading` slot, then by the first heading
39
+ * in the content, then by `label`, and otherwise by its place in the tour. It
40
+ * is described by the `description` slot and by its place, such as "2 of 5",
41
+ * so a screen reader says where the reader is at every stop. `focus()` moves
42
+ * focus to the panel, which is what the tour does when it opens or moves on.
31
43
  *
32
44
  * @since 0.1.0
33
45
  * @status stable
@@ -36,6 +48,8 @@ import '../../button/index.js';
36
48
  *
37
49
  * @slot - The stop's content. Any content works, such as a card, or a heading and a line.
38
50
  * @slot actions - Replaces the controls, which are Back, Next, and Skip.
51
+ * @slot description - A line under the title, which describes the panel.
52
+ * @slot heading - The stop's title, which names the panel.
39
53
  *
40
54
  * @event {HelixTourAskEvent} helix-tour-ask - Fired when one of the stop's controls is pressed, to move the tour.
41
55
  *
@@ -47,14 +61,20 @@ import '../../button/index.js';
47
61
  * @csspart arrow-top - The arrow while the panel is above its target, on the panel's bottom edge. Applied with `arrow`.
48
62
  * @csspart back - The control that goes to the previous stop.
49
63
  * @csspart back-button - The button inside `back`.
64
+ * @csspart back-live - The live region inside `back`.
50
65
  * @csspart back-text - The words on that button, trimmed to their capitals and baseline.
51
66
  * @csspart bubble - The panel that points at the target.
52
67
  * @csspart content - The region holding the slotted content.
68
+ * @csspart description - The slot the description is in. It draws no box until you give it one.
69
+ * @csspart heading - The slot the title is in. It draws no box until you give it one.
53
70
  * @csspart next - The control that goes to the next stop, and finishes the tour on the last stop.
54
71
  * @csspart next-button - The button inside `next`.
72
+ * @csspart next-live - The live region inside `next`.
55
73
  * @csspart next-text - The words on that button, trimmed to their capitals and baseline.
74
+ * @csspart progress - The stop's place in the tour, such as "2 of 5". It describes the panel, and is shown when the tour has `progress`.
56
75
  * @csspart skip - The control that ends the tour early.
57
76
  * @csspart skip-button - The button inside `skip`.
77
+ * @csspart skip-live - The live region inside `skip`.
58
78
  * @csspart skip-text - The words on that button, trimmed to their capitals and baseline.
59
79
  *
60
80
  * @cssstate placed-bottom - The panel sits below its target.
@@ -89,6 +109,11 @@ export declare class HelixHeadlessTourStop extends HelixElement {
89
109
  * The id of the element the stop points at.
90
110
  */
91
111
  for: string;
112
+ /**
113
+ * The panel's name, where neither the `heading` slot nor the content holds a
114
+ * heading.
115
+ */
116
+ label: string;
92
117
  /**
93
118
  * Whether the stop is the last one. Its next control then shows `doneLabel`.
94
119
  */
@@ -101,10 +126,30 @@ export declare class HelixHeadlessTourStop extends HelixElement {
101
126
  * The side of the target the panel is placed on when there is room.
102
127
  */
103
128
  placement: Placement;
129
+ /**
130
+ * @internal Set by the tour: the stop's place in it, such as "2 of 5".
131
+ */
132
+ position: string;
133
+ /**
134
+ * @internal Set by the tour: whether the stop's place is drawn as well as
135
+ * read.
136
+ */
137
+ progress: boolean;
104
138
  /**
105
139
  * Label for the control that leaves the tour.
106
140
  */
107
141
  skipLabel: string;
142
+ /**
143
+ * What kind of stop this is. Unset, it is a bubble pointing at its target.
144
+ *
145
+ * - `dialog`: a panel in the middle of the window, over a backdrop
146
+ * - `floating`: a panel in a corner of the window
147
+ * - `wait`: nothing shown, while the tour waits for the target to be on the page
148
+ *
149
+ * Reflected, because the styled tier places a dialog and a floating panel by
150
+ * it, and unset at rest, so no default is written onto the host.
151
+ */
152
+ type?: 'dialog' | 'floating' | 'wait';
108
153
  /**
109
154
  * The side of its target the panel last landed on, which names the arrow's
110
155
  * second part. Undefined until the first placement, which the server never
@@ -123,9 +168,9 @@ export declare class HelixHeadlessTourStop extends HelixElement {
123
168
  */
124
169
  focus(options?: FocusOptions): void;
125
170
  /**
126
- * Names the panel by the first heading in the content, by element
127
- * reference: the heading is in the light DOM and the panel in this shadow
128
- * root, which an IDREF cannot cross.
171
+ * Names and describes the panel, by element reference: the words are in the
172
+ * light DOM and the panel in this shadow root, which an IDREF cannot cross.
173
+ * `stopNaming` decides which words.
129
174
  */
130
175
  private nameByHeading;
131
176
  protected updated(changed: PropertyValues): void;
@@ -4,10 +4,12 @@ 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 { customElement, elementById, HelixElement, HelixTourAskEvent, PopoverController, } from '@fusion.dev/helix-core';
7
+ import { arrowReach, customElement, elementById, HelixElement, HelixTourAskEvent, PopoverController, reflectElements, sideOf, } from '@fusion.dev/helix-core';
8
+ import { nothing } from 'lit';
8
9
  import { property, state } from 'lit/decorators.js';
9
10
  import { html } from 'lit/static-html.js';
10
11
  import '../../button/index.js';
12
+ import { stopNaming } from '../naming.js';
11
13
  let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
12
14
  constructor() {
13
15
  super(...arguments);
@@ -16,26 +18,30 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
16
18
  this.doneLabel = 'Done';
17
19
  this.first = false;
18
20
  this.for = '';
21
+ this.label = '';
19
22
  this.last = false;
20
23
  this.nextLabel = 'Next';
21
24
  this.placement = 'bottom';
25
+ this.position = '';
26
+ this.progress = false;
22
27
  this.skipLabel = 'Skip';
23
28
  this.floating = new PopoverController(this, {
24
29
  anchor: () => this.target,
25
30
  arrow: () => this.partNamed('arrow'),
26
31
  offset: () => {
27
32
  const tour = this.parentElement;
28
- return ((tour?.spotlightPadding ?? 0) + (this.partNamed('arrow')?.offsetWidth ?? 0) / Math.SQRT2);
33
+ return (tour?.spotlightPadding ?? 0) + arrowReach(this.partNamed('arrow'));
29
34
  },
30
35
  onDismiss: () => { },
31
36
  onPlace: (placement) => {
32
- const side = placement.split('-')[0];
37
+ const side = sideOf(placement);
33
38
  this.toggleState('placed-bottom', side === 'bottom');
34
39
  this.toggleState('placed-left', side === 'left');
35
40
  this.toggleState('placed-right', side === 'right');
36
41
  this.toggleState('placed-top', side === 'top');
37
42
  this.side = side;
38
43
  },
44
+ place: () => this.type === undefined,
39
45
  placement: () => this.placement,
40
46
  surface: () => this.partNamed('bubble'),
41
47
  });
@@ -53,17 +59,23 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
53
59
  }
54
60
  nameByHeading() {
55
61
  const bubble = this.partNamed('bubble');
56
- if (!bubble || !('ariaLabelledByElements' in bubble)) {
57
- return;
58
- }
59
- const heading = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
60
- const found = [...this.children].flatMap((one) => one.matches(heading) ? [one] : [...one.querySelectorAll(heading)])[0];
61
- bubble.ariaLabelledByElements = found ? [found] : null;
62
+ const slotted = (name) => this.renderRoot
63
+ .querySelector(`slot[name="${name}"]`)
64
+ ?.assignedElements({ flatten: true }) ?? [];
65
+ const { describedBy, labelledBy } = stopNaming({
66
+ content: [...this.children].filter((one) => !one.slot),
67
+ description: slotted('description'),
68
+ heading: slotted('heading'),
69
+ labeled: this.label !== '',
70
+ progress: this.position === '' ? null : this.partNamed('progress'),
71
+ });
72
+ reflectElements(bubble, 'ariaLabelledByElements', labelledBy, true);
73
+ reflectElements(bubble, 'ariaDescribedByElements', describedBy, true);
62
74
  }
63
75
  updated(changed) {
64
76
  super.updated(changed);
65
77
  this.nameByHeading();
66
- if (this.current) {
78
+ if (this.current && this.type !== 'wait') {
67
79
  this.floating.show();
68
80
  }
69
81
  else {
@@ -74,14 +86,14 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
74
86
  return html `<slot name="actions">
75
87
  <${this.tag('button')}
76
88
  part="skip"
77
- exportparts="button: skip-button, text: skip-text" emphasis="ghost" @click=${() => this.ask('skip')}>
89
+ exportparts="button: skip-button, live: skip-live, text: skip-text" emphasis="ghost" @click=${() => this.ask('skip')}>
78
90
  ${this.skipLabel}
79
91
  </${this.tag('button')}>
80
92
  ${this.first
81
93
  ? ''
82
94
  : html `<${this.tag('button')}
83
95
  part="back"
84
- exportparts="button: back-button, text: back-text"
96
+ exportparts="button: back-button, live: back-live, text: back-text"
85
97
  emphasis="outlined"
86
98
  @click=${() => this.ask('back')}
87
99
  >
@@ -89,7 +101,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
89
101
  </${this.tag('button')}>`}
90
102
  <${this.tag('button')}
91
103
  part="next"
92
- exportparts="button: next-button, text: next-text"
104
+ exportparts="button: next-button, live: next-live, text: next-text"
93
105
  emphasis="solid"
94
106
  tone="primary"
95
107
  @click=${() => this.ask('next')}
@@ -102,16 +114,22 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
102
114
  return html `<div
103
115
  part="bubble"
104
116
  popover="manual"
105
- role="group"
117
+ role=${this.type === 'dialog' ? 'dialog' : 'group'}
106
118
  tabindex="-1"
107
- ?hidden=${!this.current}
119
+ aria-label=${this.label || nothing}
120
+ ?hidden=${!this.current || this.type === 'wait'}
108
121
  >
109
122
  ${''}
110
123
  <div
111
124
  part="arrow${this.side ? { bottom: ' arrow-bottom', left: ' arrow-left', right: ' arrow-right', top: ' arrow-top' }[this.side] : ''}"
112
125
  aria-hidden="true"
113
126
  ></div>
127
+ <slot name="heading" part="heading" @slotchange=${this.nameByHeading}></slot>
128
+ <slot name="description" part="description" @slotchange=${this.nameByHeading}></slot>
114
129
  <div part="content"><slot></slot></div>
130
+ ${this.position === ''
131
+ ? ''
132
+ : html `<span part="progress" ?hidden=${!this.progress}>${this.position}</span>`}
115
133
  <div part="actions">
116
134
  ${this.renderActions()}
117
135
  </div>
@@ -136,6 +154,9 @@ __decorate([
136
154
  __decorate([
137
155
  property()
138
156
  ], HelixHeadlessTourStop.prototype, "for", void 0);
157
+ __decorate([
158
+ property()
159
+ ], HelixHeadlessTourStop.prototype, "label", void 0);
139
160
  __decorate([
140
161
  property({ reflect: true, type: Boolean })
141
162
  ], HelixHeadlessTourStop.prototype, "last", void 0);
@@ -145,9 +166,18 @@ __decorate([
145
166
  __decorate([
146
167
  property()
147
168
  ], HelixHeadlessTourStop.prototype, "placement", void 0);
169
+ __decorate([
170
+ property({ attribute: false })
171
+ ], HelixHeadlessTourStop.prototype, "position", void 0);
172
+ __decorate([
173
+ property({ attribute: false })
174
+ ], HelixHeadlessTourStop.prototype, "progress", void 0);
148
175
  __decorate([
149
176
  property({ attribute: 'skip-label' })
150
177
  ], HelixHeadlessTourStop.prototype, "skipLabel", void 0);
178
+ __decorate([
179
+ property({ reflect: true })
180
+ ], HelixHeadlessTourStop.prototype, "type", void 0);
151
181
  __decorate([
152
182
  state()
153
183
  ], HelixHeadlessTourStop.prototype, "side", void 0);