@fusion.dev/helix-headless 0.144.0 → 0.145.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 (155) hide show
  1. package/custom-elements.json +8312 -881
  2. package/dist/accordion/item/accordion-item.d.ts +48 -12
  3. package/dist/accordion/item/accordion-item.js +26 -5
  4. package/dist/accordion/root/accordion-root.d.ts +6 -0
  5. package/dist/accordion/root/accordion-root.js +7 -1
  6. package/dist/announcement/root/announcement-root.d.ts +21 -1
  7. package/dist/announcement/root/announcement-root.js +36 -1
  8. package/dist/app-header/root/app-header-root.d.ts +7 -0
  9. package/dist/app-header/root/app-header-root.js +5 -1
  10. package/dist/app-header/sectioning.d.ts +7 -0
  11. package/dist/app-header/sectioning.js +26 -0
  12. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +32 -1
  13. package/dist/breadcrumbs/root/breadcrumbs-root.js +25 -2
  14. package/dist/canvas/root/canvas-root.d.ts +38 -3
  15. package/dist/canvas/root/canvas-root.js +121 -40
  16. package/dist/carousel/root/carousel-root.d.ts +90 -0
  17. package/dist/carousel/root/carousel-root.js +134 -12
  18. package/dist/chat/composer/chat-composer.d.ts +38 -6
  19. package/dist/chat/composer/chat-composer.js +42 -9
  20. package/dist/chat/message/chat-message.d.ts +32 -1
  21. package/dist/chat/message/chat-message.js +25 -9
  22. package/dist/chat/naming.d.ts +22 -0
  23. package/dist/chat/naming.js +12 -0
  24. package/dist/chat/relative-clock.d.ts +39 -0
  25. package/dist/chat/relative-clock.js +31 -0
  26. package/dist/chat/root/chat-root.d.ts +36 -0
  27. package/dist/chat/root/chat-root.js +24 -1
  28. package/dist/code-block/root/code-block-root.d.ts +8 -1
  29. package/dist/code-block/root/code-block-root.js +5 -5
  30. package/dist/copy-button/root/copy-button-root.d.ts +21 -1
  31. package/dist/copy-button/root/copy-button-root.js +2 -2
  32. package/dist/data-table/root/data-table-root.d.ts +150 -2
  33. package/dist/data-table/root/data-table-root.js +303 -29
  34. package/dist/data-table/tree.d.ts +40 -0
  35. package/dist/data-table/tree.js +55 -0
  36. package/dist/description-list/item/description-list-item.d.ts +17 -2
  37. package/dist/description-list/item/description-list-item.js +15 -2
  38. package/dist/description-list/root/description-list-root.d.ts +27 -1
  39. package/dist/description-list/root/description-list-root.js +26 -1
  40. package/dist/drilldown/item/drilldown-item.d.ts +11 -6
  41. package/dist/drilldown/item/drilldown-item.js +8 -5
  42. package/dist/drilldown/root/drilldown-root.d.ts +11 -0
  43. package/dist/drilldown/root/drilldown-root.js +8 -3
  44. package/dist/element-events.generated.js +53 -8
  45. package/dist/filter-input/menu/filter-menu.d.ts +3 -0
  46. package/dist/filter-input/menu/filter-menu.js +8 -4
  47. package/dist/image/root/image-root.js +9 -1
  48. package/dist/input/root/input-root.d.ts +1 -1
  49. package/dist/input/root/input-root.js +2 -2
  50. package/dist/list/item/list-item.d.ts +68 -1
  51. package/dist/list/item/list-item.js +66 -4
  52. package/dist/list/numbering.d.ts +14 -0
  53. package/dist/list/numbering.js +8 -0
  54. package/dist/list/root/list-root.d.ts +106 -9
  55. package/dist/list/root/list-root.js +189 -4
  56. package/dist/list/selection.d.ts +19 -0
  57. package/dist/list/selection.js +21 -0
  58. package/dist/listbox/root/listbox-root.js +1 -2
  59. package/dist/map/root/map-root.d.ts +6 -2
  60. package/dist/map/root/map-root.js +5 -2
  61. package/dist/megamenu/root/megamenu-root.d.ts +21 -0
  62. package/dist/megamenu/root/megamenu-root.js +16 -2
  63. package/dist/menu/index.d.ts +1 -0
  64. package/dist/menu/index.js +1 -0
  65. package/dist/menu/item/menu-item.d.ts +38 -11
  66. package/dist/menu/item/menu-item.js +33 -1
  67. package/dist/menu/root/menu-root.d.ts +25 -1
  68. package/dist/menu/root/menu-root.js +63 -6
  69. package/dist/menu/section/menu-section.d.ts +55 -0
  70. package/dist/menu/section/menu-section.js +60 -0
  71. package/dist/navigation/root/navigation-root.d.ts +16 -1
  72. package/dist/navigation/root/navigation-root.js +27 -1
  73. package/dist/notifications/root/notifications-root.d.ts +18 -0
  74. package/dist/notifications/root/notifications-root.js +17 -1
  75. package/dist/object-inspector/root/object-inspector-root.d.ts +97 -16
  76. package/dist/object-inspector/root/object-inspector-root.js +179 -79
  77. package/dist/object-inspector/rows.d.ts +57 -0
  78. package/dist/object-inspector/rows.js +86 -0
  79. package/dist/outline/levels.d.ts +31 -0
  80. package/dist/outline/levels.js +23 -0
  81. package/dist/outline/root/outline-root.d.ts +68 -3
  82. package/dist/outline/root/outline-root.js +73 -8
  83. package/dist/pagination/size/pagination-size.d.ts +11 -2
  84. package/dist/pagination/size/pagination-size.js +19 -4
  85. package/dist/radio/group/radio-group.d.ts +4 -10
  86. package/dist/radio/group/radio-group.js +5 -10
  87. package/dist/rating/root/rating-root.d.ts +1 -1
  88. package/dist/rating/root/rating-root.js +2 -2
  89. package/dist/scroll-area/root/scroll-area-root.d.ts +113 -0
  90. package/dist/scroll-area/root/scroll-area-root.js +218 -3
  91. package/dist/scroll-area/scrollbar.d.ts +65 -0
  92. package/dist/scroll-area/scrollbar.js +24 -0
  93. package/dist/select/root/select-root.js +1 -1
  94. package/dist/splitter/root/splitter-root.d.ts +13 -0
  95. package/dist/splitter/root/splitter-root.js +20 -2
  96. package/dist/splitter/snap.d.ts +9 -0
  97. package/dist/splitter/snap.js +12 -0
  98. package/dist/steps/reach.d.ts +15 -0
  99. package/dist/steps/reach.js +12 -0
  100. package/dist/steps/root/steps-root.d.ts +30 -0
  101. package/dist/steps/root/steps-root.js +53 -0
  102. package/dist/steps/step/step.d.ts +38 -2
  103. package/dist/steps/step/step.js +28 -6
  104. package/dist/table/column/table-column.d.ts +11 -0
  105. package/dist/table/column/table-column.js +6 -1
  106. package/dist/table/root/table-root.d.ts +101 -0
  107. package/dist/table/root/table-root.js +166 -5
  108. package/dist/table/row/table-row.d.ts +47 -1
  109. package/dist/table/row/table-row.js +39 -2
  110. package/dist/tabs/panel/tab-panel.d.ts +9 -5
  111. package/dist/tabs/panel/tab-panel.js +3 -4
  112. package/dist/tabs/root/tabs-root.d.ts +25 -1
  113. package/dist/tabs/root/tabs-root.js +21 -3
  114. package/dist/tabs/tab/tab.d.ts +36 -4
  115. package/dist/tabs/tab/tab.js +46 -3
  116. package/dist/timeline/group/timeline-group.d.ts +46 -0
  117. package/dist/timeline/group/timeline-group.js +37 -0
  118. package/dist/timeline/index.d.ts +1 -0
  119. package/dist/timeline/index.js +1 -0
  120. package/dist/timeline/root/timeline-root.d.ts +10 -3
  121. package/dist/toast/item/toast-item.d.ts +10 -17
  122. package/dist/toast/item/toast-item.js +15 -36
  123. package/dist/toolbar/root/toolbar-root.d.ts +13 -0
  124. package/dist/toolbar/root/toolbar-root.js +15 -5
  125. package/dist/tree/item/tree-item.d.ts +54 -1
  126. package/dist/tree/item/tree-item.js +89 -20
  127. package/dist/tree/root/tree-root.d.ts +37 -0
  128. package/dist/tree/root/tree-root.js +51 -6
  129. package/dist/tree/selection.d.ts +24 -0
  130. package/dist/tree/selection.js +26 -0
  131. package/dist/video/media-events.d.ts +49 -0
  132. package/dist/video/media-events.js +39 -0
  133. package/dist/video/root/video-root.d.ts +99 -13
  134. package/dist/video/root/video-root.js +84 -47
  135. package/dist/video-playlist/menu/video-playlist-menu.d.ts +3 -0
  136. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  137. package/dist/video-playlist/order.d.ts +41 -0
  138. package/dist/video-playlist/order.js +34 -0
  139. package/dist/video-playlist/root/video-playlist-root.d.ts +70 -2
  140. package/dist/video-playlist/root/video-playlist-root.js +60 -16
  141. package/dist/virtual-list/controls.d.ts +4 -0
  142. package/dist/virtual-list/controls.js +30 -0
  143. package/dist/virtual-list/fill.d.ts +30 -0
  144. package/dist/virtual-list/fill.js +39 -0
  145. package/dist/virtual-list/keys.d.ts +15 -0
  146. package/dist/virtual-list/keys.js +24 -0
  147. package/dist/virtual-list/root/virtual-list-root.d.ts +155 -23
  148. package/dist/virtual-list/root/virtual-list-root.js +175 -34
  149. package/lint.json +1 -1
  150. package/package.json +5 -5
  151. package/types/jsx.d.ts +3778 -801
  152. package/types/svelte.d.ts +2 -0
  153. package/types/vue.d.ts +782 -215
  154. package/dist/listbox/range.d.ts +0 -17
  155. package/dist/listbox/range.js +0 -5
