@ponchia/ui 0.6.7 → 0.6.9

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 (111) hide show
  1. package/CHANGELOG.md +129 -4
  2. package/README.md +4 -4
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +26 -9
  5. package/behaviors/carousel.d.ts.map +1 -1
  6. package/behaviors/carousel.js +145 -49
  7. package/behaviors/combobox.d.ts.map +1 -1
  8. package/behaviors/combobox.js +220 -92
  9. package/behaviors/command.d.ts.map +1 -1
  10. package/behaviors/command.js +74 -14
  11. package/behaviors/connectors.d.ts.map +1 -1
  12. package/behaviors/connectors.js +131 -32
  13. package/behaviors/crosshair.d.ts.map +1 -1
  14. package/behaviors/crosshair.js +47 -1
  15. package/behaviors/dialog.d.ts.map +1 -1
  16. package/behaviors/dialog.js +24 -9
  17. package/behaviors/disclosure.d.ts.map +1 -1
  18. package/behaviors/disclosure.js +33 -3
  19. package/behaviors/dismissible.d.ts.map +1 -1
  20. package/behaviors/dismissible.js +3 -2
  21. package/behaviors/forms.d.ts.map +1 -1
  22. package/behaviors/forms.js +211 -140
  23. package/behaviors/glyph.d.ts.map +1 -1
  24. package/behaviors/glyph.js +172 -132
  25. package/behaviors/inert.d.ts +1 -1
  26. package/behaviors/inert.d.ts.map +1 -1
  27. package/behaviors/inert.js +4 -3
  28. package/behaviors/internal.d.ts.map +1 -1
  29. package/behaviors/internal.js +4 -3
  30. package/behaviors/legend.d.ts +0 -5
  31. package/behaviors/legend.d.ts.map +1 -1
  32. package/behaviors/legend.js +45 -13
  33. package/behaviors/menu.d.ts.map +1 -1
  34. package/behaviors/menu.js +13 -8
  35. package/behaviors/modal.d.ts.map +1 -1
  36. package/behaviors/modal.js +77 -19
  37. package/behaviors/popover.d.ts +4 -3
  38. package/behaviors/popover.d.ts.map +1 -1
  39. package/behaviors/popover.js +94 -14
  40. package/behaviors/sources.d.ts.map +1 -1
  41. package/behaviors/sources.js +14 -2
  42. package/behaviors/splitter.d.ts.map +1 -1
  43. package/behaviors/splitter.js +149 -110
  44. package/behaviors/spotlight.d.ts.map +1 -1
  45. package/behaviors/spotlight.js +28 -2
  46. package/behaviors/table.d.ts +1 -1
  47. package/behaviors/table.d.ts.map +1 -1
  48. package/behaviors/table.js +108 -17
  49. package/behaviors/tabs.d.ts.map +1 -1
  50. package/behaviors/tabs.js +84 -20
  51. package/behaviors/theme.d.ts.map +1 -1
  52. package/behaviors/theme.js +25 -5
  53. package/behaviors/toast.js +5 -5
  54. package/classes/index.d.ts +15 -2
  55. package/classes/index.js +48 -35
  56. package/connectors/index.d.ts +41 -8
  57. package/connectors/index.d.ts.map +1 -1
  58. package/connectors/index.js +74 -19
  59. package/css/annotations.css +12 -0
  60. package/css/app.css +3 -4
  61. package/css/base.css +1 -1
  62. package/css/content.css +3 -3
  63. package/css/crosshair.css +27 -2
  64. package/css/disclosure.css +3 -3
  65. package/css/dots.css +4 -4
  66. package/css/feedback.css +8 -37
  67. package/css/forms.css +9 -12
  68. package/css/legend.css +1 -1
  69. package/css/marks.css +1 -1
  70. package/css/motion.css +6 -6
  71. package/css/navigation.css +12 -0
  72. package/css/overlay.css +5 -7
  73. package/css/primitives.css +14 -16
  74. package/css/sidenote.css +2 -2
  75. package/css/table.css +2 -2
  76. package/css/tokens.css +16 -0
  77. package/dist/bronto.css +1 -1
  78. package/dist/css/analytical.css +1 -1
  79. package/dist/css/annotations.css +1 -1
  80. package/dist/css/crosshair.css +1 -1
  81. package/dist/css/feedback.css +1 -1
  82. package/dist/css/navigation.css +1 -1
  83. package/dist/css/report-kit.css +1 -1
  84. package/dist/css/tokens.css +1 -1
  85. package/docs/adr/0001-color-system.md +3 -2
  86. package/docs/annotations.md +21 -1
  87. package/docs/architecture.md +74 -13
  88. package/docs/command.md +4 -1
  89. package/docs/connectors.md +16 -0
  90. package/docs/crosshair.md +1 -1
  91. package/docs/dots.md +4 -1
  92. package/docs/glyphs.md +11 -0
  93. package/docs/interop/react-flow.md +89 -0
  94. package/docs/migrations/0.2-to-0.3.md +1 -1
  95. package/docs/package-contract.md +7 -5
  96. package/docs/reporting.md +23 -12
  97. package/docs/stability.md +85 -9
  98. package/docs/theming.md +2 -2
  99. package/docs/usage.md +16 -2
  100. package/docs/vega.md +4 -4
  101. package/glyphs/glyphs.js +43 -33
  102. package/llms.txt +19 -8
  103. package/package.json +23 -4
  104. package/schemas/report-claims.v1.schema.json +1 -1
  105. package/svelte/index.d.ts +71 -45
  106. package/svelte/index.d.ts.map +1 -1
  107. package/svelte/index.js +29 -2
  108. package/tokens/index.js +2 -2
  109. package/vue/index.d.ts +42 -5
  110. package/vue/index.d.ts.map +1 -1
  111. package/vue/index.js +32 -1
