@fusion.dev/helix-headless 0.142.0 → 0.143.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 (217) hide show
  1. package/custom-elements.json +16710 -707
  2. package/dist/accordion/item/accordion-item.d.ts +38 -2
  3. package/dist/accordion/item/accordion-item.js +38 -6
  4. package/dist/accordion/root/accordion-root.d.ts +23 -1
  5. package/dist/accordion/root/accordion-root.js +39 -3
  6. package/dist/announcement/root/announcement-root.d.ts +11 -7
  7. package/dist/announcement/root/announcement-root.js +1 -1
  8. package/dist/app-header/root/app-header-root.d.ts +21 -0
  9. package/dist/app-header/root/app-header-root.js +31 -5
  10. package/dist/area-chart/root/area-chart-root.js +16 -5
  11. package/dist/avatar/group/avatar-group.d.ts +24 -3
  12. package/dist/avatar/group/avatar-group.js +34 -2
  13. package/dist/avatar/root/avatar-root.d.ts +3 -1
  14. package/dist/avatar/root/avatar-root.js +8 -1
  15. package/dist/badge/root/badge-root.d.ts +8 -0
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +19 -3
  17. package/dist/bar-chart/root/bar-chart-root.js +43 -13
  18. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  19. package/dist/blockquote/root/blockquote-root.js +6 -4
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  22. package/dist/button/root/button-root.d.ts +37 -0
  23. package/dist/button/root/button-root.js +44 -0
  24. package/dist/callout/root/callout-root.d.ts +14 -5
  25. package/dist/callout/root/callout-root.js +11 -3
  26. package/dist/canvas/root/canvas-root.d.ts +1 -0
  27. package/dist/canvas/root/canvas-root.js +10 -1
  28. package/dist/carousel/root/carousel-root.d.ts +47 -5
  29. package/dist/carousel/root/carousel-root.js +53 -21
  30. package/dist/chat/composer/chat-composer.d.ts +13 -11
  31. package/dist/chat/composer/chat-composer.js +6 -12
  32. package/dist/chat/message/chat-message.js +8 -1
  33. package/dist/chat/root/chat-root.d.ts +1 -0
  34. package/dist/chat/root/chat-root.js +2 -1
  35. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  36. package/dist/checkbox/root/checkbox-root.js +7 -3
  37. package/dist/checkbox-group/root/checkbox-group-root.d.ts +65 -32
  38. package/dist/checkbox-group/root/checkbox-group-root.js +85 -37
  39. package/dist/code/root/code-root.d.ts +1 -1
  40. package/dist/code-block/root/code-block-root.d.ts +19 -0
  41. package/dist/code-block/root/code-block-root.js +21 -1
  42. package/dist/color-input/root/color-input-root.d.ts +26 -1
  43. package/dist/color-input/root/color-input-root.js +32 -3
  44. package/dist/color-picker/root/color-picker-root.d.ts +37 -7
  45. package/dist/color-picker/root/color-picker-root.js +83 -6
  46. package/dist/combobox/root/combobox-root.d.ts +90 -2
  47. package/dist/combobox/root/combobox-root.js +160 -19
  48. package/dist/command/root/command-root.d.ts +17 -6
  49. package/dist/command/root/command-root.js +12 -1
  50. package/dist/copy-button/root/copy-button-root.d.ts +21 -6
  51. package/dist/copy-button/root/copy-button-root.js +11 -1
  52. package/dist/data-table/root/data-table-root.d.ts +164 -10
  53. package/dist/data-table/root/data-table-root.js +488 -57
  54. package/dist/date-input/root/date-input-root.d.ts +53 -8
  55. package/dist/date-input/root/date-input-root.js +70 -18
  56. package/dist/date-picker/root/date-picker-root.d.ts +84 -17
  57. package/dist/date-picker/root/date-picker-root.js +125 -20
  58. package/dist/dialog/root/dialog-root.d.ts +38 -10
  59. package/dist/dialog/root/dialog-root.js +55 -2
  60. package/dist/donut-chart/root/donut-chart-root.d.ts +39 -15
  61. package/dist/donut-chart/root/donut-chart-root.js +30 -10
  62. package/dist/drawer/root/drawer-root.d.ts +32 -7
  63. package/dist/drawer/root/drawer-root.js +50 -5
  64. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  65. package/dist/drilldown/item/drilldown-item.js +6 -2
  66. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  67. package/dist/drilldown/root/drilldown-root.js +26 -10
  68. package/dist/element-events.generated.js +35 -9
  69. package/dist/file-input/root/file-input-root.d.ts +18 -1
  70. package/dist/file-input/root/file-input-root.js +34 -8
  71. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  72. package/dist/filter-input/menu/filter-menu.js +8 -0
  73. package/dist/filter-input/root/filter-input-root.d.ts +5 -2
  74. package/dist/filter-input/root/filter-input-root.js +22 -7
  75. package/dist/format-date/root/format-date-root.d.ts +11 -3
  76. package/dist/format-date/root/format-date-root.js +26 -5
  77. package/dist/graph/root/graph-root.d.ts +24 -3
  78. package/dist/graph/root/graph-root.js +66 -12
  79. package/dist/heatmap/root/buckets.d.ts +2 -1
  80. package/dist/heatmap/root/buckets.js +5 -1
  81. package/dist/heatmap/root/heatmap-root.d.ts +27 -6
  82. package/dist/heatmap/root/heatmap-root.js +23 -6
  83. package/dist/icon/root/icon-root.d.ts +24 -3
  84. package/dist/icon/root/icon-root.js +34 -1
  85. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  86. package/dist/image/root/image-root.d.ts +9 -2
  87. package/dist/image/root/image-root.js +8 -2
  88. package/dist/input/root/input-root.d.ts +51 -9
  89. package/dist/input/root/input-root.js +46 -5
  90. package/dist/kbd/root/kbd-root.d.ts +12 -4
  91. package/dist/kbd/root/kbd-root.js +10 -11
  92. package/dist/legend/root/legend-root.d.ts +7 -0
  93. package/dist/legend/root/legend-root.js +7 -2
  94. package/dist/lightbox/item/lightbox-item.d.ts +2 -1
  95. package/dist/lightbox/root/lightbox-root.d.ts +16 -5
  96. package/dist/lightbox/root/lightbox-root.js +24 -3
  97. package/dist/line-chart/root/line-chart-root.d.ts +19 -2
  98. package/dist/line-chart/root/line-chart-root.js +40 -13
  99. package/dist/list/item/list-item.d.ts +11 -5
  100. package/dist/list/item/list-item.js +5 -6
  101. package/dist/listbox/root/listbox-root.d.ts +111 -2
  102. package/dist/listbox/root/listbox-root.js +144 -16
  103. package/dist/map/root/map-root.d.ts +19 -4
  104. package/dist/map/root/map-root.js +36 -7
  105. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  106. package/dist/megamenu/root/megamenu-root.js +10 -1
  107. package/dist/menu/item/menu-item.d.ts +41 -12
  108. package/dist/menu/item/menu-item.js +46 -3
  109. package/dist/menu/root/menu-root.d.ts +32 -2
  110. package/dist/menu/root/menu-root.js +48 -8
  111. package/dist/meter/root/meter-root.d.ts +6 -0
  112. package/dist/meter/root/meter-root.js +18 -2
  113. package/dist/navigation/root/navigation-root.d.ts +10 -2
  114. package/dist/navigation/root/navigation-root.js +13 -2
  115. package/dist/notifications/group/notification-group.d.ts +1 -0
  116. package/dist/notifications/notification/notification.d.ts +12 -0
  117. package/dist/notifications/notification/notification.js +31 -2
  118. package/dist/notifications/root/notifications-root.d.ts +7 -0
  119. package/dist/notifications/root/notifications-root.js +17 -2
  120. package/dist/number-input/root/number-input-root.d.ts +62 -7
  121. package/dist/number-input/root/number-input-root.js +68 -16
  122. package/dist/object-inspector/root/object-inspector-root.d.ts +48 -5
  123. package/dist/object-inspector/root/object-inspector-root.js +91 -11
  124. package/dist/option/root/option-root.d.ts +4 -1
  125. package/dist/option/root/option-root.js +1 -0
  126. package/dist/outline/root/outline-root.d.ts +13 -2
  127. package/dist/outline/root/outline-root.js +4 -2
  128. package/dist/pagination/root/pagination-root.d.ts +19 -1
  129. package/dist/pagination/root/pagination-root.js +49 -9
  130. package/dist/pagination/size/pagination-size.d.ts +13 -3
  131. package/dist/pagination/size/pagination-size.js +7 -0
  132. package/dist/pin-input/root/pin-input-root.d.ts +16 -0
  133. package/dist/pin-input/root/pin-input-root.js +19 -5
  134. package/dist/popover/root/popover-root.d.ts +19 -2
  135. package/dist/popover/root/popover-root.js +22 -1
  136. package/dist/progress/root/progress-root.d.ts +16 -1
  137. package/dist/progress/root/progress-root.js +31 -7
  138. package/dist/qr-code/root/qr-code-root.d.ts +19 -0
  139. package/dist/qr-code/root/qr-code-root.js +29 -1
  140. package/dist/radio/group/radio-group.d.ts +41 -0
  141. package/dist/radio/group/radio-group.js +50 -14
  142. package/dist/radio/root/radio-root.d.ts +8 -5
  143. package/dist/radio/root/radio-root.js +1 -2
  144. package/dist/rating/root/rating-root.d.ts +24 -1
  145. package/dist/rating/root/rating-root.js +24 -7
  146. package/dist/scatter-chart/root/scatter-chart-root.js +15 -7
  147. package/dist/select/root/select-root.d.ts +69 -3
  148. package/dist/select/root/select-root.js +92 -8
  149. package/dist/skeleton/root/skeleton-root.d.ts +14 -3
  150. package/dist/skeleton/root/skeleton-root.js +17 -3
  151. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  152. package/dist/skip-link/root/skip-link-root.js +15 -1
  153. package/dist/slider/root/slider-root.d.ts +20 -2
  154. package/dist/slider/root/slider-root.js +41 -3
  155. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  156. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  157. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  158. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  159. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  160. package/dist/splitter/root/splitter-root.d.ts +28 -6
  161. package/dist/splitter/root/splitter-root.js +35 -1
  162. package/dist/stat/root/stat-root.d.ts +7 -1
  163. package/dist/stat/root/stat-root.js +14 -1
  164. package/dist/steps/root/steps-root.js +1 -1
  165. package/dist/switch/root/switch-root.d.ts +2 -0
  166. package/dist/switch/root/switch-root.js +1 -0
  167. package/dist/table/cell/table-cell.d.ts +24 -4
  168. package/dist/table/cell/table-cell.js +21 -1
  169. package/dist/table/column/table-column.d.ts +32 -6
  170. package/dist/table/column/table-column.js +36 -6
  171. package/dist/table/root/table-root.d.ts +94 -3
  172. package/dist/table/root/table-root.js +128 -5
  173. package/dist/table/row/table-row.d.ts +7 -0
  174. package/dist/table/row/table-row.js +9 -1
  175. package/dist/tabs/root/tabs-root.d.ts +6 -1
  176. package/dist/tabs/root/tabs-root.js +6 -2
  177. package/dist/tag/root/tag-root.d.ts +23 -1
  178. package/dist/tag/root/tag-root.js +16 -2
  179. package/dist/textarea/root/textarea-root.d.ts +4 -0
  180. package/dist/textarea/root/textarea-root.js +1 -1
  181. package/dist/time-input/root/time-input-root.d.ts +28 -2
  182. package/dist/time-input/root/time-input-root.js +46 -10
  183. package/dist/time-picker/root/time-picker-root.d.ts +101 -18
  184. package/dist/time-picker/root/time-picker-root.js +115 -16
  185. package/dist/toast/root/toast-root.d.ts +64 -1
  186. package/dist/toast/root/toast-root.js +147 -2
  187. package/dist/toggle/group/toggle-group.d.ts +38 -1
  188. package/dist/toggle/group/toggle-group.js +34 -8
  189. package/dist/toggle/root/toggle-root.d.ts +14 -10
  190. package/dist/toggle/root/toggle-root.js +3 -3
  191. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  192. package/dist/toolbar/root/toolbar-root.js +99 -16
  193. package/dist/tooltip/root/tooltip-root.d.ts +37 -4
  194. package/dist/tooltip/root/tooltip-root.js +48 -0
  195. package/dist/tour/root/tour-root.d.ts +34 -5
  196. package/dist/tour/root/tour-root.js +51 -14
  197. package/dist/tour/stop/tour-stop.d.ts +18 -1
  198. package/dist/tour/stop/tour-stop.js +25 -5
  199. package/dist/tree/item/tree-item.d.ts +21 -5
  200. package/dist/tree/item/tree-item.js +14 -4
  201. package/dist/tree/root/tree-root.d.ts +29 -5
  202. package/dist/tree/root/tree-root.js +64 -8
  203. package/dist/trend/root/trend-root.d.ts +23 -1
  204. package/dist/trend/root/trend-root.js +28 -7
  205. package/dist/video/root/keys.d.ts +5 -0
  206. package/dist/video/root/keys.js +13 -1
  207. package/dist/video/root/video-root.d.ts +19 -0
  208. package/dist/video/root/video-root.js +48 -4
  209. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  210. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  211. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  212. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  213. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  214. package/lint.json +1 -1
  215. package/package.json +5 -5
  216. package/types/jsx.d.ts +4348 -965
  217. package/types/vue.d.ts +972 -232