@@ -5,22 +5,23 @@ 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 HelixHeadlessObjectInspector_1;
8
- import { AriaForwardController, CopyController, customElement, elementById, fillTemplate, formatNumbers, HelixElement, inlineStep, scrollItemIntoView, searchableById, VirtualizeController, } from '@fusion.dev/helix-core';
8
+ import { AriaForwardController, CopyController, countLabel, customElement, drawStatus, elementById, fillTemplate, formatNumbers, HelixActivateEvent, HelixBeforeActivateEvent, HelixElement, HelixFilterEvent, HelixFilteredEvent, inlineStep, requestChange, StatusController, scrollItemIntoView, searchableById, VirtualizeController, } from '@fusion.dev/helix-core';
9
9
  import { caretDown, caretRight } from '@fusion.dev/helix-icons';
10
10
  import { html, nothing } from 'lit';
11
11
  import { property, state } from 'lit/decorators.js';
12
12
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
13
13
  import { html as staticHtml } from 'lit/static-html.js';
14
14
  import '../../copy-button/index.js';
15
- const branchy = (value) => value !== null && typeof value === 'object';
16
- const entries = (value) => Array.isArray(value) ? value.map((item, at) => [String(at), item]) : Object.entries(value);
17
- const child = (path, key) => `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
15
+ import { ancestorsOf, entries, flatten, search, seed } from '../rows.js';
18
16
  let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends HelixElement {
19
17
  constructor() {
20
18
  super(...arguments);
21
19
  this.copyLabel = 'Copy {name}';
22
20
  this.copyValueLabel = 'Copy value';
23
21
  this.defaultDepth = 1;
22
+ this.filter = '';
23
+ this.filterable = false;
24
+ this.filterLabel = 'Filter';
24
25
  this.for = '';
25
26
  this.itemLabel = '{count} item';
26
27
  this.itemsLabel = '{count} items';
@@ -28,12 +29,14 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
28
29
  this.keysLabel = '{count} keys';
29
30
  this.label = '';
30
31
  this.noCopy = false;
32
+ this.resultLabel = '{count} result';
33
+ this.resultsLabel = '{count} results';
31
34
  this.treeLabel = 'Value';
32
35
  this.value = undefined;
33
36
  this.windowed = false;
34
37
  this.read = undefined;
35
38
  this.opened = new Set();
36
- this.active = 0;
39
+ this.cursor = 0;
37
40
  this.revealing = false;
38
41
  this.typed = { at: 0, text: '' };
39
42
  this.branches = new Set();
@@ -41,6 +44,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
41
44
  label: true,
42
45
  target: () => this.partNamed('tree'),
43
46
  });
47
+ this.status = new StatusController(this);
44
48
  this.virtualize = new VirtualizeController(this, {
45
49
  container: () => (this.windowed ? this.partNamed('tree') : null),
46
50
  count: () => this.rows.length,
@@ -52,7 +56,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
52
56
  this.copier = new CopyController(this);
53
57
  this.handleKeydown = (event) => {
54
58
  const rows = this.rows;
55
- const row = rows[this.active];
59
+ const row = rows[this.cursor];
56
60
  if (!row) {
57
61
  return;
58
62
  }
@@ -66,10 +70,10 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
66
70
  }
67
71
  switch (event.key) {
68
72
  case 'ArrowDown':
69
- this.move(this.active + 1);
73
+ this.move(this.cursor + 1);
70
74
  break;
71
75
  case 'ArrowUp':
72
- this.move(this.active - 1);
76
+ this.move(this.cursor - 1);
73
77
  break;
74
78
  case 'Home':
75
79
  this.move(0);
@@ -84,14 +88,14 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
84
88
  this.toggle(row, true);
85
89
  }
86
90
  else if (row.open) {
87
- this.move(this.active + 1);
91
+ this.move(this.cursor + 1);
88
92
  }
89
93
  }
90
94
  else if (row.open) {
91
95
  this.toggle(row, false);
92
96
  }
93
97
  else {
94
- const parent = rows.findLastIndex((candidate, at) => at < this.active && candidate.depth === row.depth - 1);
98
+ const parent = rows.findLastIndex((candidate, at) => at < this.cursor && candidate.depth === row.depth - 1);
95
99
  if (parent >= 0) {
96
100
  this.move(parent);
97
101
  }
@@ -114,80 +118,123 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
114
118
  }
115
119
  event.preventDefault();
116
120
  };
121
+ this.handleFilterInput = (event) => {
122
+ const field = event.currentTarget;
123
+ const applied = requestChange({
124
+ apply: (value) => {
125
+ this.filter = value;
126
+ },
127
+ event: (value) => new HelixFilterEvent({ value }),
128
+ host: this,
129
+ next: field.value,
130
+ });
131
+ if (applied) {
132
+ this.dispatchEvent(new HelixFilteredEvent({ value: field.value }));
133
+ }
134
+ else {
135
+ field.value = this.filter;
136
+ }
137
+ };
138
+ this.handleFilterKeydown = (event) => {
139
+ if (event.key === 'ArrowDown') {
140
+ event.preventDefault();
141
+ this.partNamed('tree')?.focus();
142
+ }
143
+ };
117
144
  }
118
145
  static { HelixHeadlessObjectInspector_1 = this; }
146
+ get active() {
147
+ return this.wanted ?? this.rows[this.cursor]?.path ?? '';
148
+ }
149
+ set active(path) {
150
+ const previous = this.active;
151
+ this.wanted = path;
152
+ this.requestUpdate('active', previous);
153
+ }
154
+ get expanded() {
155
+ return [...this.opened];
156
+ }
157
+ set expanded(paths) {
158
+ const previous = this.expanded;
159
+ this.declared = new Set(paths);
160
+ this.opened = this.declared;
161
+ this.requestUpdate('expanded', previous);
162
+ }
119
163
  static { this.ESTIMATE = 24; }
120
164
  get root() {
121
165
  return this.value === undefined ? this.read : this.value;
122
166
  }
123
- flatten() {
124
- const rows = [];
125
- if (this.root === undefined) {
126
- return rows;
127
- }
128
- const visit = (value, path, depth, position, siblings, key) => {
129
- const branch = branchy(value);
130
- const open = branch && this.opened.has(path);
131
- rows.push({
132
- branch,
133
- depth,
134
- open,
135
- path,
136
- position,
137
- siblings,
138
- value,
139
- ...(key === undefined ? {} : { key }),
140
- });
141
- if (open) {
142
- const children = entries(value);
143
- children.forEach(([name, item], at) => {
144
- visit(item, child(path, name), depth + 1, at + 1, children.length, name);
145
- });
146
- }
147
- };
148
- visit(this.root, '', 0, 1, 1);
149
- return rows;
150
- }
151
167
  seed(keep) {
152
- const opened = new Set();
153
- const branches = new Set();
154
- const visit = (value, path, depth) => {
155
- if (!branchy(value)) {
156
- return;
157
- }
158
- branches.add(path);
159
- const known = keep && this.branches.has(path);
160
- if (known ? this.opened.has(path) : depth < this.defaultDepth) {
161
- opened.add(path);
162
- }
163
- for (const [name, item] of entries(value)) {
164
- visit(item, child(path, name), depth + 1);
165
- }
166
- };
167
- visit(this.root, '', 0);
168
+ const { branches, opened } = seed(this.root, this.defaultDepth, keep ? this.branches : undefined, this.opened);
168
169
  this.branches = branches;
169
170
  this.opened = opened;
170
171
  if (!keep) {
171
- this.active = 0;
172
+ this.cursor = 0;
172
173
  }
173
174
  }
175
+ openAround(paths) {
176
+ const opened = new Set(this.opened);
177
+ for (const path of paths) {
178
+ for (const above of ancestorsOf(path)) {
179
+ if (this.branches.has(above)) {
180
+ opened.add(above);
181
+ }
182
+ }
183
+ }
184
+ this.opened = opened;
185
+ }
174
186
  willUpdate(changed) {
175
187
  super.willUpdate(changed);
176
188
  if (this.loadedFor !== this.for && searchableById(this) && !this.hydrating) {
177
189
  this.loadedFor = this.for;
178
190
  this.load();
179
191
  }
180
- const was = this.rows[this.active]?.path;
181
- if (changed.has('value') || changed.has('read') || changed.has('defaultDepth')) {
192
+ const was = this.rows[this.cursor]?.path;
193
+ const written = this.declared;
194
+ this.declared = undefined;
195
+ const fresh = changed.has('value') || changed.has('read');
196
+ if (fresh || changed.has('defaultDepth')) {
182
197
  this.seed(!changed.has('defaultDepth'));
183
198
  }
184
- if (changed.has('value') ||
185
- changed.has('read') ||
186
- changed.has('opened') ||
187
- changed.has('defaultDepth')) {
188
- this.rows = this.flatten();
189
- const found = was === undefined ? -1 : this.rows.findIndex((row) => row.path === was);
190
- this.active = found >= 0 ? found : Math.min(this.active, Math.max(0, this.rows.length - 1));
199
+ if (written) {
200
+ this.opened = written;
201
+ }
202
+ if (fresh || changed.has('filter')) {
203
+ this.found = this.filter.trim() === '' ? undefined : search(this.root, this.filter);
204
+ this.openAround(this.found ?? []);
205
+ }
206
+ const wanted = this.root === undefined ? undefined : this.wanted;
207
+ if (wanted !== undefined) {
208
+ this.wanted = undefined;
209
+ this.openAround([wanted]);
210
+ }
211
+ if (fresh ||
212
+ wanted !== undefined ||
213
+ ['active', 'defaultDepth', 'expanded', 'filter', 'opened'].some((name) => changed.has(name))) {
214
+ this.rows = flatten(this.root, this.opened, this.found);
215
+ this.cursor = this.place(wanted ?? was, changed.has('filter'));
216
+ }
217
+ if (changed.has('filter') && this.hasUpdated) {
218
+ this.tell();
219
+ }
220
+ }
221
+ place(path, filtered) {
222
+ const rows = this.rows;
223
+ const at = (one) => rows.findIndex((row) => row.path === one);
224
+ const own = path === undefined ? -1 : at(path);
225
+ if (own >= 0 || path === undefined) {
226
+ return own >= 0 ? own : Math.min(this.cursor, Math.max(0, rows.length - 1));
227
+ }
228
+ const match = filtered ? rows.findIndex((row) => row.match) : -1;
229
+ const around = ancestorsOf(path)
230
+ .reverse()
231
+ .map(at)
232
+ .find((one) => one >= 0);
233
+ return match >= 0 ? match : (around ?? 0);
234
+ }
235
+ tell() {
236
+ if (this.found) {
237
+ this.status.say(countLabel(this.found.size, this.resultLabel, this.resultsLabel, this.lang));
191
238
  }
192
239
  }
193
240
  firstUpdated(changed) {
@@ -215,7 +262,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
215
262
  }
216
263
  reveal(attempts = 8) {
217
264
  const tree = this.partNamed('tree');
218
- const row = tree?.querySelector(`[data-index="${this.active}"]`);
265
+ const row = tree?.querySelector(`[data-index="${this.cursor}"]`);
219
266
  if (row) {
220
267
  scrollItemIntoView(row);
221
268
  return;
@@ -227,8 +274,8 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
227
274
  return;
228
275
  }
229
276
  const height = first.offsetHeight || HelixHeadlessObjectInspector_1.ESTIMATE;
230
- const from = Number(this.active > Number(last.dataset.index) ? last.dataset.index : first.dataset.index);
231
- tree.scrollTop += (this.active - from) * height;
277
+ const from = Number(this.cursor > Number(last.dataset.index) ? last.dataset.index : first.dataset.index);
278
+ tree.scrollTop += (this.cursor - from) * height;
232
279
  this.virtualize.measure();
233
280
  void this.updateComplete.then(() => this.reveal(attempts - 1));
234
281
  }
@@ -252,9 +299,27 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
252
299
  this.opened = opened;
253
300
  }
254
301
  move(to) {
255
- const last = this.rows.length - 1;
256
- this.active = Math.max(0, Math.min(to, last));
302
+ const at = Math.max(0, Math.min(to, this.rows.length - 1));
303
+ const path = this.rows[at]?.path;
304
+ if (path === undefined) {
305
+ return false;
306
+ }
307
+ if (at !== this.cursor) {
308
+ const moved = requestChange({
309
+ apply: () => {
310
+ this.cursor = at;
311
+ },
312
+ event: (value) => new HelixBeforeActivateEvent({ value }),
313
+ host: this,
314
+ next: path,
315
+ });
316
+ if (!moved) {
317
+ return false;
318
+ }
319
+ this.dispatchEvent(new HelixActivateEvent({ value: path }));
320
+ }
257
321
  this.revealing = true;
322
+ return true;
258
323
  }
259
324
  openSiblings(row) {
260
325
  const parent = row.path.slice(0, row.path.lastIndexOf('/'));
@@ -275,7 +340,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
275
340
  const wanted = text.toLocaleLowerCase();
276
341
  const repeated = [...wanted].every((one) => one === wanted[0]);
277
342
  const rows = this.rows;
278
- const start = wanted.length === 1 || repeated ? this.active + 1 : this.active;
343
+ const start = wanted.length === 1 || repeated ? this.cursor + 1 : this.cursor;
279
344
  for (let step = 0; step < rows.length; step += 1) {
280
345
  const at = (start + step) % rows.length;
281
346
  const key = rows[at]?.key?.toLocaleLowerCase();
@@ -286,8 +351,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
286
351
  }
287
352
  }
288
353
  handleRowClick(index, row) {
289
- this.active = index;
290
- if (row.branch) {
354
+ if (this.move(index) && row.branch) {
291
355
  this.toggle(row);
292
356
  }
293
357
  }
@@ -324,14 +388,15 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
324
388
  return html `<span part="string" dir="ltr">${String(value)}</span>`;
325
389
  }
326
390
  renderRow(row, index) {
327
- const active = index === this.active;
391
+ const active = index === this.cursor;
328
392
  return html `<div
