@fusion.dev/helix-headless 0.147.0 → 0.148.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 (141) hide show
  1. package/custom-elements.json +4512 -790
  2. package/dist/accordion/item/accordion-item.d.ts +5 -2
  3. package/dist/accordion/item/accordion-item.js +11 -7
  4. package/dist/accordion/root/accordion-root.js +4 -6
  5. package/dist/announcement/root/announcement-root.d.ts +56 -5
  6. package/dist/announcement/root/announcement-root.js +124 -21
  7. package/dist/area-chart/root/area-chart-root.js +3 -4
  8. package/dist/badge/count.d.ts +14 -0
  9. package/dist/badge/count.js +20 -0
  10. package/dist/badge/root/badge-root.js +8 -22
  11. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  12. package/dist/blockquote/root/blockquote-root.js +85 -8
  13. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +2 -2
  14. package/dist/button-group/arrows.d.ts +14 -0
  15. package/dist/button-group/arrows.js +10 -0
  16. package/dist/button-group/root/button-group-root.d.ts +141 -3
  17. package/dist/button-group/root/button-group-root.js +142 -2
  18. package/dist/canvas/edge-pan.d.ts +61 -0
  19. package/dist/canvas/edge-pan.js +63 -0
  20. package/dist/canvas/node/canvas-node.d.ts +12 -2
  21. package/dist/canvas/node/canvas-node.js +6 -2
  22. package/dist/canvas/root/canvas-document.d.ts +10 -0
  23. package/dist/canvas/root/canvas-document.js +1 -0
  24. package/dist/canvas/root/canvas-root.d.ts +107 -5
  25. package/dist/canvas/root/canvas-root.js +140 -36
  26. package/dist/canvas/snap.d.ts +42 -0
  27. package/dist/canvas/snap.js +32 -0
  28. package/dist/carousel/root/carousel-root.d.ts +4 -3
  29. package/dist/carousel/root/carousel-root.js +2 -6
  30. package/dist/chat/root/chat-root.d.ts +13 -3
  31. package/dist/chat/root/chat-root.js +2 -6
  32. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  33. package/dist/checkbox-group/root/checkbox-group-root.js +18 -10
  34. package/dist/checkbox-group/select-all.d.ts +11 -0
  35. package/dist/checkbox-group/select-all.js +5 -0
  36. package/dist/code-block/root/code-block-root.d.ts +8 -6
  37. package/dist/code-block/root/code-block-root.js +4 -3
  38. package/dist/color-picker/root/color-picker-root.js +6 -12
  39. package/dist/color-picker/wheel.d.ts +11 -0
  40. package/dist/color-picker/wheel.js +9 -0
  41. package/dist/combobox/root/combobox-root.d.ts +70 -11
  42. package/dist/combobox/root/combobox-root.js +68 -18
  43. package/dist/command/root/command-root.d.ts +4 -3
  44. package/dist/data-table/root/data-table-root.js +17 -44
  45. package/dist/description-list/item/description-list-item.d.ts +4 -0
  46. package/dist/description-list/item/description-list-item.js +7 -1
  47. package/dist/divider/root/divider-root.d.ts +45 -16
  48. package/dist/divider/root/divider-root.js +28 -4
  49. package/dist/drilldown/item/drilldown-item.js +3 -4
  50. package/dist/element-events.generated.js +1 -0
  51. package/dist/empty/root/empty-root.d.ts +17 -3
  52. package/dist/empty/root/empty-root.js +29 -5
  53. package/dist/file-input/root/file-input-root.js +3 -4
  54. package/dist/file-input/status.js +2 -1
  55. package/dist/filter-input/chip/filter-chip.js +2 -3
  56. package/dist/format-date/fields.d.ts +20 -0
  57. package/dist/format-date/fields.js +14 -0
  58. package/dist/format-date/options.d.ts +2 -1
  59. package/dist/format-date/options.js +9 -38
  60. package/dist/format-date/root/format-date-root.d.ts +35 -3
  61. package/dist/format-date/root/format-date-root.js +24 -32
  62. package/dist/graph/root/graph-root.d.ts +23 -5
  63. package/dist/graph/root/graph-root.js +17 -20
  64. package/dist/heatmap/root/heatmap-root.d.ts +12 -0
  65. package/dist/heatmap/root/heatmap-root.js +26 -11
  66. package/dist/image/root/image-root.d.ts +11 -0
  67. package/dist/kbd/root/kbd-root.js +1 -2
  68. package/dist/legend/root/legend-root.js +4 -17
  69. package/dist/list/root/list-root.js +8 -22
  70. package/dist/listbox/root/listbox-root.d.ts +119 -24
  71. package/dist/listbox/root/listbox-root.js +130 -132
  72. package/dist/map/marker/map-marker.d.ts +2 -1
  73. package/dist/map/root/map-root.d.ts +40 -2
  74. package/dist/map/root/map-root.js +64 -23
  75. package/dist/megamenu/root/megamenu-root.js +4 -11
  76. package/dist/menu/root/menu-root.d.ts +2 -2
  77. package/dist/navigation/root/navigation-root.js +5 -8
  78. package/dist/notifications/group/notification-group.d.ts +8 -1
  79. package/dist/notifications/group/notification-group.js +12 -6
  80. package/dist/notifications/notification/notification.d.ts +12 -1
  81. package/dist/notifications/notification/notification.js +3 -1
  82. package/dist/notifications/root/notifications-root.d.ts +41 -5
  83. package/dist/notifications/root/notifications-root.js +27 -7
  84. package/dist/object-inspector/root/object-inspector-root.d.ts +5 -1
  85. package/dist/object-inspector/root/object-inspector-root.js +21 -11
  86. package/dist/pagination/size/pagination-size.d.ts +1 -0
  87. package/dist/pagination/size/pagination-size.js +1 -1
  88. package/dist/pin-input/code.d.ts +23 -0
  89. package/dist/pin-input/code.js +25 -0
  90. package/dist/pin-input/root/pin-input-root.js +9 -23
  91. package/dist/qr-code/root/qr-code-root.d.ts +14 -3
  92. package/dist/qr-code/root/qr-code-root.js +19 -5
  93. package/dist/scatter-chart/measures.d.ts +22 -0
  94. package/dist/scatter-chart/measures.js +11 -0
  95. package/dist/scatter-chart/root/scatter-chart-root.d.ts +2 -6
  96. package/dist/scatter-chart/root/scatter-chart-root.js +6 -19
  97. package/dist/select/root/select-root.d.ts +56 -8
  98. package/dist/select/root/select-root.js +33 -9
  99. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  100. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  101. package/dist/speed-dial/index.d.ts +1 -0
  102. package/dist/speed-dial/index.js +1 -0
  103. package/dist/speed-dial/item/speed-dial-item.d.ts +26 -1
  104. package/dist/speed-dial/item/speed-dial-item.js +41 -3
  105. package/dist/speed-dial/long-press.d.ts +50 -0
  106. package/dist/speed-dial/long-press.js +48 -0
  107. package/dist/speed-dial/root/speed-dial-root.d.ts +80 -1
  108. package/dist/speed-dial/root/speed-dial-root.js +114 -5
  109. package/dist/splitter/root/splitter-root.d.ts +7 -3
  110. package/dist/splitter/root/splitter-root.js +16 -3
  111. package/dist/table/cell/table-cell.d.ts +8 -1
  112. package/dist/table/cell/table-cell.js +6 -2
  113. package/dist/table/grid-controller.d.ts +117 -0
  114. package/dist/table/grid-controller.js +293 -0
  115. package/dist/table/root/table-root.d.ts +47 -3
  116. package/dist/table/root/table-root.js +81 -24
  117. package/dist/table/row/table-row.d.ts +13 -1
  118. package/dist/table/row/table-row.js +20 -6
  119. package/dist/tag/group/tag-group.js +6 -15
  120. package/dist/time-picker/root/time-picker-root.d.ts +1 -0
  121. package/dist/time-picker/root/time-picker-root.js +3 -0
  122. package/dist/toast/item/toast-item.d.ts +6 -3
  123. package/dist/toast/item/toast-item.js +1 -0
  124. package/dist/toast/root/toast-root.d.ts +13 -1
  125. package/dist/toast/root/toast-root.js +20 -14
  126. package/dist/toggle/group/toggle-group.js +3 -5
  127. package/dist/toolbar/root/toolbar-root.d.ts +31 -0
  128. package/dist/toolbar/root/toolbar-root.js +39 -8
  129. package/dist/tooltip/root/tooltip-root.js +1 -2
  130. package/dist/video/root/video-root.d.ts +11 -0
  131. package/dist/video/root/video-root.js +15 -1
  132. package/dist/virtual-list/root/virtual-list-root.js +6 -18
  133. package/lint.json +1 -1
  134. package/package.json +5 -5
  135. package/types/jsx.d.ts +2005 -266
  136. package/types/svelte.d.ts +1 -0
  137. package/types/vue.d.ts +338 -62
  138. package/dist/kbd/keys.d.ts +0 -29
  139. package/dist/kbd/keys.js +0 -52
  140. package/dist/tooltip/focus-target.d.ts +0 -6
  141. package/dist/tooltip/focus-target.js +0 -9
