@fusion.dev/helix-headless 0.142.1 → 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,14 +4,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, DisabledCascadeController, HelixFilterEvent, HelixFormElement, hostAria, MixinCollection, MixinControllerValue, MixinField, reportValues, requestChange, scrollItemIntoView, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, DisabledCascadeController, HelixFilterEvent, HelixFormElement, hostAria, MixinCollection, MixinControllerValue, MixinField, reportValues, requestChange, scrollItemIntoView, Typeahead, typesCharacter, } from '@fusion.dev/helix-core';
8
8
  import { magnifyingGlass } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { HelixHeadlessOptionGroup } from '../../option/group/option-group.js';
13
13
  import { HelixHeadlessOption } from '../../option/root/option-root.js';
14
- const MOVES = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home']);
14
+ const MOVES = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home', 'PageDown', 'PageUp']);
15
+ const FIELD_MOVES = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
16
+ const PAGE = 10;
15
17
  const matched = new Map();
16
18
  const HIGHLIGHT = 'helix-filter-match';
17
19
  const paintable = () => typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';
@@ -67,8 +69,12 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
67
69
  this.filterState = undefined;
68
70
  this.filterable = false;
69
71
  this.filterLabel = 'Filter';
72
+ this.manual = false;
70
73
  this.multiple = false;
71
74
  this.activeValue = '';
75
+ this.parked = false;
76
+ this.typeahead = new Typeahead();
77
+ this.seen = new Set();
72
78
  this.forwarded = false;
73
79
  this.hasMatches = true;
74
80
  this.counted = false;
@@ -88,39 +94,70 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
88
94
  if (shown.length === 0) {
89
95
  return;
90
96
  }