329
- part="row${active ? ' row-active' : ''}"
393
+ part="row${active ? ' row-active' : ''}${row.match ? ' row-match' : ''}"
330
394
  role="treeitem"
331
395
  id=${`row-${index}`}
332
396
  data-index=${index}
333
397
  aria-level=${row.depth + 1}
334
398
  aria-posinset=${row.position}
399
+ aria-selected="false"
335
400
  aria-setsize=${row.siblings}
336
401
  aria-expanded=${row.branch ? String(row.open) : nothing}
337
402
  style=${`--object-inspector-level: ${row.depth}`}
@@ -368,15 +433,28 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
368
433
  ? this.virtualize.window
369
434
  : { after: 0, before: 0, first: 0, last: rows.length };
370
435
  const shown = rows.slice(first, last);
371
- return html `${this.label ? html `<div part="header"><span part="label">${this.label}</span></div>` : nothing}
436
+ return html `${this.label ? html `<div part="header"><span part="label">${this.label}</span></div>` : nothing}${this.filterable
437
+ ? html `<input
438
+ part="search-field"
439
+ type="search"
440
+ spellcheck="false"
441
+ .value=${this.filter}
442
+ placeholder=${this.filterLabel}
443
+ aria-label=${this.filterLabel}
444
+ aria-controls="tree"
445
+ @input=${this.handleFilterInput}
446
+ @keydown=${this.handleFilterKeydown}
447
+ />`
448
+ : nothing}
372
449
  <div part="frame">