@@ -4,59 +4,15 @@ 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, countLabel, customElement, DelayController, DisabledCascadeController, endInSight, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, MixinCollection, MixinControllerValue, MixinField, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, typesCharacter, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, centerInView, columnStep, countLabel, customElement, DelayController, DisabledCascadeController, drawAutofill, endInSight, fillTemplate, HelixFilterEvent, HelixFormElement, HelixLoadMoreEvent, hostAria, inlineStep, MixinCollection, MixinControllerValue, MixinField, markMatches, OptionWindowController, onApple, pageRows, rangeAdd, reportValues, requestChange, scrollItemIntoView, selectsAll, Typeahead, toggleEvery, 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
- import { HelixHeadlessOptionGroup } from '../../option/group/option-group.js';
13
12
  import { HelixHeadlessOption } from '../../option/root/option-root.js';
14
- const MOVES = new Set(['ArrowDown', 'ArrowUp', 'End', 'Home', 'PageDown', 'PageUp']);
15
13
  const FIELD_MOVES = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
14
+ const MOVES = new Set([...FIELD_MOVES, 'End', 'Home']);
16
15
  const PAGE = 10;
17
- const matched = new Map();
18
- const HIGHLIGHT = 'helix-filter-match';
19
- const paintable = () => typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';
20
- const paintMatches = () => {
21
- if (!paintable()) {
22
- return;
23
- }
24
- const all = [...matched.values()].flat();
25
- if (all.length === 0) {
26
- CSS.highlights.delete(HIGHLIGHT);
27
- return;
28
- }
29
- CSS.highlights.set(HIGHLIGHT, new Highlight(...all));
30
- };
31
- const rangeFor = (root, needle) => {
32
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
33
- const nodes = [];
34
- let whole = '';
35
- for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
36
- nodes.push(node);
37
- whole += node.nodeValue ?? '';
38
- }
39
- const at = whole.toLowerCase().indexOf(needle);
40
- if (at < 0) {
41
- return [];
42
- }
43
- const range = document.createRange();
44
- let seen = 0;
45
- let started = false;
46
- for (const node of nodes) {
47
- const length = node.nodeValue?.length ?? 0;
48
- if (!started && at < seen + length) {
49
- range.setStart(node, at - seen);
50
- started = true;
51
- }
52
- if (started && at + needle.length <= seen + length) {
53
- range.setEnd(node, at + needle.length - seen);
54
- return [range];
55
- }
56
- seen += length;
57
- }
58
- return [];
59
- };
60
16
  let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(MixinControllerValue(MixinField(HelixFormElement))) {
61
17
  constructor() {
62
18
  super(...arguments);
@@ -70,13 +26,20 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
70
26
  this.filterable = false;
71
27
  this.filterDelay = 0;
72
28
  this.filterLabel = 'Filter';
29
+ this.items = undefined;
30
+ this.data = new OptionWindowController(this, {
31
+ current: () => this.currentValue,
32
+ items: () => this.items,
33
+ });
73
34
  this.loadError = '';
74
35
  this.manual = false;
75
36
  this.multiple = false;
76
37
  this.resultLabel = '{count} result';
77
38
  this.resultsLabel = '{count} results';
39
+ this.selectedLabel = '{name}, selected';
78
40
  this.activeValue = '';
79
41
  this.parked = false;
42
+ this.anchor = '';
80
43
  this.typeahead = new Typeahead();
81
44
  this.seen = new Set();
82
45
  this.forwarded = false;
@@ -85,6 +48,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
85
48
  this.cascade = new DisabledCascadeController(this, {
86
49
  items: () => this.options,
87
50
  });
51
+ this.chosenAgain = undefined;
88
52
  this.handleSelection = (event) => {
89
53
  if (event.detail.href !== undefined) {
90
54
  return;
@@ -102,11 +66,17 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
102
66
  return;
103
67
  }
104
68
  const typing = this.forwarded || event.composedPath()[0] === this.partNamed('search-field');
69
+ if (this.multiple && event.key === ' ' && event.shiftKey && !typing) {
70
+ event.preventDefault();
71
+ this.extend(this.activeValue);
72
+ return;
73
+ }
105
74
  if (event.key === 'Enter' || (event.key === ' ' && !typing && !this.typeahead.typing())) {
106
75
  const link = shown.find((option) => option.value === this.activeValue && Boolean(option.href));
107
76
  if (link !== undefined) {
108
77
  event.preventDefault();
109
78
  if (event.key === 'Enter') {
79
+ ;
110
80
  link.select(event);
111
81
  }
112
82
  }
@@ -121,10 +91,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
121
91
  }
122
92
  if (this.multiple && !typing && selectsAll(event)) {
123
93
  event.preventDefault();
124
- const reachable = shown.map((option) => option.value);
125
- const all = reachable.every((value) => this.values.includes(value));
126
- const kept = this.values.filter((value) => !reachable.includes(value));
127
- this.requestValue((all ? kept : [...kept, ...reachable]).join(','));
94
+ this.requestValue(toggleEvery(this.values, shown.map((option) => option.value)).join(','));
128
95
  return;
129
96
  }
130
97
  if (!typing && typesCharacter(event)) {
@@ -140,7 +107,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
140
107
  event.shiftKey &&
141
108
  (event.ctrlKey || event.metaKey) &&
142
109
  (event.key === 'Home' || event.key === 'End');
143
- if (!fieldRange && !(typing ? FIELD_MOVES : MOVES).has(event.key)) {
110
+ const grid = this.layout === 'grid' && !this.data.records;
111
+ const along = grid && !typing ? inlineStep(this, event.key) : 0;
112
+ if (!fieldRange && !along && !(typing ? FIELD_MOVES : MOVES).has(event.key)) {
144
113
  return;
145
114
  }
146
115
  event.preventDefault();
@@ -149,7 +118,13 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
149
118
  const at = shown.findIndex((option) => option.value === from);
150
119
  const last = shown.length - 1;
151
120
  let next = at;
152
- if (event.key === 'ArrowDown') {
121
+ if (along) {
122
+ next = Math.min(Math.max(at + along, 0), last);
123
+ }
124
+ else if (grid && at >= 0 && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
125
+ next = columnStep(shown.map((option) => option.getBoundingClientRect().left), at, event.key === 'ArrowDown' ? 1 : -1);
126
+ }
127
+ else if (event.key === 'ArrowDown') {
153
128
  next = at < 0 ? 0 : (at + 1) % shown.length;
154
129
  }
155
130
  else if (event.key === 'ArrowUp') {
@@ -197,7 +172,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
197
172
  this.askedAt = -1;
198
173
  this.handleScroll = (event) => {
199
174
  const items = event.currentTarget;
200
- const count = this.options.length;
175
+ const count = this.entries.length;
201
176
  if (this.loading ||
202
177
  !endInSight({
203
178
  askedAt: this.askedAt,
@@ -241,43 +216,52 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
241
216
  this.requestUpdate('filter', previous);
242
217
  }
243
218
  get selectedIndex() {
244
- return this.options.findIndex((option) => option.selected);
219
+ return this.entries.findIndex((option) => option.selected);
245
220
  }
246
221
  set selectedIndex(at) {
247
- const option = this.options[at];
222
+ const option = this.entries[at];
248
223
  this.value = option?.value ?? '';
249
224
  }
250
225
  highlight(where) {
251
226
  const shown = this.shown.filter((option) => !option.disabled);
252
227
  this.typeahead.reset();
253
228
  this.parked = where === 'none';
254
- if (where === 'none') {
255
- this.activeValue = '';
256
- }
257
- else if (where === 'first') {
258
- this.activeValue = shown[0]?.value ?? '';
259
- }
260
- else if (where === 'last') {
261
- this.activeValue = shown.at(-1)?.value ?? '';
262
- }
263
- else {
264
- const chosen = shown.find((option) => this.values.includes(option.value));
265
- this.activeValue = (chosen ?? shown[0])?.value ?? '';
266
- }
229
+ const chosen = where === 'selected' ? shown.find((option) => this.values.includes(option.value)) : undefined;
230
+ this.activeValue =
231
+ where === 'none'
232
+ ? ''
233
+ : ((where === 'last' ? shown.at(-1) : (chosen ?? shown[0]))?.value ?? '');
267
234
  if (this.childrenKnown) {
268
235
  this.place();
269
236
  }
270
237
  }
271
238
  tell(changed) {
272
- if (!changed.has('filter') || !this.hasUpdated) {
239
+ if (!this.hasUpdated) {
240
+ return;
241
+ }
242
+ if (!changed.has('filter')) {
243
+ this.tellActive(changed);
273
244
  return;
274
245
  }
275
246
  const count = this.shown.length;
276
- if (this.filter.trim() === '' || count === 0) {
277
- this.announced = '';
247
+ this.announced =
248
+ this.filter.trim() && count
249
+ ? countLabel(count, this.resultLabel, this.resultsLabel, this.lang)
250
+ : '';
251
+ }
252
+ tellActive(changed) {
253
+ if (!changed.has('activeValue') ||
254
+ !this.filterable ||
255
+ !onApple() ||
256
+ this.renderRoot.activeElement?.getAttribute('part') !== 'search-field') {
278
257
  return;
279
258
  }
280
- this.announced = countLabel(count, this.resultLabel, this.resultsLabel, this.lang);
259
+ const active = this.entries.find((option) => option.value === this.activeValue);
260
+ if (active) {
261
+ this.announced = active.selected
262
+ ? fillTemplate(this.selectedLabel, { name: active.text })
263
+ : active.text;
264
+ }
281
265
  }
282
266
  typeaheadTo(key) {
283
267
  const shown = this.shown.filter((option) => !option.disabled);
@@ -315,16 +299,25 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
315
299
  return found;
316
300
  }
317
301
  get groups() {
318
- return this.among((node) => node instanceof HelixHeadlessOptionGroup, this.children);
302
+ return this.among((node) => node.localName.endsWith('-option-group'), this.children);
303
+ }
304
+ get entries() {
305
+ return this.data.records ?? this.options;
306
+ }
307
+ get elements() {
308
+ return this.data.records ? this.data.drawn : this.options;
319
309
  }
320
310
  get shown() {
321
- return this.options.filter((option) => !option.hidden);
311
+ return this.entries.filter((option) => !option.hidden);
322
312
  }
323
313
  get values() {
324
314
  return this.value === '' ? [] : this.value.split(',');
325
315
  }
326
316
  choose(value) {
327
317
  const chosen = this.values;
318
+ if (!chosen.includes(value)) {
319
+ this.anchor = value;
320
+ }
328
321
  const next = this.multiple
329
322
  ? chosen.includes(value)
330
323
  ? chosen.filter((each) => each !== value)
@@ -332,23 +325,31 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
332
325
  : [value];
333
326
  const joined = next.join(',');
334
327
  if (joined === this.value) {
328
+ this.chosenAgain?.();
335
329
  return;
336
330
  }
337
331
  this.requestValue(joined);
338
332
  }
333
+ extend(to) {
334
+ const reach = this.shown.filter((option) => !option.disabled).map((option) => option.value);
335
+ const taken = rangeAdd(this.values, reach, reach.indexOf(this.anchor), reach.indexOf(to));
336
+ if (taken) {
337
+ this.requestValue(taken.join(','));
338
+ }
339
+ }
339
340
  arrive(value) {
340
341
  this.parked = false;
341
342
  this.activeValue = value;
342
- const link = this.options.some((option) => option.value === value && Boolean(option.href));
343
+ const link = this.entries.some((option) => option.value === value && Boolean(option.href));
343
344
  if (!this.multiple && !this.manual && !this.forwarded && !link) {
344
345
  this.choose(value);
345
346
  }
346
347
  }
347
348
  pageSize() {
348
349
  const items = this.partNamed('items');
349
- const row = this.shown[0]?.getBoundingClientRect().height ?? 0;
350
+ const row = this.elements.find((option) => !option.hidden)?.getBoundingClientRect().height ?? 0;
350
351
  const height = items?.clientHeight ?? 0;
351
- return row > 0 && height > 0 ? Math.max(1, Math.floor(height / row) - 1) : PAGE;
352
+ return row > 0 && height > 0 ? pageRows(height, row) : PAGE;
352
353
  }
353
354
  navigate(event) {
354
355
  this.forwarded = true;
@@ -360,29 +361,21 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
360
361
  }
361
362
  }
362
363
  reveal() {
363
- const items = this.partNamed('items');
364
- const chosen = this.options.find((option) => option.selected && !option.hidden);
365
- if (!items || !chosen) {
364
+ if (this.data.records) {
365
+ this.data.center(this.values[0] ?? '');
366
366
  return;
367
367
  }
368
- for (let pass = 0; pass < 2; pass += 1) {
369
- const box = items.getBoundingClientRect();
370
- const row = chosen.getBoundingClientRect();
371
- if (box.height === 0 || row.height === 0) {
372
- return;
373
- }
374
- const off = row.top + row.height / 2 - (box.top + box.height / 2);
375
- if (Math.abs(off) < 1) {
376
- return;
377
- }
378
- items.scrollTop += off;
368
+ const items = this.partNamed('items');
369
+ const chosen = this.options.find((option) => option.selected && !option.hidden);
370
+ if (items && chosen) {
371
+ centerInView(items, chosen);
379
372
  }
380
373
  }
381
374
  place() {
382
375
  this.forgetRemoved();
383
376
  const needle = this.filter.trim().toLowerCase();
384
377
  const chosen = new Set(this.values);
385
- for (const option of this.options) {
378
+ for (const option of this.entries) {
386
379
  option.hidden = needle !== '' && !option.text.toLowerCase().includes(needle);
387
380
  option.selected = chosen.has(option.value);
388
381
  }
@@ -398,20 +391,10 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
398
391
  if (rescue && !shown.some((option) => option.value === this.activeValue)) {
399
392
  this.activeValue = shown.find((option) => !option.disabled)?.value ?? '';
400
393
  }
401
- for (const option of this.options) {
394
+ for (const option of this.entries) {
402
395
  option.active = option.value === this.activeValue;
403
396
  }
404
- if (needle === '' || !paintable()) {
405
- matched.delete(this);
406
- }
407
- else {
408
- matched.set(this, shown.flatMap((option) => rangeFor(option, needle)));
409
- }
410
- paintMatches();
411
- const anyShown = shown.length > 0;
412
- if (anyShown !== this.hasMatches) {
413
- this.hasMatches = anyShown;
414
- }
397
+ this.hasMatches = shown.length > 0;
415
398
  this.counted = true;
416
399
  }
417
400
  get owned() {
@@ -422,15 +405,19 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
422
405
  if (this.owned) {
423
406
  return;
424
407
  }
425
- const present = new Set(this.options.map((option) => option.value));
408
+ const present = new Set(this.entries.map((option) => option.value));
426
409
  const gone = this.values.filter((value) => this.seen.has(value) && !present.has(value));
427
410
  this.seen = present;
428
411
  if (gone.length > 0) {
429
412
  this.value = this.values.filter((value) => !gone.includes(value)).join(',');
430
413
  }
431
414
  }
432
- followActive() {
433
- const active = this.shown.find((option) => option.value === this.activeValue);
415
+ followActive(changed) {
416
+ if (this.data.records &&
417
+ !this.data.follow(changed.has('activeValue') || changed.has('filter'))) {
418
+ return;
419
+ }
420
+ const active = this.elements.find((option) => !option.hidden && option.value === this.activeValue);
434
421
  if (active) {
435
422
  const items = this.partNamed('items');
436
423
  scrollItemIntoView(active, { padding: 4, ...(items ? { scroller: items } : {}) });
@@ -454,8 +441,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
454
441
  super.disconnectedCallback();
455
442
  this.removeEventListener('helix-select', this.handleSelection);
456
443
  this.removeEventListener('keydown', this.handleKeydown);
457
- matched.delete(this);
458
- paintMatches();
444
+ markMatches(this, [], '');
459
445
  }
460
446
  willUpdate(changed) {
461
447
  super.willUpdate(changed);
@@ -476,7 +462,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
476
462
  }
477
463
  this.toggleState('empty', !this.showsOptions);
478
464
  this.toggleState('loading', this.loading);
479
- this.toggleState('error', this.loadError !== '');
465
+ this.toggleState('error', this.failed);
480
466
  this.toggleState('no-results', this.noResults);
481
467
  }
482
468
  seedDeclared() {
@@ -498,14 +484,15 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
498
484
  if (!list || !('ariaActiveDescendantElement' in list)) {
499
485
  return;
500
486
  }
501
- const active = this.options.find((option) => option.value === this.currentValue);
487
+ const active = this.elements.find((option) => option.value === this.currentValue);
502
488
  list.ariaActiveDescendantElement = active ?? null;
503
489
  }
504
490
  updated(changed) {
505
491
  super.updated(changed);
506
- this.followActive();
492
+ this.followActive(changed);
507
493
  this.sync();
508
494
  this.linkActive();
495
+ markMatches(this, this.elements.filter((option) => !option.hidden), this.filter.trim());
509
496
  }
510
497
  takeFilter(control) {
511
498
  this.filtering.cancel();
@@ -547,12 +534,12 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
547
534
  get showsOptions() {
548
535
  return this.counted && !this.loading
549
536
  ? this.filter === ''
550
- ? this.options.length > 0
537
+ ? this.entries.length > 0
551
538
  : this.hasMatches
552
539
  : true;
553
540
  }
554
541
  renderStatus(hasContent) {
555
- if (this.loadError !== '') {
542
+ if (this.failed) {
556
543
  return html `<p part="error"><slot name="error">${this.loadError}</slot></p>`;
557
544
  }
558
545
  if (!this.loading && hasContent) {
@@ -571,17 +558,17 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
571
558
  return (this.counted &&
572
559
  !this.loading &&
573
560
  this.filter !== '' &&
574
- this.options.length > 0 &&
561
+ this.entries.length > 0 &&
575
562
  !this.hasMatches);
576
563
  }
564
+ get failed() {
565
+ return Boolean(this.loadError);
566
+ }
577
567
  renderLive(hasContent) {
578
- if (this.loadError !== '') {
579
- return html `<span part="live" role="status">${this.loadError}</span>`;
580
- }
581
- if (this.noResults && this.noResultsText !== undefined) {
582
- return html `<span part="live" role="status">${this.noResultsText}</span>`;
583
- }
584
- return super.renderLive(hasContent);
568
+ const words = this.loadError || (this.noResults ? this.noResultsText : undefined);
569
+ return words === undefined
570
+ ? super.renderLive(hasContent)
571
+ : html `<span part="live" role="status">${words}</span>`;
585
572
  }
586
573
  render() {
587
574
  const shown = this.showsOptions;
@@ -594,7 +581,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
594
581
  <input
595
582
  part="search-field"
596
583
  type="text"
597
- autocomplete=${this.autocomplete || nothing}
584
+ autocomplete="off"
598
585
  spellcheck="false"
599
586
  .value=${this.filter}
600
587
  placeholder=${this.filterLabel}
@@ -617,14 +604,22 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinCollection(Mi
617
604
  aria-labelledby=${this.labeled ? this.labelId : nothing}
618
605
  aria-multiselectable=${this.multiple ? 'true' : nothing}
619
606
  aria-required=${this.required ? 'true' : nothing}
620
- ${''}
621
607
  tabindex=${this.filterable ? nothing : this.isDisabled ? -1 : 0}
622
608
  @scroll=${this.handleScroll}
623
609
  >
624
- <slot @slotchange=${() => this.place()}></slot>
610
+ ${this.data.render() ?? html `<slot @slotchange=${() => this.requestUpdate()}></slot>`}
625
611
  ${this.renderStatus(shown)}
626
612
  </div>
627
- ${this.renderLive(shown)}${this.renderHint()}${this.renderFeedback()}`;
613
+ ${this.renderLive(shown)}${this.renderHint()}${this.renderFeedback()}${this.autocomplete === 'off'
614
+ ? nothing
615
+ : drawAutofill({
616
+ autocomplete: this.autocomplete,
617
+ disabled: this.isDisabled,
618
+ multiple: this.multiple,
619
+ onFill: (value) => this.requestValue(value),
620
+ options: this.childrenKnown ? this.entries : [],
621
+ value: this.value,
622
+ })}`;
628
623
  }
629
624
  };
630
625
  __decorate([
@@ -648,6 +643,12 @@ __decorate([
648
643
  __decorate([
649
644
  property({ attribute: 'filter-label' })
650
645
  ], HelixHeadlessListbox.prototype, "filterLabel", void 0);
646
+ __decorate([
647
+ property({ attribute: false })
648
+ ], HelixHeadlessListbox.prototype, "items", void 0);
649
+ __decorate([
650
+ property({ reflect: true })
651
+ ], HelixHeadlessListbox.prototype, "layout", void 0);
651
652
  __decorate([
652
653
  property({ attribute: 'load-error' })
653
654
  ], HelixHeadlessListbox.prototype, "loadError", void 0);
@@ -667,14 +668,11 @@ __decorate([
667
668
  property({ attribute: 'results-label' })
668
669
  ], HelixHeadlessListbox.prototype, "resultsLabel", void 0);
669
670
  __decorate([
670
- state()
671
- ], HelixHeadlessListbox.prototype, "activeValue", void 0);
671
+ property({ attribute: 'selected-label' })
672
+ ], HelixHeadlessListbox.prototype, "selectedLabel", void 0);
672
673
  __decorate([
673
674
  state()
674
- ], HelixHeadlessListbox.prototype, "hasMatches", void 0);
675
- __decorate([
676
- state()
677
- ], HelixHeadlessListbox.prototype, "counted", void 0);
675
+ ], HelixHeadlessListbox.prototype, "activeValue", void 0);
678
676
  HelixHeadlessListbox = __decorate([
679
677
  customElement('helix-headless-listbox')
680
678
  ], HelixHeadlessListbox);
@@ -12,7 +12,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
12
12
  * `name`, or its `label` if it has no name.
13
13
  *
14
14
  * All the markers in a map share one tab stop. The arrow keys move between
15
- * them in the order they are written.
15
+ * them in the order they are written. Taking away the marker that has focus
16
+ * moves focus to the next one, or to the one before where it was the last.
16
17
  *
17
18
  * @since 0.105.0
18
19
  * @status experimental
@@ -67,8 +67,20 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
67
67
  *
68
68
  * @slot - The element your renderer draws the map in, and the map's markers, routes, areas, positions, and points layers.
69
69
  * @slot attribution - The map credit, which most tile licenses require.
70
+ * @slot face-north-icon - Replaces the compass needle. It inherits `--map-needle-turn`, so `rotate: var(--map-needle-turn)` keeps it pointing north.
70
71
  * @slot footer - Content below the map, such as a legend or a scale.
72
+ * @slot fullscreen-icon - Replaces the glyph inside the fullscreen button.
71
73
  * @slot header - Content above the map, such as a title or a search field.
74
+ * @slot layers-icon - Replaces the glyph inside the layers button.
75
+ * @slot pan-down-icon - Replaces the glyph inside the pan down button.
76
+ * @slot pan-end-icon - Replaces the glyph inside the button that pans toward the end of a line of text.
77
+ * @slot pan-start-icon - Replaces the glyph inside the button that pans toward the start of a line of text.
78
+ * @slot pan-up-icon - Replaces the glyph inside the pan up button.
79
+ * @slot rotate-left-icon - Replaces the glyph inside the rotate left button.
80
+ * @slot rotate-right-icon - Replaces the glyph inside the rotate right button.
81
+ * @slot tilt-icon - Replaces the "3D" inside the 3D button.
82
+ * @slot zoom-in-icon - Replaces the glyph inside the zoom-in button.
83
+ * @slot zoom-out-icon - Replaces the glyph inside the zoom-out button.
72
84
  *
73
85
  * @event {Event} change - The layers menu changed the map type, which `map-type` now holds.
74
86
  * @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.
@@ -122,8 +134,11 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
122
134
  *
123
135
  * @cssstate fullscreen - The map fills the screen.
124
136
  * @cssstate idle - The user has been still long enough for the header and footer to fade.
137
+ * @cssstate layers-open - The layers menu is open.
125
138
  * @cssstate ready - The map has been drawn whole, after `helix-map-ready`.
139
+ * @cssstate rotated - The map is turned away from north, by `bearing`.
126
140
  * @cssstate sharing - The page keeps the plain wheel and one finger, with `gestures="cooperative"` or while a map with `gestures="activate"` waits to be pressed.
141
+ * @cssstate tilted - The map is tilted by `pitch`, so it no longer looks straight down.
127
142
  * @cssstate using - A map with `gestures="activate"` has been pressed and has the gestures.
128
143
  */
129
144
  export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
@@ -323,8 +338,31 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
323
338
  protected readonly keys: KeyboardNavigationController;
324
339
  /** The marker a press or a click came from, where it came from one. */
325
340
  private markerOf;
341
+ /**
342
+ * The marker focus is on, while it is.
343
+ *
344
+ * **Kept so a marker the page takes away hands focus on.** A delivery done
345
+ * or a filter changed removes a marker, and the one a reader was on went
346
+ * with focus still in it: the browser dropped focus to the page, and the
347
+ * reader was put back at its top with nothing said.
348
+ */
349
+ private focused;
326
350
  /** Moves the tab stop to the marker focus arrived at. */
327
351
  private readonly handleFocusIn;
352
+ /**
353
+ * Forgets the marker focus left, unless the page took it away.
354
+ *
355
+ * Focus leaving for nothing is a press on the page or the marker removed,
356
+ * and which one is known once the removal has finished: a marker still in
357
+ * the page was left, and one gone is owed a place for focus to land.
358
+ */
359
+ private readonly handleFocusOut;
360
+ /**
361
+ * Gives focus to the marker that took the stop of one the page took away
362
+ * with focus on it: the next, the last where it was the last, its cluster
363
+ * where it is gathered, and the region where none is left.
364
+ */
365
+ private refocus;
328
366
  /**
329
367
  * Says a marker was pressed, by click, Enter or Space: a place by its name,
330
368
  * and a cluster as a cluster, from the marker it is drawn as.
@@ -376,7 +414,7 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
376
414
  * order they are written, with its picture and name, pressed while it is
377
415
  * shown. A group's tile is pressed while any of its layers is shown.
378
416
  */
379
- private renderLayersMenu;
417
+ protected renderLayersMenu(): TemplateResult | typeof nothing;
380
418
  /**
381
419
  * The map types, as cards with a picture of the map in each: Standard,
382
420
  * Hybrid, and Satellite. One is chosen, so they are radio buttons, and the
@@ -392,7 +430,7 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
392
430
  * arrows around it are the way to move and turn the map without dragging,
393
431
  * which a pointer otherwise needs.
394
432
  */
395
- private renderCompass;
433
+ protected renderCompass(pan: boolean, rotate: boolean): TemplateResult;
396
434
  /**
397
435
  * The controls, in one column in the order they are read: fullscreen, the
398
436
  * layers, 3D, zoom, and the compass with the arrows round it, last, where