91
- if (event.key === ' ' || event.key === 'Enter') {
92
- event.preventDefault();
97
+ const typing = this.forwarded || event.composedPath()[0] === this.partNamed('search-field');
98
+ if (event.key === 'Enter' || (event.key === ' ' && !typing && !this.typeahead.typing())) {
93
99
  if (this.activeValue !== '') {
100
+ event.preventDefault();
94
101
  this.choose(this.activeValue);
95
102
  }
103
+ else if (event.key === ' ') {
104
+ event.preventDefault();
105
+ }
96
106
  return;
97
107
  }
98
- if (!MOVES.has(event.key) && event.key !== 'Enter') {
108
+ if (this.multiple &&
109
+ !typing &&
110
+ event.key.toLowerCase() === 'a' &&
111
+ (event.ctrlKey || event.metaKey) &&
112
+ !event.altKey) {
113
+ event.preventDefault();
114
+ const reachable = shown.map((option) => option.value);
115
+ const all = reachable.every((value) => this.values.includes(value));
116
+ const kept = this.values.filter((value) => !reachable.includes(value));
117
+ this.requestValue((all ? kept : [...kept, ...reachable]).join(','));
118
+ return;
119
+ }
120
+ if (!typing && typesCharacter(event)) {
121
+ event.preventDefault();
122
+ const value = this.typeaheadTo(event.key);
123
+ if (value !== undefined) {
124
+ this.arrive(value);
125
+ }
126
+ return;
127
+ }
128
+ if (!(typing ? FIELD_MOVES : MOVES).has(event.key)) {
99
129
  return;
100
130
  }
101
131
  event.preventDefault();
132
+ this.typeahead.reset();
102
133
  const from = this.currentValue;
103
134
  const at = shown.findIndex((option) => option.value === from);
135
+ const last = shown.length - 1;
104
136
  let next = at;
105
137
  if (event.key === 'ArrowDown') {
106
138
  next = at < 0 ? 0 : (at + 1) % shown.length;
107
139
  }
108
140
  else if (event.key === 'ArrowUp') {
109
- next = at < 0 ? shown.length - 1 : (at - 1 + shown.length) % shown.length;
141
+ next = at < 0 ? last : (at - 1 + shown.length) % shown.length;
110
142
  }
111
143
  else if (event.key === 'Home') {
112
144
  next = 0;
113
145
  }
146
+ else if (event.key === 'End') {
147
+ next = last;
148
+ }
149
+ else if (event.key === 'PageDown') {
150
+ next = at < 0 ? 0 : Math.min(at + this.pageSize(), last);
151
+ }
114
152
  else {
115
- next = shown.length - 1;
153
+ next = at < 0 ? last : Math.max(at - this.pageSize(), 0);
116
154
  }
117
155
  const target = shown[next];
118
- if (!target) {
119
- return;
120
- }
121
- this.activeValue = target.value;
122
- if (!this.multiple && !this.forwarded) {
123
- this.choose(target.value);
156
+ if (target) {
157
+ this.arrive(target.value);
158
+ if (this.multiple && !typing && event.shiftKey && !this.values.includes(target.value)) {
159
+ this.choose(target.value);
160
+ }
124
161
  }
125
162
  };
126
163
  this.handleFilter = (event) => {
@@ -138,7 +175,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
138
175
  this.requestFilter('');
139
176
  return;
140
177
  }
141
- if (!MOVES.has(event.key)) {
178
+ if (!FIELD_MOVES.has(event.key)) {
142
179
  return;
143
180
  }
144
181
  event.stopPropagation();
@@ -160,6 +197,42 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
160
197
  const option = this.options[at];
161
198
  this.value = option?.value ?? '';
162
199
  }
200
+ highlight(where) {
201
+ const shown = this.shown.filter((option) => !option.disabled);
202
+ this.typeahead.reset();
203
+ this.parked = where === 'none';
204
+ if (where === 'none') {
205
+ this.activeValue = '';
206
+ }
207
+ else if (where === 'first') {
208
+ this.activeValue = shown[0]?.value ?? '';
209
+ }
210
+ else if (where === 'last') {
211
+ this.activeValue = shown.at(-1)?.value ?? '';
212
+ }
213
+ else {
214
+ const chosen = shown.find((option) => this.values.includes(option.value));
215
+ this.activeValue = (chosen ?? shown[0])?.value ?? '';
216
+ }
217
+ if (this.childrenKnown) {
218
+ this.place();
219
+ }
220
+ }
221
+ typeaheadTo(key) {
222
+ const shown = this.shown.filter((option) => !option.disabled);
223
+ const from = shown.findIndex((option) => option.value === this.currentValue);
224
+ const at = this.typeahead.match(key, shown.map((option) => option.text), from);
225
+ const target = shown[at];
226
+ if (!target) {
227
+ return undefined;
228
+ }
229
+ this.parked = false;
230
+ this.activeValue = target.value;
231
+ return target.value;
232
+ }
233
+ get typing() {
234
+ return this.typeahead.typing();
235
+ }
163
236
  get type() {
164
237
  return this.multiple ? 'select-multiple' : 'select-one';
165
238
  }
@@ -218,6 +291,19 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
218
291
  }
219
292
  this.requestValue(joined);
220
293
  }
294
+ arrive(value) {
295
+ this.parked = false;
296
+ this.activeValue = value;
297
+ if (!this.multiple && !this.manual && !this.forwarded) {
298
+ this.choose(value);
299
+ }
300
+ }
301
+ pageSize() {
302
+ const items = this.partNamed('items');
303
+ const row = this.shown[0]?.getBoundingClientRect().height ?? 0;
304
+ const height = items?.clientHeight ?? 0;
305
+ return row > 0 && height > 0 ? Math.max(1, Math.floor(height / row) - 1) : PAGE;
306
+ }
221
307
  navigate(event) {
222
308
  this.forwarded = true;
223
309
  try {
@@ -247,6 +333,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
247
333
  }
248
334
  }
249
335
  place() {
336
+ this.forgetRemoved();
250
337
  const needle = this.filter.trim().toLowerCase();
251
338
  const chosen = new Set(this.values);
252
339
  for (const option of this.options) {
@@ -258,7 +345,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
258
345
  group.hidden = inside.length > 0 && inside.every((option) => option.hidden);
259
346
  }
260
347
  const shown = this.shown;
261
- const rescue = needle !== '' || this.activeValue !== '';
348
+ const rescue = !this.parked && (needle !== '' || this.activeValue !== '');
262
349
  if (rescue && !shown.some((option) => option.value === this.activeValue)) {
263
350
  this.activeValue = shown.find((option) => !option.disabled)?.value ?? '';
264
351
  }
@@ -278,6 +365,21 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
278
365
  }