373
450
  <div
374
451
  part="tree"
452
+ id="tree"
375
453
  role="tree"
376
454
  tabindex="0"
377
455
  ?data-scroller=${this.windowed}
378
456
  aria-label=${this.label || this.treeLabel}
379
- aria-activedescendant=${rows.length > 0 ? `row-${this.active}` : nothing}
457
+ aria-activedescendant=${rows.length > 0 ? `row-${this.cursor}` : nothing}
380
458
  @keydown=${this.handleKeydown}
381
459
  @scroll=${() => this.virtualize.measure()}
382
460
  >
@@ -384,10 +462,14 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
384
462
  ${shown.map((row, at) => this.renderRow(row, first + at))}
385
463
  ${this.windowed ? html `<div part="after" role="presentation" style="block-size: ${after}px"></div>` : nothing}
386
464
  </div>
387
- ${this.noCopy ? nothing : this.renderCopyButton(rows[this.active])}
388
- </div>`;
465
+ ${this.noCopy ? nothing : this.renderCopyButton(rows[this.cursor])}
466
+ </div>
467
+ ${drawStatus(this.status)}`;
389
468
  }
390
469
  };
470
+ __decorate([
471
+ property()
472
+ ], HelixHeadlessObjectInspector.prototype, "active", null);
391
473
  __decorate([
392
474
  property({ attribute: 'copy-label' })
393
475
  ], HelixHeadlessObjectInspector.prototype, "copyLabel", void 0);