@@ -7,8 +7,128 @@ import {
7
7
  scrollIntoViewSafe,
8
8
  wrapIndex,
9
9
  collectHosts,
10
+ closestSafe,
10
11
  } from './internal.js';
11
12
 
13
+ const COMBOBOX_OPTION_SELECTOR = '[role="option"], .ui-combobox__option';
14
+
15
+ const snapshotAttrs = (el, names) => {
16
+ const out = {};
17
+ for (const name of names) {
18
+ out[name] = {
19
+ had: el.hasAttribute(name),
20
+ value: el.getAttribute(name),
21
+ };
22
+ }
23
+ return out;
24
+ };
25
+
26
+ const restoreAttrs = (el, attrs) => {
27
+ for (const [name, attr] of Object.entries(attrs)) {
28
+ if (attr.had) el.setAttribute(name, attr.value);
29
+ else el.removeAttribute(name);
30
+ }
31
+ };
32
+
33
+ const inputLabel = (input) =>
34
+ input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
35
+
36
+ const inputHasAccessibleName = (input) =>
37
+ input.hasAttribute('aria-label') ||
38
+ input.hasAttribute('aria-labelledby') ||
39
+ !!input.labels?.length ||
40
+ input.hasAttribute('title');
41
+
42
+ function mirrorListboxLabel(input, list) {
43
+ if (list.hasAttribute('aria-label') || list.hasAttribute('aria-labelledby')) return;
44
+ const name = inputLabel(input);
45
+ if (name) list.setAttribute('aria-label', name);
46
+ }
47
+
48
+ function warnNamelessCombobox(input) {
49
+ if (inputHasAccessibleName(input) || typeof console === 'undefined') return;
50
+ console.warn(
51
+ '[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
52
+ );
53
+ }
54
+
55
+ function prepareEmptyState(empty) {
56
+ if (!empty) return;
57
+ empty.hidden = true;
58
+ if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
59
+ }
60
+
61
+ function liveOptionObserver(box, list, relist) {
62
+ if (!box.hasAttribute('data-bronto-combobox-live')) return null;
63
+ if (typeof MutationObserver !== 'function') return null;
64
+ const observer = new MutationObserver(relist);
65
+ observer.observe(list, { childList: true, subtree: true });
66
+ return observer;
67
+ }
68
+
69
+ function bindComboboxLifecycle({
70
+ box,
71
+ input,
72
+ list,
73
+ empty,
74
+ rememberState,
75
+ restoreState,
76
+ assignListId,
77
+ syncOptions,
78
+ close,
79
+ relist,
80
+ onInput,
81
+ onKey,
82
+ onOptionClick,
83
+ onDocClick,
84
+ resetActive,
85
+ }) {
86
+ const state = rememberState();
87
+ const listId = assignListId();
88
+ syncOptions();
89
+ list.setAttribute('role', 'listbox');
90
+ // Give the listbox its own accessible name (a bare role=listbox is unnamed
91
+ // to a screen reader) by mirroring the input's real name.
92
+ // The placeholder is deliberately NOT in this chain: the input warning below
93
+ // already rejects a placeholder as an inadequate name, so papering the
94
+ // listbox over with it would contradict that. If there is no real name, the
95
+ // listbox stays unnamed and the warning is the signal.
96
+ mirrorListboxLabel(input, list);
97
+ // A `role="combobox"` with no accessible name is a silent AT failure. A
98
+ // placeholder is not a robust name (it can vanish and is ignored by some
99
+ // AT), so warn unless there is a real label/aria-label/aria-labelledby/title
100
+ // We cannot invent a good name, hence a dev-time warning, not a guess.
101
+ warnNamelessCombobox(input);
102
+ input.setAttribute('role', 'combobox');
103
+ input.setAttribute('aria-controls', listId);
104
+ input.setAttribute('aria-autocomplete', 'list');
105
+ input.setAttribute('aria-expanded', 'false');
106
+ input.setAttribute('autocomplete', 'off');
107
+ // Hide the empty-state at rest: it must only appear once a filter yields no
108
+ // matches, never on an idle combobox. Without this an author who omits
109
+ // `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
110
+ // before the user has typed anything. Make it a status live region so its
111
+ // appearance is announced.
112
+ prepareEmptyState(empty);
113
+ close();
114
+ input.addEventListener('input', onInput);
115
+ input.addEventListener('keydown', onKey);
116
+ list.addEventListener('click', onOptionClick);
117
+ document.addEventListener('click', onDocClick);
118
+ // Opt-in: keep options in sync with a list mutated after init (async /
119
+ // remote results). Off by default so the common static case stays free.
120
+ const observer = liveOptionObserver(box, list, relist);
121
+ return () => {
122
+ observer?.disconnect();
123
+ input.removeEventListener('input', onInput);
124
+ input.removeEventListener('keydown', onKey);
125
+ list.removeEventListener('click', onOptionClick);
126
+ document.removeEventListener('click', onDocClick);
127
+ restoreState(state);
128
+ resetActive();
129
+ };
130
+ }
131
+
12
132
  /**
13
133
  * Editable combobox with a filtered listbox popup, implementing the
14
134
  * WAI-ARIA APG combobox pattern (the widget the framework most lacked
@@ -59,59 +179,69 @@ export function initCombobox({ root } = {}) {
59
179
  const list = box.querySelector('[role="listbox"], .ui-combobox__list');
60
180
  if (!input || !list) continue;
61
181
  const empty = box.querySelector('.ui-combobox__empty');
62
- const listId = list.id || (list.id = `bronto-cb-list-${nextFieldUid()}`);
182
+ const optionStates = new WeakMap();
183
+ let listId = '';
184
+
185
+ const rememberOptionState = (option) => {
186
+ if (optionStates.has(option)) return;
187
+ optionStates.set(option, {
188
+ hidden: option.hidden,
189
+ active: option.classList.contains('is-active'),
190
+ attrs: snapshotAttrs(option, ['id', 'role', 'aria-selected']),
191
+ });
192
+ };
193
+
194
+ const rememberState = () => ({
195
+ input: snapshotAttrs(input, [
196
+ 'role',
197
+ 'aria-controls',
198
+ 'aria-autocomplete',
199
+ 'aria-expanded',
200
+ 'aria-activedescendant',
201
+ 'autocomplete',
202
+ ]),
203
+ list: {
204
+ hidden: list.hidden,
205
+ attrs: snapshotAttrs(list, ['id', 'role', 'aria-label']),
206
+ },
207
+ empty: empty
208
+ ? {
209
+ hidden: empty.hidden,
210
+ attrs: snapshotAttrs(empty, ['role']),
211
+ }
212
+ : null,
213
+ options: optionStates,
214
+ });
215
+
216
+ const restoreState = (state) => {
217
+ restoreAttrs(input, state.input);
218
+ list.hidden = state.list.hidden;
219
+ restoreAttrs(list, state.list.attrs);
220
+ if (empty && state.empty) {
221
+ empty.hidden = state.empty.hidden;
222
+ restoreAttrs(empty, state.empty.attrs);
223
+ }
224
+ for (const option of options) {
225
+ const optionState = state.options.get(option);
226
+ if (!optionState) continue;
227
+ option.hidden = optionState.hidden;
228
+ option.classList.toggle('is-active', optionState.active);
229
+ restoreAttrs(option, optionState.attrs);
230
+ }
231
+ };
232
+
63
233
  // Re-readable so the opt-in MutationObserver (`data-bronto-combobox-live`)
64
234
  // can pick up async/replaced option nodes without a full re-init. `visible`,
65
235
  // `filter`, `move`, etc. close over this binding, so reassigning it is enough.
66
236
  let options = [];
67
237
  const syncOptions = () => {
68
- options = [...list.querySelectorAll('[role="option"], .ui-combobox__option')];
238
+ options = [...list.querySelectorAll(COMBOBOX_OPTION_SELECTOR)];
69
239
  options.forEach((o, i) => {
240
+ rememberOptionState(o);
70
241
  if (!o.id) o.id = `${listId}-opt-${i}`;
71
242
  o.setAttribute('role', 'option');
72
243
  });
73
244
  };
74
- syncOptions();
75
- list.setAttribute('role', 'listbox');
76
- // Give the listbox its own accessible name (a bare role=listbox is unnamed
77
- // to a screen reader) by mirroring the input's REAL name. (a11y review C30.)
78
- // The placeholder is deliberately NOT in this chain: the input warning below
79
- // already rejects a placeholder as an inadequate name, so papering the
80
- // listbox over with it would contradict that — if there's no real name the
81
- // listbox stays unnamed and the warning is the signal. (component audit C28.)
82
- if (!list.hasAttribute('aria-label') && !list.hasAttribute('aria-labelledby')) {
83
- const name = input.getAttribute('aria-label') || input.labels?.[0]?.textContent?.trim();
84
- if (name) list.setAttribute('aria-label', name);
85
- }
86
- // A `role="combobox"` with no accessible name is a silent AT failure. A
87
- // placeholder is not a robust name (it can vanish and is ignored by some
88
- // AT), so warn unless there is a real label/aria-label/aria-labelledby/title
89
- // (C7). We can't invent a good name, hence a dev-time warning, not a guess.
90
- const inputNamed =
91
- input.hasAttribute('aria-label') ||
92
- input.hasAttribute('aria-labelledby') ||
93
- !!input.labels?.length ||
94
- input.hasAttribute('title');
95
- if (!inputNamed && typeof console !== 'undefined') {
96
- console.warn(
97
- '[bronto] initCombobox(): the combobox input has no accessible name — add a <label>, aria-label, or aria-labelledby (a placeholder is not enough).',
98
- );
99
- }
100
- input.setAttribute('role', 'combobox');
101
- input.setAttribute('aria-controls', listId);
102
- input.setAttribute('aria-autocomplete', 'list');
103
- input.setAttribute('aria-expanded', 'false');
104
- input.setAttribute('autocomplete', 'off');
105
- list.hidden = true;
106
- // Hide the empty-state at rest: it must only appear once a filter yields no
107
- // matches, never on an idle combobox. Without this an author who omits
108
- // `hidden` on `.ui-combobox__empty` ships a box that reads "No matches"
109
- // before the user has typed anything. (component audit C10.) Make it a
110
- // status live region so its appearance is announced. (component audit C38.)
111
- if (empty) {
112
- empty.hidden = true;
113
- if (!empty.hasAttribute('role')) empty.setAttribute('role', 'status');
114
- }
115
245
 
116
246
  let active = -1;
117
247
  const visible = () => options.filter((o) => !o.hidden);
@@ -161,7 +291,6 @@ export function initCombobox({ root } = {}) {
161
291
  // Show the human LABEL in the input; emit the `data-value` CODE in the
162
292
  // event. The natural pattern is code in `data-value`, label in the text —
163
293
  // putting the code in the visible input silently shows the user a raw code.
164
- // (component audit C10.)
165
294
  const label = opt.textContent.trim();
166
295
  const value = opt.dataset.value ?? label;
167
296
  input.value = label;
@@ -214,62 +343,61 @@ export function initCombobox({ root } = {}) {
214
343
 
215
344
  const onInput = () => filter();
216
345
  const onKey = (e) => {
217
- switch (e.key) {
218
- case 'ArrowDown':
219
- e.preventDefault();
220
- list.hidden ? filter() : move(1);
221
- break;
222
- case 'ArrowUp':
223
- e.preventDefault();
224
- move(-1);
225
- break;
226
- case 'Home':
227
- if (activateEdge('first')) e.preventDefault();
228
- break;
229
- case 'End':
230
- if (activateEdge('last')) e.preventDefault();
231
- break;
232
- case 'Enter':
233
- if (selectActive()) e.preventDefault();
234
- break;
235
- case 'Escape':
236
- if (closeIfOpen()) e.preventDefault();
237
- break;
238
- case 'Tab':
239
- close();
240
- break;
241
- default:
242
- break;
243
- }
346
+ const handled = keyHandlers[e.key]?.();
347
+ if (handled) e.preventDefault();
348
+ };
349
+ const keyHandlers = {
350
+ ArrowDown: () => {
351
+ if (list.hidden) filter();
352
+ else move(1);
353
+ return true;
354
+ },
355
+ ArrowUp: () => {
356
+ move(-1);
357
+ return true;
358
+ },
359
+ Home: () => activateEdge('first'),
360
+ End: () => activateEdge('last'),
361
+ Enter: () => selectActive(),
362
+ Escape: () => closeIfOpen(),
363
+ Tab: () => {
364
+ close();
365
+ return false;
366
+ },
244
367
  };
245
368
  const onOptionClick = (e) => {
246
- const opt = e.target.closest('[role="option"], .ui-combobox__option');
369
+ const opt = closestSafe(e.target, COMBOBOX_OPTION_SELECTOR);
247
370
  if (opt) select(opt);
248
371
  };
249
372
  const onDocClick = (e) => {
250
373
  if (!box.contains(e.target)) close();
251
374
  };
375
+ const assignListId = () => {
376
+ listId = list.id || (list.id = `bronto-cb-list-${nextFieldUid()}`);
377
+ return listId;
378
+ };
252
379
 
253
- const bound = bindOnce(box, 'combobox', () => {
254
- input.addEventListener('input', onInput);
255
- input.addEventListener('keydown', onKey);
256
- list.addEventListener('click', onOptionClick);
257
- document.addEventListener('click', onDocClick);
258
- // Opt-in: keep options in sync with a list mutated after init (async /
259
- // remote results). Off by default so the common static case stays free.
260
- const observer =
261
- box.hasAttribute('data-bronto-combobox-live') && typeof MutationObserver === 'function'
262
- ? new MutationObserver(relist)
263
- : null;
264
- observer?.observe(list, { childList: true, subtree: true });
265
- return () => {
266
- observer?.disconnect();
267
- input.removeEventListener('input', onInput);
268
- input.removeEventListener('keydown', onKey);
269
- list.removeEventListener('click', onOptionClick);
270
- document.removeEventListener('click', onDocClick);
271
- };
272
- });
380
+ const bound = bindOnce(box, 'combobox', () =>
381
+ bindComboboxLifecycle({
382
+ box,
383
+ input,
384
+ list,
385
+ empty,
386
+ rememberState,
387
+ restoreState,
388
+ assignListId,
389
+ syncOptions,
390
+ close,
391
+ relist,
392
+ onInput,
393
+ onKey,
394
+ onOptionClick,
395
+ onDocClick,
396
+ resetActive: () => {
397
+ active = -1;
398
+ },
399
+ }),
400
+ );
273
401
  cleanups.push(bound);
274
402
  }
275
403
 
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"AAWA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+J3C;;;;;WAzLa,MAAM;;;;WACN,MAAM"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["command.js"],"names":[],"mappings":"AAYA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,uCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA0N3C;;;;;WApPa,MAAM;;;;WACN,MAAM"}
@@ -7,6 +7,7 @@ import {
7
7
  collectHosts,
8
8
  scrollIntoViewSafe,
9
9
  wrapIndex,
10
+ closestSafe,
10
11
  } from './internal.js';
11
12
 
12
13
  /**
@@ -46,6 +47,24 @@ export function initCommand({ root } = {}) {
46
47
  const palettes = collectHosts(host, '[data-bronto-command]');
47
48
  const cleanups = [];
48
49
 
50
+ const snapshotAttrs = (el, names) => {
51
+ const out = {};
52
+ for (const name of names) {
53
+ out[name] = {
54
+ had: el.hasAttribute(name),
55
+ value: el.getAttribute(name),
56
+ };
57
+ }
58
+ return out;
59
+ };
60
+
61
+ const restoreAttrs = (el, attrs) => {
62
+ for (const [name, attr] of Object.entries(attrs)) {
63
+ if (attr.had) el.setAttribute(name, attr.value);
64
+ else el.removeAttribute(name);
65
+ }
66
+ };
67
+
49
68
  for (const box of palettes) {
50
69
  const input = box.querySelector('.ui-command__input, input');
51
70
  const list = box.querySelector('.ui-command__list, [role="listbox"]');
@@ -54,18 +73,44 @@ export function initCommand({ root } = {}) {
54
73
  const items = [...list.querySelectorAll('.ui-command__item, [role="option"]')];
55
74
  const groups = [...list.querySelectorAll('.ui-command__group')];
56
75
 
57
- const listId = list.id || (list.id = `bronto-cmd-${nextFieldUid()}`);
58
- items.forEach((it, i) => {
59
- if (!it.id) it.id = `${listId}-opt-${i}`;
60
- it.setAttribute('role', 'option');
76
+ const rememberState = () => ({
77
+ input: snapshotAttrs(input, [
78
+ 'role',
79
+ 'aria-controls',
80
+ 'aria-autocomplete',
81
+ 'aria-expanded',
82
+ 'aria-activedescendant',
83
+ 'autocomplete',
84
+ ]),
85
+ list: snapshotAttrs(list, ['id', 'role']),
86
+ empty: empty ? { hidden: empty.hidden } : null,
87
+ groups: groups.map((g) => ({
88
+ el: g,
89
+ hidden: g.hidden,
90
+ attrs: snapshotAttrs(g, ['role']),
91
+ })),
92
+ items: items.map((it) => ({
93
+ el: it,
94
+ hidden: it.hidden,
95
+ active: it.classList.contains('is-active'),
96
+ attrs: snapshotAttrs(it, ['id', 'role', 'aria-selected']),
97
+ })),
61
98
  });
62
- groups.forEach((g) => g.setAttribute('role', 'presentation'));
63
- list.setAttribute('role', 'listbox');
64
- input.setAttribute('role', 'combobox');
65
- input.setAttribute('aria-controls', listId);
66
- input.setAttribute('aria-autocomplete', 'list');
67
- input.setAttribute('aria-expanded', 'true');
68
- input.setAttribute('autocomplete', 'off');
99
+
100
+ const restoreState = (state) => {
101
+ restoreAttrs(input, state.input);
102
+ restoreAttrs(list, state.list);
103
+ if (empty && state.empty) empty.hidden = state.empty.hidden;
104
+ for (const group of state.groups) {
105
+ group.el.hidden = group.hidden;
106
+ restoreAttrs(group.el, group.attrs);
107
+ }
108
+ for (const item of state.items) {
109
+ item.el.hidden = item.hidden;
110
+ item.el.classList.toggle('is-active', item.active);
111
+ restoreAttrs(item.el, item.attrs);
112
+ }
113
+ };
69
114
 
70
115
  let active = -1;
71
116
  const visible = () => items.filter((it) => !it.hidden);
@@ -176,22 +221,37 @@ export function initCommand({ root } = {}) {
176
221
  }
177
222
  };
178
223
  const onClick = (e) => {
179
- const item = e.target.closest('.ui-command__item, [role="option"]');
224
+ const item = closestSafe(e.target, '.ui-command__item, [role="option"]');
180
225
  if (item && list.contains(item)) choose(item);
181
226
  };
182
227
 
183
228
  const bound = bindOnce(box, 'command', () => {
229
+ const state = rememberState();
230
+ const listId = list.id || (list.id = `bronto-cmd-${nextFieldUid()}`);
231
+ items.forEach((it, i) => {
232
+ if (!it.id) it.id = `${listId}-opt-${i}`;
233
+ it.setAttribute('role', 'option');
234
+ });
235
+ groups.forEach((g) => g.setAttribute('role', 'presentation'));
236
+ list.setAttribute('role', 'listbox');
237
+ input.setAttribute('role', 'combobox');
238
+ input.setAttribute('aria-controls', listId);
239
+ input.setAttribute('aria-autocomplete', 'list');
240
+ input.setAttribute('aria-expanded', 'true');
241
+ input.setAttribute('autocomplete', 'off');
184
242
  input.addEventListener('input', onInput);
185
243
  input.addEventListener('keydown', onKey);
186
244
  list.addEventListener('click', onClick);
245
+ // Seed the initial active item (first visible).
246
+ filter();
187
247
  return () => {
188
248
  input.removeEventListener('input', onInput);
189
249
  input.removeEventListener('keydown', onKey);
190
250
  list.removeEventListener('click', onClick);
251
+ restoreState(state);
252
+ active = -1;
191
253
  };
192
254
  });
193
- // Seed the initial active item (first visible).
194
- filter();
195
255
  cleanups.push(bound);
196
256
  }
197
257
 
@@ -1 +1 @@
1
- {"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["connectors.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CA+E3C"}
1
+ {"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["connectors.js"],"names":[],"mappings":"AA+EA;;;;;;;;;;;;;;GAcG;AACH,0CAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAwG3C"}