279
366
  this.counted = true;
280
367
  }
368
+ get owned() {
369
+ const root = this.getRootNode();
370
+ return root instanceof ShadowRoot && root.host.localName.startsWith('helix-');
371
+ }
372
+ forgetRemoved() {
373
+ if (this.owned) {
374
+ return;
375
+ }
376
+ const present = new Set(this.options.map((option) => option.value));
377
+ const gone = this.values.filter((value) => this.seen.has(value) && !present.has(value));
378
+ this.seen = present;
379
+ if (gone.length > 0) {
380
+ this.value = this.values.filter((value) => !gone.includes(value)).join(',');
381
+ }
382
+ }
281
383
  followActive() {
282
384
  const active = this.shown.find((option) => option.value === this.activeValue);
283
385
  if (active) {
@@ -309,10 +411,31 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
309
411
  willUpdate(changed) {
310
412
  super.willUpdate(changed);
311
413
  hostAria(this.internals, { role: '' });
414
+ if (changed.has('value') &&
415
+ !this.multiple &&
416
+ this.activeValue !== '' &&
417
+ !this.values.includes(this.activeValue)) {
418
+ this.activeValue = '';
419
+ this.typeahead.reset();
420
+ }
421
+ if (this.childrenKnown) {
422
+ this.seedDeclared();
423
+ }
312
424
  if (this.childrenKnown) {
313
425
  this.place();
314
426
  }
315
427
  }
428
+ seedDeclared() {
429
+ if (this.owned || this.defaultValue !== '' || this.hasAttribute('default-value')) {
430
+ return;
431
+ }
432
+ const declared = this.options
433
+ .filter((option) => option.defaultSelected)
434
+ .map((option) => option.value);
435
+ if (declared.length > 0) {
436
+ this.defaultValue = this.multiple ? declared.join(',') : (declared.at(-1) ?? '');
437
+ }
438
+ }
316
439
  get currentValue() {
317
440
  return this.activeValue || (this.value.split(',')[0] ?? '');
318
441
  }
@@ -338,6 +461,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
338
461
  apply: (value) => {
339
462
  const previous = this.filter;
340
463
  this.filterState = value;
464
+ this.parked = false;
341
465
  this.requestUpdate('filter', previous);
342
466
  },
343
467
  event: (value) => new HelixFilterEvent({ value }),
@@ -376,6 +500,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
376
500
  .value=${this.filter}
377
501
  placeholder=${this.filterLabel}
378
502
  aria-label=${this.filterLabel}
503
+ aria-autocomplete="list"
379
504
  aria-controls="items"
380
505
  ?disabled=${this.isDisabled}
381
506
  @input=${this.handleFilter}
@@ -394,7 +519,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
394
519
  aria-multiselectable=${this.multiple ? 'true' : nothing}
395
520
  aria-required=${this.required ? 'true' : nothing}
396
521
  ${''}
397
- tabindex=${this.isDisabled ? -1 : 0}
522
+ tabindex=${this.filterable ? nothing : this.isDisabled ? -1 : 0}
398
523
  >
399
524
  <slot @slotchange=${() => this.place()}></slot>
400
525
  ${this.renderStatus(shown)}
@@ -420,6 +545,9 @@ __decorate([
420
545
  __decorate([
421
546
  property({ attribute: 'filter-label' })
422
547
  ], HelixHeadlessListbox.prototype, "filterLabel", void 0);
548
+ __decorate([
549
+ property({ type: Boolean })
550
+ ], HelixHeadlessListbox.prototype, "manual", void 0);
423
551
  __decorate([
424
552
  property({ reflect: true, type: Boolean })
425
553
  ], HelixHeadlessListbox.prototype, "multiple", void 0);
@@ -43,14 +43,17 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
43
43
  *
44
44
  * Add `layers` to `controls` for a layers button. It opens a menu with a tile
45
45
  * for each layer, which sets the layer's `hidden` attribute. Layers with the
46
- * same `group`, such as a route and its dotted legs, share one tile.
46
+ * same `group`, such as a route and its dotted legs, share one tile. A tile
47
+ * sends `helix-before-toggle` from the layer first, so you can store the
48
+ * choice or refuse it.
47
49
  *
48
50
  * Imagery, a `helix-map-raster` with `ground`, is a map type and not a tile.
49
51
  * The menu opens with a card for each type, Standard, Hybrid, and Satellite,
50
52
  * with a picture of the map in it, and `map-type` says which is chosen.
51
53
  * Standard hides the imagery, Hybrid draws it under the roads and names, and
52
- * Satellite draws it alone. `connectMap` does not draw a hidden layer, so a layer you write with
53
- * `hidden` starts turned off.
54
+ * Satellite draws it alone. Choosing one sends `helix-before-change` with its
55
+ * name, and `change` once `map-type` holds it. `connectMap` does not draw a
56
+ * hidden layer, so a layer you write with `hidden` starts turned off.
54
57
  *
55
58
  * Markers, routes, areas, live positions, and points layers go inside the
56
59
  * frame as child elements.
@@ -66,7 +69,10 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
66
69
  * @slot footer - Content below the map, such as a legend or a scale.
67
70
  * @slot header - Content above the map, such as a title or a search field.
68
71
  *
72
+ * @event {Event} change - The layers menu changed the map type, which `map-type` now holds.
69
73
  * @event {HelixActivateEvent} helix-activate - A marker was pressed. The value is the marker's `name`, or its `label` if it has no name. A cluster sends `helix-map-cluster` instead.
74
+ * @event {HelixBeforeChangeEvent} helix-before-change - A map type was chosen in the layers menu, with its name: `standard`, `hybrid`, or `satellite`. Cancelable: cancel it to keep the current type.
75
+ * @event {HelixBeforeToggleEvent} helix-before-toggle - A layer's tile in the layers menu was pressed. Sent from the layer, with `checked` true when it is to be shown. Cancelable: cancel it to keep the layer as it is.
70
76
  * @event {HelixMapClusterEvent} helix-map-cluster - A cluster was pressed. Sent from the marker that shows the cluster, with the names of its places.
71
77
  * @event {HelixMapPanEvent} helix-map-pan - A pan button or arrow key was pressed. Move your map in response.
72
78
  * @event {HelixMapPartChangeEvent} helix-map-part-change - Clustering was turned on or off. Used by `connectMap`; pages do not need to listen for it.
@@ -279,8 +285,17 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
279
285
  private shown;
280
286
  /** A map type's name on its card. */
281
287
  private nameOf;
282
- /** Shows or hides layers, which `connectMap` draws or takes off the map. */
288
+ /**
289
+ * Shows or hides layers, which `connectMap` draws or takes off the map.
290
+ *
291
+ * **Asked first, from the layer.** The tile wrote `hidden` onto the
292
+ * consumer's element and said nothing, so a page could neither keep the
293
+ * choice nor keep its own state in step. The request comes from the first
294
+ * layer of the tile, which is the element the page wrote.
295
+ */
283
296
  private showLayers;
297
+ /** Asks for a map type, and sends `change` once it holds. */
298
+ private chooseMapType;
284
299
  /**
285
300
  * The layers menu's entries, in the order the layers are written: a layer
286
301
  * of its own, or every layer in a group, at the place of the first.
@@ -4,10 +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 { customElement, fillTemplate, formatNumbers, HelixActivateEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, hostAria, KeyboardNavigationController, MixinViewport, } from '@fusion.dev/helix-core';
7
+ import { customElement, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeChangeEvent, HelixBeforeToggleEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapReadyEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, hostAria, KeyboardNavigationController, MixinViewport, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { buildings, cloudRain, dotsNine, leaf, mountains, path, polygon, stack, target, } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
+ import { live } from 'lit/directives/live.js';
11
12
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
13
  const MAP_TYPES = ['standard', 'hybrid', 'satellite'];
13
14
  const iconOf = (layer) => {
@@ -205,10 +206,37 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
205
206
  : this.standardLabel;
206
207
  }
207
208
  showLayers(layers, shown) {
208
- for (const layer of layers) {
209
- layer.hidden = !shown;
209
+ const first = layers[0];
210
+ if (!first) {
211
+ return;
212
+ }
213
+ requestChange({
214
+ apply: () => {
215
+ for (const layer of layers) {
216
+ layer.hidden = !shown;
217
+ }
218
+ this.requestUpdate();
219
+ },
220
+ event: (checked) => new HelixBeforeToggleEvent({ checked }),
221
+ host: first,
222
+ next: shown,
223
+ });
224
+ }
225
+ chooseMapType(type) {
226
+ const applied = requestChange({
227
+ apply: (value) => {
228
+ this.mapType = value === 'standard' ? undefined : value;
229
+ },
230
+ event: (value) => new HelixBeforeChangeEvent({ value }),
231
+ host: this,
232
+ next: type,
233
+ });
234
+ if (applied) {
235
+ this.dispatchEvent(new Event('change', { bubbles: true }));
236
+ }
237
+ else {
238
+ this.requestUpdate();
210
239
  }
211
- this.requestUpdate();
212
240
  }
213
241
  get layerEntries() {
214
242
  const entries = [];
@@ -382,9 +410,10 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
382
410
  <input
383
411
  type="radio"
384
412
  name="map-type"
385
- .checked=${chosen === type}
386
- @change=${() => {
387
- this.mapType = type === 'standard' ? undefined : type;
413
+ .checked=${live(chosen === type)}
414
+ @change=${(event) => {
415
+ event.stopPropagation();
416
+ this.chooseMapType(type);
388
417
  }}
389
418
  />
390
419
  ${preview
@@ -31,7 +31,7 @@ declare const HelixHeadlessMegamenu_base: (new (...args: any[]) => import("@fusi
31
31
  *
32
32
  * @event {HelixBeforeCloseEvent} helix-before-close - The panel is about to close. Cancelable.
33
33
  * @event {HelixBeforeOpenEvent} helix-before-open - The panel is about to open. Cancelable.
34
- * @event {Event} toggle - The panel opened or closed. Carries `newState` and `oldState`.
34
+ * @event {ToggleEvent} toggle - The panel opened or closed. Carries `newState` and `oldState`.
35
35
  *
36
36
  * @csspart surface - The floating panel that opens.
37
37
  * @csspart trigger - The default trigger button, shown when the `trigger` slot is empty.
@@ -194,6 +194,15 @@ export declare class HelixHeadlessMegamenu extends HelixHeadlessMegamenu_base {
194
194
  private readonly handlePointerDown;
195
195
  private readonly handlePointerEnter;
196
196
  private readonly handlePointerLeave;
197
+ /**
198
+ * Closes the panel when keyboard focus leaves it for the page.
199
+ *
200
+ * Only when focus went somewhere: a `relatedTarget` of null is focus leaving
201
+ * the document, such as a switch to another window, and a reader who comes
202
+ * back expects the panel as they left it. `requestOpen` rather than `close`,
203
+ * because focus has already gone where the reader sent it.
204
+ */
205
+ private readonly handleFocusOut;
197
206
  private readonly handleTriggerClick;
198
207
  /**
199
208
  * Shuts the panel when a reader takes one of the destinations in it.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessMegamenu_1;
8
- import { anchorElement, customElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
8
+ import { anchorElement, customElement, deepContains, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  let showing;
@@ -57,6 +57,13 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
57
57
  this.requestOpen(false);
58
58
  });
59
59
  };
60
+ this.handleFocusOut = (event) => {
61
+ const next = event.relatedTarget;
62
+ if (!this.open || next === null || deepContains(this, next)) {
63
+ return;
64
+ }
65
+ this.requestOpen(false);
66
+ };
60
67
  this.handleTriggerClick = (event) => {
61
68
  this.stop();
62
69
  if (this.openOn !== 'hover') {
@@ -154,6 +161,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
154
161
  this.addEventListener('click', this.handleContentClick);
155
162
  this.addEventListener('pointerenter', this.handlePointerEnter);
156
163
  this.addEventListener('pointerleave', this.handlePointerLeave);
164
+ this.addEventListener('focusout', this.handleFocusOut);
157
165
  }
158
166
  disconnectedCallback() {
159
167
  super.disconnectedCallback();
@@ -164,6 +172,7 @@ let HelixHeadlessMegamenu = class HelixHeadlessMegamenu extends MixinControllerO
164
172
  this.removeEventListener('click', this.handleContentClick);
165
173
  this.removeEventListener('pointerenter', this.handlePointerEnter);
166
174
  this.removeEventListener('pointerleave', this.handlePointerLeave);
175
+ this.removeEventListener('focusout', this.handleFocusOut);
167
176
  this.stop();
168
177
  if (showing === this) {
169
178
  showing = undefined;
@@ -14,6 +14,11 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
14
14
  * only one can be on. A row that opens a page keeps `menuitem` and sets
15
15
  * `aria-haspopup`.
16
16
  *
17
+ * Choosing a choice row checks or unchecks it before `helix-select` is sent,
18
+ * so a listener reads the new `checked`. An `exclusive` row turns on and turns
19
+ * off the `exclusive` rows next to it. Cancel `helix-before-toggle` to keep a
20
+ * row as it is and set `checked` yourself.
21
+ *
17
22
  * The menu sets whether the row's page is showing as a property, never as an
18
23
  * attribute. An attribute written onto your element after it upgrades causes a
19
24
  * hydration mismatch.
@@ -27,12 +32,15 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
27
32
  * @slot start - Content before the label, such as an icon.
28
33
  * @slot submenu - The rows of a submenu that this row opens.
29
34
  *
35
+ * @event {HelixBeforeToggleEvent} helix-before-toggle - A choice row is about to be checked or unchecked, carrying the state asked for. Cancelable: cancel it to keep the row as it is.
30
36
  * @event {HelixSelectEvent} helix-select - The row was chosen. Cancelable: cancel it to keep the menu open.
31
37
  *
32
38
  * @csspart item - The row, which is an `<a>` where `href` is set.
33
39
  * @csspart mark - The trailing mark: a check mark when the row is checked, or a caret
34
40
  * when it opens a submenu. A row never shows both, so they share one column.
35
41
  * @csspart start - The leading column that holds the `start` slot. It is always rendered.
42
+ *
43
+ * @cssstate checked - The row is checked.
36
44
  */
37
45
  export declare class HelixHeadlessMenuItem extends HelixElement {
38
46
  /**
@@ -45,10 +53,16 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
45
53
  * thing they are not.
46
54
  *
47
55
  * `checked` implies it, so a row that is on never has to say both.
56
+ *
57
+ * Choosing the row checks or unchecks it, after a `helix-before-toggle` that
58
+ * you can cancel.
48
59
  */
49
60
  checkable: boolean;
50
61
  /**
51
62
  * Whether the row is checked.
63
+ *
64
+ * The row changes it when it is chosen. Not reflected: the attribute is the
65
+ * state you start the row in, and `:state(checked)` carries the current one.
52
66
  */
53
67
  checked: boolean;
54
68
  /**
@@ -66,11 +80,11 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
66
80
  /**
67
81
  * Whether only one row in its set can be checked at a time.
68
82
  *
69
- * Announcement and nothing else, the way `checkable` is: this element applies
70
- * no choice and polices no group, so what it does is tell a reader which kind
71
- * of set they are in. `menuitemcheckbox` says every row here is independent,
72
- * which for a run of comparisons, or the values of a filter that compares
73
- * against one, is the opposite of true.
83
+ * The set is the run of `exclusive` rows next to this one, up to the first
84
+ * element that is not one, such as a divider. Choosing a row in it checks
85
+ * that row and unchecks the others. Choosing the row that is checked leaves
86
+ * it checked. `menuitemradio` tells a reader which kind of set they are in,
87
+ * where `menuitemcheckbox` says every row is independent.
74
88
  *
75
89
  * Meaningless without `checkable`, since a row that cannot be ticked is not
76
90
  * one of a set of anything.
@@ -225,16 +239,31 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
225
239
  /** Chooses the row, the same as pressing it. */
226
240
  select(): void;
227
241
  /**
228
- * Whether the row has focus, which is what selected means in a menu.
242
+ * Whether the row has focus, which makes it the row the keyboard is on.
243
+ *
244
+ * `Option` calls the same thing `active`. A menu holds no answer the way a
245
+ * radio group does, only a row the reader has walked to, so focus is the
246
+ * whole of what "the current one" can mean here. Read from the shadow root
247
+ * rather than kept as state, because focus moves for reasons this element is
248
+ * not told about: a press, a Tab, the drilldown putting the reader on the
249
+ * first row of a page it has just opened.
250
+ */
251
+ get active(): boolean;
252
+ /**
253
+ * Whether the row has focus, the same as `active`.
229
254
  *
230
- * A radio group's selection is an answer it holds; a menu has no answer, only
231
- * a row the reader has walked to, so focus is the whole of what "the current
232
- * one" can mean here. Read from the shadow root rather than kept as state,
233
- * because focus moves for reasons this element is not told about: a press, a
234
- * Tab, the drilldown putting the reader on the first row of a page it has
235
- * just opened.
255
+ * @deprecated Read `active`. Everywhere else in the library `selected` means
256
+ * chosen out of a set, and a menu row is never chosen that way.
236
257
  */
237
258
  get selected(): boolean;
259
+ /**
260
+ * Checks or unchecks a choice row.
261
+ *
262
+ * An `exclusive` row only turns on, and turning on turns its set off.
263
+ */
264
+ private tick;
265
+ /** The other `exclusive` rows in this row's run of siblings. */
266
+ private exclusivePeers;
238
267
  private choose;
239
268
  private readonly handleClick;
240
269
  /**
@@ -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, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { caretRight, check } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -58,6 +58,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
58
58
  }
59
59
  willUpdate(changed) {
60
60
  super.willUpdate(changed);
61
+ this.toggleState('checked', this.checked);
61
62
  if (!this.childrenKnown) {
62
63
  return;
63
64
  }
@@ -78,13 +79,55 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
78
79
  select() {
79
80
  this.choose();
80
81
  }
82
+ get active() {
83
+ const active = deepActiveElement();
84
+ return active !== null && active.getRootNode() === this.renderRoot;
85
+ }
81
86
  get selected() {
82
- return this.ownerDocument.activeElement === this;
87
+ return this.active;
88
+ }
89
+ tick() {
90
+ if (!(this.checkable || this.checked) || this.hasSubmenu) {
91
+ return;
92
+ }
93
+ const next = this.exclusive ? true : !this.checked;
94
+ if (next === this.checked) {
95
+ return;
96
+ }
97
+ const applied = requestChange({
98
+ apply: (checked) => {
99
+ this.checked = checked;
100
+ },
101
+ event: (checked) => new HelixBeforeToggleEvent({ checked }),
102
+ host: this,
103
+ next,
104
+ });
105
+ if (applied && this.exclusive) {
106
+ for (const peer of this.exclusivePeers()) {
107
+ peer.checked = false;
108
+ }
109
+ }
110
+ }
111
+ exclusivePeers() {
112
+ const peers = [];
113
+ const inSet = (node) => node !== null &&
114
+ node.isMenuItem === true &&
115
+ node.exclusive &&
116
+ node.slot === this.slot;
117
+ for (const step of ['previousElementSibling', 'nextElementSibling']) {
118
+ let node = this[step];
119
+ while (inSet(node)) {
120
+ peers.push(node);
121
+ node = node[step];
122
+ }
123
+ }
124
+ return peers;
83
125
  }
84
126
  choose() {
85
127
  if (this.disabled) {
86
128
  return;
87
129
  }
130
+ this.tick();
88
131
  this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
89
132
  }
90
133
  render() {
@@ -155,7 +198,7 @@ __decorate([
155
198
  property({ reflect: true, type: Boolean })
156
199
  ], HelixHeadlessMenuItem.prototype, "checkable", void 0);
157
200
  __decorate([
158
- property({ reflect: true, type: Boolean })
201
+ property({ type: Boolean })
159
202
  ], HelixHeadlessMenuItem.prototype, "checked", void 0);
160
203
  __decorate([
161
204
  property({ reflect: true, type: Boolean })