@@ -397,6 +479,18 @@ __decorate([
397
479
  __decorate([
398
480
  property({ attribute: 'default-depth', type: Number })
399
481
  ], HelixHeadlessObjectInspector.prototype, "defaultDepth", void 0);
482
+ __decorate([
483
+ property({ attribute: false })
484
+ ], HelixHeadlessObjectInspector.prototype, "expanded", null);
485
+ __decorate([
486
+ property()
487
+ ], HelixHeadlessObjectInspector.prototype, "filter", void 0);
488
+ __decorate([
489
+ property({ type: Boolean })
490
+ ], HelixHeadlessObjectInspector.prototype, "filterable", void 0);
491
+ __decorate([
492
+ property({ attribute: 'filter-label' })
493
+ ], HelixHeadlessObjectInspector.prototype, "filterLabel", void 0);
400
494
  __decorate([
401
495
  property()
402
496
  ], HelixHeadlessObjectInspector.prototype, "for", void 0);
@@ -418,6 +512,12 @@ __decorate([
418
512
  __decorate([
419
513
  property({ attribute: 'no-copy', type: Boolean })
420
514
  ], HelixHeadlessObjectInspector.prototype, "noCopy", void 0);
515
+ __decorate([
516
+ property({ attribute: 'result-label' })
517
+ ], HelixHeadlessObjectInspector.prototype, "resultLabel", void 0);
518
+ __decorate([
519
+ property({ attribute: 'results-label' })
520
+ ], HelixHeadlessObjectInspector.prototype, "resultsLabel", void 0);
421
521
  __decorate([
422
522
  property({ attribute: 'tree-label' })
423
523
  ], HelixHeadlessObjectInspector.prototype, "treeLabel", void 0);
@@ -435,7 +535,7 @@ __decorate([
435
535
  ], HelixHeadlessObjectInspector.prototype, "opened", void 0);
436
536
  __decorate([
437
537
  state()
438
- ], HelixHeadlessObjectInspector.prototype, "active", void 0);
538
+ ], HelixHeadlessObjectInspector.prototype, "cursor", void 0);
439
539
  HelixHeadlessObjectInspector = HelixHeadlessObjectInspector_1 = __decorate([
440
540
  customElement('helix-headless-object-inspector')
441
541
  ], HelixHeadlessObjectInspector);
@@ -0,0 +1,57 @@
1
+ /** One visible line of the tree: a value, where it sits, and whether it is open. */
2
+ export interface Row {
3
+ branch: boolean;
4
+ depth: number;
5
+ /** The property name or array index this value is under; absent for the root. */
6
+ key?: string;
7
+ /** Whether its key or its value holds what the tree is filtered by. */
8
+ match: boolean;
9
+ open: boolean;
10
+ path: string;
11
+ position: number;
12
+ siblings: number;
13
+ value: unknown;
14
+ }
15
+ /** Whether a value has entries of its own, an object or an array. */
16
+ export declare const branchy: (value: unknown) => value is object;
17
+ /** A branch's entries, an array's under its indexes. */
18
+ export declare const entries: (value: object) => [string, unknown][];
19
+ /** A child's path under its parent's, escaped the way a JSON Pointer escapes one. */
20
+ export declare const child: (path: string, key: string) => string;
21
+ /**
22
+ * Every branch a path sits inside, from the root down, and not the path itself.
23
+ *
24
+ * The root's path is the empty string, so every other path has it first. A
25
+ * key's own `/` is escaped as `~1`, so cutting at each `/` cuts between keys.
26
+ */
27
+ export declare const ancestorsOf: (path: string) => string[];
28
+ /**
29
+ * The paths of every entry whose key or value holds `query`, ignoring case,
30
+ * across the whole value and not only the open branches.
31
+ *
32
+ * A branch is matched by its key alone: its brackets and its count are drawn,
33
+ * and they are not what a reader is looking for.
34
+ */
35
+ export declare const search: (root: unknown, query: string) => Set<string>;
36
+ /**
37
+ * Every visible row, in order, for a value with `opened` branches open.
38
+ *
39
+ * With `found`, the paths a filter matched, a branch shows only the entries
40
+ * that matched or lead to one, and a matched entry shows everything under it.
41
+ * Position and set size count the entries shown, which is the set a reader
42
+ * hears.
43
+ */
44
+ export declare const flatten: (root: unknown, opened: Set<string>, found?: Set<string>) => Row[];
45
+ /**
46
+ * Which branches are open for a value, by path.
47
+ *
48
+ * Every branch shallower than `depth` opens. A branch in `known`, the
49
+ * branches of the last value, keeps whether it was in `opened`, so a new value
50
+ * keeps the branches a reader opened or closed and only a new one takes the
51
+ * default. Returns the open branches and every branch, for the next value to
52
+ * be told apart from this one.
53
+ */
54
+ export declare const seed: (root: unknown, depth: number, known?: Set<string>, opened?: Set<string>) => {
55
+ branches: Set<string>;
56
+ opened: Set<string>;
57
+ };
@@ -0,0 +1,86 @@
1
+ export const branchy = (value) => value !== null && typeof value === 'object';
2
+ export const entries = (value) => Array.isArray(value) ? value.map((item, at) => [String(at), item]) : Object.entries(value);
3
+ export const child = (path, key) => `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
4
+ export const ancestorsOf = (path) => {
5
+ const cuts = [];
6
+ for (let at = path.indexOf('/'); at !== -1; at = path.indexOf('/', at + 1)) {
7
+ cuts.push(path.slice(0, at));
8
+ }
9
+ return cuts;
10
+ };
11
+ const words = (value) => (branchy(value) ? '' : String(value));
12
+ export const search = (root, query) => {
13
+ const found = new Set();
14
+ const wanted = query.trim().toLocaleLowerCase();
15
+ if (wanted === '' || root === undefined) {
16
+ return found;
17
+ }
18
+ const visit = (value, path, key) => {
19
+ if (key?.toLocaleLowerCase().includes(wanted) ||
20
+ words(value).toLocaleLowerCase().includes(wanted)) {
21
+ found.add(path);
22
+ }
23
+ if (branchy(value)) {
24
+ for (const [name, item] of entries(value)) {
25
+ visit(item, child(path, name), name);
26
+ }
27
+ }
28
+ };
29
+ visit(root, '');
30
+ return found;
31
+ };
32
+ export const flatten = (root, opened, found) => {
33
+ const rows = [];
34
+ if (root === undefined) {
35
+ return rows;
36
+ }
37
+ const trail = new Set([...(found ?? [])].flatMap(ancestorsOf));
38
+ const visit = (value, path, depth, position, siblings, under, key) => {
39
+ const branch = branchy(value);
40
+ const open = branch && opened.has(path);
41
+ const match = found?.has(path) === true;
42
+ rows.push({
43
+ branch,
44
+ depth,
45
+ match,
46
+ open,
47
+ path,
48
+ position,
49
+ siblings,
50
+ value,
51
+ ...(key === undefined ? {} : { key }),
52
+ });
53
+ if (!open) {
54
+ return;
55
+ }
56
+ const inside = under || match || !found;
57
+ const children = entries(value)
58
+ .map(([name, item]) => [name, item, child(path, name)])
59
+ .filter(([, , at]) => inside || found?.has(at) || trail.has(at));
60
+ children.forEach(([name, item, at], index) => {
61
+ visit(item, at, depth + 1, index + 1, children.length, inside && !!found, name);
62
+ });
63
+ };
64
+ if (!found || found.has('') || trail.has('')) {
65
+ visit(root, '', 0, 1, 1, false);
66
+ }
67
+ return rows;
68
+ };
69
+ export const seed = (root, depth, known = new Set(), opened = new Set()) => {
70
+ const open = new Set();
71
+ const branches = new Set();
72
+ const visit = (value, path, level) => {
73
+ if (!branchy(value)) {
74
+ return;
75
+ }
76
+ branches.add(path);
77
+ if (known.has(path) ? opened.has(path) : level < depth) {
78
+ open.add(path);
79
+ }
80
+ for (const [name, item] of entries(value)) {
81
+ visit(item, child(path, name), level + 1);
82
+ }
83
+ };
84
+ visit(root, '', 0);
85
+ return { branches, opened: open };
86
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The levels of a flat run of links, as the nested lists they describe.
3
+ *
4
+ * A contents list written flat, `<a data-level="2">`, says its depth in an
5
+ * attribute that only a stylesheet reads. The outline turns the run into lists
6
+ * inside list items in its shadow root, so a screen reader hears the levels a
7
+ * nested `<ul>` would have given it, with the links left where they are.
8
+ */
9
+ /** One link in the tree: its place in the run, and the links nested under it. */
10
+ export interface Branch {
11
+ /** The link's position in the flat run, counted from zero. */
12
+ index: number;
13
+ /** The links one level or more deeper that follow it, before the next at its level. */
14
+ children: Branch[];
15
+ }
16
+ /**
17
+ * The level a link's `data-level` asks for, a whole number from 1.
18
+ *
19
+ * Absent, empty, or not a number is the top level, which is how the outline
20
+ * has always read a link without one.
21
+ */
22
+ export declare const levelOf: (value: string | null) => number;
23
+ /**
24
+ * Nests a run of levels.
25
+ *
26
+ * Each link goes under the nearest link before it with a lower level, and to
27
+ * the top where there is none. A jump of two levels nests one list deeper and
28
+ * not two, since a list item with no link of its own would be read as an empty
29
+ * entry. A run that starts deeper than 1 starts at the top all the same.
30
+ */
31
+ export declare const nest: (levels: readonly number[]) => Branch[];
@@ -0,0 +1,23 @@
1
+ export const levelOf = (value) => {
2
+ const level = Number.parseInt(value ?? '', 10);
3
+ return Number.isFinite(level) && level >= 1 ? level : 1;
4
+ };
5
+ export const nest = (levels) => {
6
+ const top = [];
7
+ const open = [];
8
+ levels.forEach((level, index) => {
9
+ const branch = { children: [], index };
10
+ while (open.length > 0 && (open.at(-1)?.level ?? 0) >= level) {
11
+ open.pop();
12
+ }
13
+ const parent = open.at(-1);
14
+ if (parent) {
15
+ parent.branch.children.push(branch);
16
+ }
17
+ else {
18
+ top.push(branch);
19
+ }
20
+ open.push({ branch, level });
21
+ });
22
+ return top;
23
+ };