@@ -4,10 +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, HelixBeforeActivateEvent, HelixBeforeCloseEvent, HelixCompleteEvent, HelixElement, hostAria, requestChange, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, deepContains, 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
- let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
10
+ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.backLabel = '';
@@ -16,7 +16,6 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
16
16
  this.indexState = undefined;
17
17
  this.label = '';
18
18
  this.nextLabel = '';
19
- this.open = false;
20
19
  this.skipLabel = '';
21
20
  this.spotlightPadding = 8;
22
21
  this.sizer = undefined;
@@ -42,6 +41,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
42
41
  this.handleViewport = () => {
43
42
  this.light();
44
43
  };
44
+ this.followed = false;
45
45
  }
46
46
  get index() {
47
47
  return this.indexState ?? this.defaultIndex;
@@ -71,19 +71,14 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
71
71
  this.move(this.index - 1);
72
72
  }
73
73
  skip() {
74
- const event = new HelixBeforeCloseEvent();
75
- this.dispatchEvent(event);
76
- if (event.defaultPrevented) {
77
- return;
78
- }
79
- this.open = false;
74
+ this.requestOpen(false);
80
75
  }
81
76
  finish() {
82
77
  this.dispatchEvent(new HelixCompleteEvent({ value: String(this.index) }));
83
78
  this.open = false;
84
79
  }
85
80
  move(to) {
86
- requestChange({
81
+ const moved = requestChange({
87
82
  apply: (value) => {
88
83
  this.index = value;
89
84
  },
@@ -91,6 +86,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
91
86
  host: this,
92
87
  next: to,
93
88
  });
89
+ if (moved) {
90
+ this.dispatchEvent(new HelixActivateEvent({ value: String(this.index) }));
91
+ }
94
92
  }
95
93
  connectedCallback() {
96
94
  super.connectedCallback();
@@ -113,12 +111,54 @@ let HelixHeadlessTour = class HelixHeadlessTour extends HelixElement {
113
111
  }
114
112
  willUpdate(changed) {
115
113
  super.willUpdate(changed);
116
- hostAria(this.internals, { label: this.label, role: 'region' });
114
+ hostAria(this.internals, {
115
+ label: this.open ? this.label : '',
116
+ role: this.open ? 'region' : '',
117
+ });
117
118
  }
118
119
  updated(changed) {
119
120
  super.updated(changed);
120
121
  this.settle();
121
122
  this.light();
123
+ this.follow(changed);
124
+ }
125
+ follow(changed) {
126
+ if (!this.followed) {
127
+ this.followed = true;
128
+ return;
129
+ }
130
+ const opened = changed.has('open') && this.open && changed.get('open') === false;
131
+ const closed = changed.has('open') && !this.open && changed.get('open') === true;
132
+ const active = deepActiveElement();
133
+ const inside = active === null || active === document.body || deepContains(this, active);
134
+ if (opened) {
135
+ if (active instanceof HTMLElement && !deepContains(this, active)) {
136
+ this.returnFocus = active;
137
+ }
138
+ this.focusCurrent();
139
+ }
140
+ else if (closed) {
141
+ const back = this.returnFocus;
142
+ this.returnFocus = undefined;
143
+ if (inside && back?.isConnected) {
144
+ back.focus();
145
+ }
146
+ }
147
+ else if (this.open && changed.has('index') && inside) {
148
+ this.focusCurrent();
149
+ }
150
+ }
151
+ focusCurrent() {
152
+ const stops = this.stops;
153
+ const stop = stops[Math.max(0, Math.min(this.index, stops.length - 1))];
154
+ if (!stop) {
155
+ return;
156
+ }
157
+ void stop.updateComplete.then(() => {
158
+ if (this.open && stop.current) {
159
+ stop.focus();
160
+ }
161
+ });
122
162
  }
123
163
  settle() {
124
164
  const stops = this.stops;
@@ -215,9 +255,6 @@ __decorate([
215
255
  __decorate([
216
256
  property({ attribute: 'next-label' })
217
257
  ], HelixHeadlessTour.prototype, "nextLabel", void 0);
218
- __decorate([
219
- property({ reflect: true, type: Boolean })
220
- ], HelixHeadlessTour.prototype, "open", void 0);
221
258
  __decorate([
222
259
  property({ attribute: 'skip-label' })
223
260
  ], HelixHeadlessTour.prototype, "skipLabel", void 0);
@@ -25,6 +25,10 @@ import '../../button/index.js';
25
25
  * on the page from markup. It resolves against the stop's root node, so a stop
26
26
  * inside a shadow tree points at a target in that tree.
27
27
  *
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.
31
+ *
28
32
  * @since 0.1.0
29
33
  * @status stable
30
34
  * @category overlays
@@ -110,7 +114,20 @@ export declare class HelixHeadlessTourStop extends HelixElement {
110
114
  /** The element the stop points at, or `null` when no element has the `for` id. */
111
115
  get target(): HTMLElement | null;
112
116
  private readonly floating;
113
- protected willUpdate(changed: PropertyValues): void;
117
+ /**
118
+ * Moves focus to the panel while the stop is shown.
119
+ *
120
+ * The host draws no box and takes no focus, so focusing it did nothing. The
121
+ * panel takes focus without joining the `Tab` order, and a screen reader
122
+ * reads the group it is.
123
+ */
124
+ focus(options?: FocusOptions): void;
125
+ /**
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.
129
+ */
130
+ private nameByHeading;
114
131
  protected updated(changed: PropertyValues): void;
115
132
  /**
116
133
  * Skip, back and next, behind a seam the styled tier can move.
@@ -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 { customElement, elementById, HelixElement, HelixTourAskEvent, hostAria, PopoverController, } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixElement, HelixTourAskEvent, PopoverController, } from '@fusion.dev/helix-core';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { html } from 'lit/static-html.js';
10
10
  import '../../button/index.js';
@@ -43,12 +43,26 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
43
43
  get target() {
44
44
  return this.for === '' ? null : elementById(this, this.for);
45
45
  }
46
- willUpdate(changed) {
47
- super.willUpdate(changed);
48
- hostAria(this.internals, { role: 'group' });
46
+ focus(options) {
47
+ const bubble = this.partNamed('bubble');
48
+ if (this.current && bubble) {
49
+ bubble.focus(options);
50
+ return;
51
+ }
52
+ super.focus(options);
53
+ }
54
+ nameByHeading() {
55
+ 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;
49
62
  }
50
63
  updated(changed) {
51
64
  super.updated(changed);
65
+ this.nameByHeading();
52
66
  if (this.current) {
53
67
  this.floating.show();
54
68
  }
@@ -85,7 +99,13 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
85
99
  </slot>`;
86
100
  }
87
101
  render() {
88
- return html `<div part="bubble" popover="manual" ?hidden=${!this.current}>
102
+ return html `<div
103
+ part="bubble"
104
+ popover="manual"
105
+ role="group"
106
+ tabindex="-1"
107
+ ?hidden=${!this.current}
108
+ >
89
109
  ${''}
90
110
  <div
91
111
  part="arrow${this.side ? { bottom: ' arrow-bottom', left: ' arrow-left', right: ' arrow-right', top: ' arrow-top' }[this.side] : ''}"
@@ -19,6 +19,10 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
19
19
  * **`href` makes the whole row a link**, rendered as an `<a>`. Enter, middle
20
20
  * click, and the status bar then work anywhere on the row.
21
21
  *
22
+ * Pressing a branch, or Enter or Space on it, opens or closes it. A branch with
23
+ * a `value` is chosen as well and sends `helix-select`, unless the tree has
24
+ * `nesting="heading"`, where a branch names the list under it.
25
+ *
22
26
  * @since 0.1.0
23
27
  * @status stable
24
28
  * @aliases TreeNode, Branch, Leaf
@@ -32,7 +36,7 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
32
36
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before a branch closes, and cancelable.
33
37
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before a branch opens, and cancelable.
34
38
  * @event {HelixSelectEvent} helix-select - Fired when the item is chosen, and cancelable. Canceling it changes nothing.
35
- * @event {Event} toggle - Sent when the branch opens or closes, with `newState` and `oldState`.
39
+ * @event {ToggleEvent} toggle - Sent when the branch opens or closes, with `newState` and `oldState`.
36
40
  *
37
41
  * @csspart group - The container for nested items.
38
42
  * @csspart item - The node, and the focus target unless `href` moves it to the row.
@@ -46,6 +50,12 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
46
50
  * @cssstate selected - While it is selected.
47
51
  */
48
52
  export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
53
+ /**
54
+ * @internal Set by the tree: whether a branch heads the list under it, as
55
+ * under `nesting="heading"`. Such a branch only opens and closes when it is
56
+ * pressed, because it names a list and is not a destination of its own.
57
+ */
58
+ heads: boolean;
49
59
  /**
50
60
  * The URL this node links to. Setting it makes the row a link.
51
61
  *
@@ -101,14 +111,14 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
101
111
  * Where the link opens. Use it for a row that leaves the site.
102
112
  */
103
113
  target: string;
104
- /**
105
- * Identifies the item to your code. It is sent in `helix-select`.
106
- */
107
- value: string;
108
114
  /**
109
115
  * Whether this item holds the tree's single tab stop.
110
116
  */
111
117
  focusable: boolean;
118
+ /**
119
+ * Identifies the item to your code. It is sent in `helix-select`.
120
+ */
121
+ value: string;
112
122
  /**
113
123
  * Whether anything is slotted into `children`.
114
124
  *
@@ -119,6 +129,12 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
119
129
  */
120
130
  private branch;
121
131
  protected firstUpdated(changed: PropertyValues): void;
132
+ /**
133
+ * @internal Whether pressing this branch chooses it as well as opening or
134
+ * closing it: it has a value and does not head its list. Read by the tree
135
+ * for Enter and Space.
136
+ */
137
+ get choosable(): boolean;
122
138
  /**
123
139
  * @internal Whether this row holds children, read by the tree, which moves
124
140
  * and opens differently for a branch. Public rather than protected because
@@ -12,12 +12,13 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerOpen(HelixElement) {
13
13
  constructor() {
14
14
  super(...arguments);
15
+ this.heads = false;
15
16
  this.href = '';
16
17
  this.rel = '';
17
18
  this.selected = false;
18
19
  this.target = '';
19
- this.value = '';
20
20
  this.focusable = false;
21
+ this.value = '';
21
22
  this.branch = false;
22
23
  this.handleToggleClick = (event) => {
23
24
  event.preventDefault();
@@ -28,6 +29,9 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
28
29
  const onLink = event.composedPath().some((node) => node instanceof HTMLAnchorElement);
29
30
  if (this.branch && this.href === '') {
30
31
  if (!onLink) {
32
+ if (this.choosable) {
33
+ this.select();
34
+ }
31
35
  this.toggleOpen();
32
36
  }
33
37
  return;
@@ -46,6 +50,9 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
46
50
  this.branch = slot.assignedElements().length > 0;
47
51
  });
48
52
  }
53
+ get choosable() {
54
+ return this.value !== '' && !this.heads;
55
+ }
49
56
  get isBranch() {
50
57
  return this.branch;
51
58
  }
@@ -145,6 +152,9 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
145
152
  </div>`;
146
153
  }
147
154
  };
155
+ __decorate([
156
+ property({ attribute: false })
157
+ ], HelixHeadlessTreeItem.prototype, "heads", void 0);
148
158
  __decorate([
149
159
  property()
150
160
  ], HelixHeadlessTreeItem.prototype, "href", void 0);
@@ -160,12 +170,12 @@ __decorate([
160
170
  __decorate([
161
171
  property()
162
172
  ], HelixHeadlessTreeItem.prototype, "target", void 0);
163
- __decorate([
164
- property()
165
- ], HelixHeadlessTreeItem.prototype, "value", void 0);
166
173
  __decorate([
167
174
  state()
168
175
  ], HelixHeadlessTreeItem.prototype, "focusable", void 0);
176
+ __decorate([
177
+ property()
178
+ ], HelixHeadlessTreeItem.prototype, "value", void 0);
169
179
  __decorate([
170
180
  state()
171
181
  ], HelixHeadlessTreeItem.prototype, "branch", void 0);
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & typeof HelixElement;
4
4
  /**
@@ -13,6 +13,9 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
13
13
  * Items are found with `instanceof`, never by tag name, so the headless tier
14
14
  * does not depend on the styled one.
15
15
  *
16
+ * Typing a letter moves to the next visible item whose name starts with it,
17
+ * and `*` opens every branch beside the focused item.
18
+ *
16
19
  * @since 0.1.0
17
20
  * @status stable
18
21
  * @category navigation
@@ -33,8 +36,15 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
33
36
  export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
34
37
  /**
35
38
  * Accessible name for the tree.
39
+ *
40
+ * An `aria-label` or `aria-labelledby` on the host names the tree too, and
41
+ * wins over this.
36
42
  */
37
43
  label: string;
44
+ /** Carries a host `aria-label` and `aria-labelledby` to the tree. */
45
+ protected readonly aria: AriaForwardController;
46
+ /** The letters typed so far, for moving to an item by its name. */
47
+ private readonly typeahead;
38
48
  /**
39
49
  * What a deeper level means: `peer` for no change, `subordinate` for a lower
40
50
  * rank, or `heading` for a parent that heads the items under it.
@@ -98,16 +108,30 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
98
108
  * so an item moved in the tree gets the right indent without knowing it moved.
99
109
  */
100
110
  private syncItems;
111
+ /** The nearest item around `item` that a reader can see, if any. */
112
+ private visibleAncestor;
113
+ /**
114
+ * Moves to the next visible item whose name starts with the letters typed,
115
+ * or opens every branch beside the focused item on `*`.
116
+ *
117
+ * Returns whether the press was one of those.
118
+ */
119
+ private handleTyping;
101
120
  private move;
102
121
  private focusOn;
103
122
  private readonly handleKeydown;
104
123
  /**
105
124
  * A branch opened or closed, whoever did it.
106
125
  *
107
- * Listening for the item's own open events rather than calling `syncItems`
108
- * from each keyboard path: that misses the pointer, and collapsing a branch
109
- * by clicking its chevron then leaves the tree's one tab stop on a row inside
110
- * the subtree just hidden. It also covers a consumer setting `open` directly.
126
+ * Listening for the item's own `toggle` rather than calling `syncItems` from
127
+ * each keyboard path: that misses the pointer, and collapsing a branch by
128
+ * clicking its row then left the tree's one tab stop on a row inside the
129
+ * subtree just hidden, so Tab passed over the tree. It also covers a
130
+ * consumer setting `open` directly.
131
+ *
132
+ * **In capture**, because `toggle` does not bubble, which is the platform's
133
+ * shape for it. The `helix-before-*` pair this listened for does not bubble
134
+ * either, and is sent before the branch has moved.
111
135
  */
112
136
  private readonly handleBranch;
113
137
  connectedCallback(): 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 { closestInstance, customElement, HelixElement, inlineStep, MixinCollection, MixinControllerActive, scrollItemIntoView, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, closestInstance, customElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, scrollItemIntoView, slottedWords, Typeahead, typesCharacter, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessTreeItem } from '../item/tree-item.js';
@@ -22,7 +22,16 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
22
22
  constructor() {
23
23
  super(...arguments);
24
24
  this.label = 'Tree';
25
+ this.aria = new AriaForwardController(this, {
26
+ label: true,
27
+ target: () => this.partNamed('tree'),
28
+ });
29
+ this.typeahead = new Typeahead();
25
30
  this.handleSelection = (event) => {
31
+ const origin = event.target;
32
+ if (!(origin instanceof HelixHeadlessTreeItem) || !this.items.includes(origin)) {
33
+ return;
34
+ }
26
35
  if (!event.defaultPrevented) {
27
36
  this.requestActive(event.detail.value);
28
37
  }
@@ -30,9 +39,17 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
30
39
  };
31
40
  this.handleKeydown = (event) => {
32
41
  const current = closestInstance(event.target, HelixHeadlessTreeItem);
33
- if (!current || !this.items.includes(current) || !KEYS.has(event.key)) {
42
+ if (!current || !this.items.includes(current)) {
34
43
  return;
35
44
  }
45
+ if (this.handleTyping(current, event)) {
46
+ event.preventDefault();
47
+ return;
48
+ }
49
+ if (!KEYS.has(event.key)) {
50
+ return;
51
+ }
52
+ this.typeahead.reset();
36
53
  const along = inlineStep(this, event.key);
37
54
  if (event.key === 'ArrowDown') {
38
55
  this.move(current, 1);
@@ -67,6 +84,9 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
67
84
  }
68
85
  else {
69
86
  if (current.isBranch) {
87
+ if (current.choosable) {
88
+ current.select();
89
+ }
70
90
  current.toggleOpen();
71
91
  this.syncItems();
72
92
  }
@@ -98,8 +118,10 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
98
118
  syncItems() {
99
119
  const visible = this.visibleItems;
100
120
  const chosen = this.resolveActive(visible);
101
- const held = visible.find((item) => item.focusable);
121
+ const held = visible.find((item) => item.focusable) ??
122
+ this.visibleAncestor(this.items.find((item) => item.focusable));
102
123
  const stop = held ?? chosen;
124
+ const heads = this.nesting === 'heading';
103
125
  for (const item of this.items) {
104
126
  const selected = item.value !== '' && item.value === this.active;
105
127
  const focusable = item === stop;
@@ -115,9 +137,45 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
115
137
  item.selected = selected;
116
138
  item.focusable = focusable;
117
139
  item.level = level;
140
+ item.heads = heads;
118
141
  });
119
142
  }
120
143
  }
144
+ visibleAncestor(item) {
145
+ const visible = this.visibleItems;
146
+ let node = item?.parentElement ?? null;
147
+ while (node && node !== this) {
148
+ if (node instanceof HelixHeadlessTreeItem && visible.includes(node)) {
149
+ return node;
150
+ }
151
+ node = node.parentElement;
152
+ }
153
+ return undefined;
154
+ }
155
+ handleTyping(current, event) {
156
+ if (!typesCharacter(event) || keyClaimed(event)) {
157
+ return false;
158
+ }
159
+ if (event.key === ' ' && !this.typeahead.typing()) {
160
+ return false;
161
+ }
162
+ if (event.key === '*') {
163
+ const siblings = [...(current.parentElement?.children ?? [])].filter((node) => node instanceof HelixHeadlessTreeItem && node.slot === current.slot);
164
+ for (const sibling of siblings) {
165
+ if (sibling.isBranch && !sibling.open) {
166
+ sibling.requestOpen(true);
167
+ }
168
+ }
169
+ this.syncItems();
170
+ return true;
171
+ }
172
+ const visible = this.visibleItems;
173
+ const at = this.typeahead.match(event.key, visible.map((item) => slottedWords(item, [''])), visible.indexOf(current));
174
+ if (at >= 0) {
175
+ this.focusOn(visible[at]);
176
+ }
177
+ return true;
178
+ }
121
179
  move(from, delta) {
122
180
  const visible = this.visibleItems;
123
181
  const index = visible.indexOf(from);
@@ -137,8 +195,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
137
195
  connectedCallback() {
138
196
  super.connectedCallback();
139
197
  this.addEventListener('helix-select', this.handleSelection);
140
- this.addEventListener('helix-before-open', this.handleBranch);
141
- this.addEventListener('helix-before-close', this.handleBranch);
198
+ this.addEventListener('toggle', this.handleBranch, true);
142
199
  this.addEventListener('keydown', this.handleKeydown);
143
200
  this.watcher = new MutationObserver(() => this.syncItems());
144
201
  this.watcher.observe(this, { childList: true, subtree: true });
@@ -146,8 +203,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
146
203
  disconnectedCallback() {
147
204
  super.disconnectedCallback();
148
205
  this.removeEventListener('helix-select', this.handleSelection);
149
- this.removeEventListener('helix-before-open', this.handleBranch);
150
- this.removeEventListener('helix-before-close', this.handleBranch);
206
+ this.removeEventListener('toggle', this.handleBranch, true);
151
207
  this.removeEventListener('keydown', this.handleKeydown);
152
208
  this.watcher?.disconnect();
153
209
  this.watcher = undefined;
@@ -158,7 +214,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
158
214
  }
159
215
  updated(changed) {
160
216
  super.updated(changed);
161
- if (changed.has('active')) {
217
+ if (changed.has('active') || changed.has('nesting')) {
162
218
  this.syncItems();
163
219
  }
164
220
  }
@@ -16,7 +16,8 @@ import { type TemplateResult } from 'lit';
16
16
  *
17
17
  * The direction is also written as a word, so a screen reader reads it. The
18
18
  * styled tier hides `part="direction"` visually and keeps it in the
19
- * accessibility tree.
19
+ * accessibility tree. The words are properties, `up-label` through
20
+ * `worse-label`, so a page in another language can set its own.
20
21
  *
21
22
  * The metric and the label are bare slots. An element that assigned its own
22
23
  * slot on connect would add an attribute to its host after upgrade, which the
@@ -35,6 +36,10 @@ import { type TemplateResult } from 'lit';
35
36
  * @csspart icon - The arrow.
36
37
  */
37
38
  export declare class HelixHeadlessTrend extends HelixElement {
39
+ /**
40
+ * The word read out after the direction when the move is an improvement.
41
+ */
42
+ betterLabel: string;
38
43
  /**
39
44
  * Which way the measurement moved.
40
45
  *
@@ -46,6 +51,10 @@ export declare class HelixHeadlessTrend extends HelixElement {
46
51
  * and reports as a hydration mismatch.
47
52
  */
48
53
  direction?: 'down' | 'neutral' | 'up';
54
+ /**
55
+ * The word read out for a move down.
56
+ */
57
+ downLabel: string;
49
58
  /**
50
59
  * Which direction counts as an improvement for this metric.
51
60
  *
@@ -55,6 +64,19 @@ export declare class HelixHeadlessTrend extends HelixElement {
55
64
  * direction changes underneath.
56
65
  */
57
66
  good?: 'down' | 'up';
67
+ /**
68
+ * The word read out for no move, or for a direction that is not one of the
69
+ * three.
70
+ */
71
+ unchangedLabel: string;
72
+ /**
73
+ * The word read out for a move up.
74
+ */
75
+ upLabel: string;
76
+ /**
77
+ * The word read out after the direction when the move is a setback.
78
+ */
79
+ worseLabel: string;
58
80
  protected render(): TemplateResult;
59
81
  }
60
82
  declare global {
@@ -10,18 +10,24 @@ import { html } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  const DIRECTIONS = {
13
- down: { icon: arrowDown, word: 'Down' },
14
- neutral: { icon: minus, word: 'Unchanged' },
15
- up: { icon: arrowUp, word: 'Up' },
13
+ down: { icon: arrowDown, word: 'downLabel' },
14
+ neutral: { icon: minus, word: 'unchangedLabel' },
15
+ up: { icon: arrowUp, word: 'upLabel' },
16
16
  };
17
17
  const readDirection = (value) => DIRECTIONS[value] ?? DIRECTIONS.neutral;
18
18
  let HelixHeadlessTrend = class HelixHeadlessTrend extends HelixElement {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.betterLabel = 'better';
22
+ this.downLabel = 'Down';
23
+ this.unchangedLabel = 'Unchanged';
24
+ this.upLabel = 'Up';
25
+ this.worseLabel = 'worse';
26
+ }
19
27
  render() {
20
28
  const direction = readDirection(this.direction);
21
29
  const judgment = this.good && this.direction && this.direction !== 'neutral'
22
- ? this.good === this.direction
23
- ? ', better'
24
- : ', worse'
30
+ ? `, ${this.good === this.direction ? this.betterLabel : this.worseLabel}`
25
31
  : '';
26
32
  return html `<svg
27
33
  aria-hidden="true"
@@ -32,17 +38,32 @@ let HelixHeadlessTrend = class HelixHeadlessTrend extends HelixElement {
32
38
  >
33
39
  ${unsafeSVG(direction.icon.content)}
34
40
  </svg>
35
- <span part="direction">${`${direction.word}${judgment}`}</span>
41
+ <span part="direction">${`${this[direction.word]}${judgment}`}</span>
36
42
  <slot></slot>
37
43
  <slot name="label"></slot>`;
38
44
  }
39
45
  };
46
+ __decorate([
47
+ property({ attribute: 'better-label' })
48
+ ], HelixHeadlessTrend.prototype, "betterLabel", void 0);
40
49
  __decorate([
41
50
  property({ reflect: true })
42
51
  ], HelixHeadlessTrend.prototype, "direction", void 0);
52
+ __decorate([
53
+ property({ attribute: 'down-label' })
54
+ ], HelixHeadlessTrend.prototype, "downLabel", void 0);
43
55
  __decorate([
44
56
  property({ reflect: true })
45
57
  ], HelixHeadlessTrend.prototype, "good", void 0);
58
+ __decorate([
59
+ property({ attribute: 'unchanged-label' })
60
+ ], HelixHeadlessTrend.prototype, "unchangedLabel", void 0);
61
+ __decorate([
62
+ property({ attribute: 'up-label' })
63
+ ], HelixHeadlessTrend.prototype, "upLabel", void 0);
64
+ __decorate([
65
+ property({ attribute: 'worse-label' })
66
+ ], HelixHeadlessTrend.prototype, "worseLabel", void 0);
46
67
  HelixHeadlessTrend = __decorate([
47
68
  customElement('helix-headless-trend')
48
69
  ], HelixHeadlessTrend);
@@ -21,5 +21,10 @@ export interface KeyActions {
21
21
  * Nothing is taken while focus is in a range. The scrubber and the volume are
22
22
  * real inputs and the arrows are already theirs, so intercepting would mean
23
23
  * seeking twice for one press.
24
+ *
25
+ * **Nor a press a control already handled, nor Space on a control.** Space is
26
+ * how a button is pressed, so Space on Mute played the video and left it
27
+ * unmuted, and a menu row chosen with Space also toggled playback. A key a
28
+ * menu or a control took for itself arrives here already refused.
24
29
  */
25
30
  export declare const takeKey: (event: KeyboardEvent, video: HTMLVideoElement, actions: KeyActions) => boolean;