pith-ui 0.1.0 → 0.1.1

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 (91) hide show
  1. package/AI.md +8 -0
  2. package/API.md +656 -249
  3. package/README.md +1 -1
  4. package/dist/composer.d.ts.map +1 -1
  5. package/dist/composer.js +13 -4
  6. package/dist/composer.js.map +1 -1
  7. package/dist/content/conversation-view.d.ts +21 -4
  8. package/dist/content/conversation-view.d.ts.map +1 -1
  9. package/dist/content/conversation-view.js +38 -5
  10. package/dist/content/conversation-view.js.map +1 -1
  11. package/dist/content/image-viewer.d.ts +4 -2
  12. package/dist/content/image-viewer.d.ts.map +1 -1
  13. package/dist/content/image-viewer.js +25 -12
  14. package/dist/content/image-viewer.js.map +1 -1
  15. package/dist/content/paged-preview.d.ts.map +1 -1
  16. package/dist/content/paged-preview.js +6 -3
  17. package/dist/content/paged-preview.js.map +1 -1
  18. package/dist/content/welcome.d.ts.map +1 -1
  19. package/dist/content/welcome.js +14 -5
  20. package/dist/content/welcome.js.map +1 -1
  21. package/dist/files/panels.d.ts +6 -1
  22. package/dist/files/panels.d.ts.map +1 -1
  23. package/dist/files/panels.js +48 -7
  24. package/dist/files/panels.js.map +1 -1
  25. package/dist/files/systems.d.ts.map +1 -1
  26. package/dist/files/systems.js +47 -30
  27. package/dist/files/systems.js.map +1 -1
  28. package/dist/forms/actions.d.ts +7 -7
  29. package/dist/forms/actions.d.ts.map +1 -1
  30. package/dist/forms/actions.js +28 -8
  31. package/dist/forms/actions.js.map +1 -1
  32. package/dist/forms/custom-connection.d.ts +2 -1
  33. package/dist/forms/custom-connection.d.ts.map +1 -1
  34. package/dist/forms/custom-connection.js +34 -27
  35. package/dist/forms/custom-connection.js.map +1 -1
  36. package/dist/forms/dialog-frame.d.ts +2 -0
  37. package/dist/forms/dialog-frame.d.ts.map +1 -1
  38. package/dist/forms/dialog-frame.js +46 -26
  39. package/dist/forms/dialog-frame.js.map +1 -1
  40. package/dist/forms/identity.d.ts +12 -0
  41. package/dist/forms/identity.d.ts.map +1 -1
  42. package/dist/forms/identity.js +32 -0
  43. package/dist/forms/identity.js.map +1 -1
  44. package/dist/forms/model-editor.d.ts +8 -3
  45. package/dist/forms/model-editor.d.ts.map +1 -1
  46. package/dist/forms/model-editor.js +73 -62
  47. package/dist/forms/model-editor.js.map +1 -1
  48. package/dist/forms/panels.d.ts +95 -6
  49. package/dist/forms/panels.d.ts.map +1 -1
  50. package/dist/forms/panels.js +142 -27
  51. package/dist/forms/panels.js.map +1 -1
  52. package/dist/forms/provider-login.d.ts.map +1 -1
  53. package/dist/forms/provider-login.js +9 -7
  54. package/dist/forms/provider-login.js.map +1 -1
  55. package/dist/forms/resource-editor.d.ts +9 -5
  56. package/dist/forms/resource-editor.d.ts.map +1 -1
  57. package/dist/forms/resource-editor.js +18 -8
  58. package/dist/forms/resource-editor.js.map +1 -1
  59. package/dist/foundations.d.ts +95 -0
  60. package/dist/foundations.d.ts.map +1 -0
  61. package/dist/foundations.js +315 -0
  62. package/dist/foundations.js.map +1 -0
  63. package/dist/navigation/context-menu.d.ts +1 -0
  64. package/dist/navigation/context-menu.d.ts.map +1 -1
  65. package/dist/navigation/context-menu.js +48 -34
  66. package/dist/navigation/context-menu.js.map +1 -1
  67. package/dist/navigation/navigation.d.ts +42 -7
  68. package/dist/navigation/navigation.d.ts.map +1 -1
  69. package/dist/navigation/navigation.js +197 -14
  70. package/dist/navigation/navigation.js.map +1 -1
  71. package/dist/primitives.d.ts +3 -0
  72. package/dist/primitives.d.ts.map +1 -1
  73. package/dist/primitives.js +60 -168
  74. package/dist/primitives.js.map +1 -1
  75. package/dist/styles.css +32 -28
  76. package/dist/surface.d.ts +29 -0
  77. package/dist/surface.d.ts.map +1 -0
  78. package/dist/surface.js +154 -0
  79. package/dist/surface.js.map +1 -0
  80. package/dist/tasks/approval.d.ts.map +1 -1
  81. package/dist/tasks/approval.js +5 -2
  82. package/dist/tasks/approval.js.map +1 -1
  83. package/dist/tasks/permission.d.ts +5 -3
  84. package/dist/tasks/permission.d.ts.map +1 -1
  85. package/dist/tasks/permission.js +11 -5
  86. package/dist/tasks/permission.js.map +1 -1
  87. package/dist/workspace.d.ts +58 -4
  88. package/dist/workspace.d.ts.map +1 -1
  89. package/dist/workspace.js +120 -15
  90. package/dist/workspace.js.map +1 -1
  91. package/package.json +1 -1
@@ -1,64 +1,20 @@
1
- import { Generation, Lifetime, setControlPending } from './core.js';
2
- import { element, focusableElements, iconElement, setText, uniqueId } from './dom.js';
1
+ import { Generation, Lifetime } from './core.js';
2
+ import { element, iconElement, setText, uniqueId } from './dom.js';
3
+ import { adoptAction, adoptActionLayout, adoptFeedback, adoptNativeControl, reconcileSelectOptions } from './foundations.js';
4
+ import { installDialogLifecycle, installMenuNavigation } from './surface.js';
3
5
  export function createButton(options) {
4
6
  const node = element('button', 'pui-button');
5
7
  node.type = options.type ?? 'button';
6
8
  const caption = element('span');
7
9
  node.append(caption);
8
- const life = new Lifetime();
9
- const generation = new Generation();
10
- let state = options;
11
- let localPending = false;
12
- let currentIcon = '';
13
- let glyph;
10
+ const action = adoptAction(node, { caption, onAction: options.onAction, onError: options.onError });
14
11
  const update = (next) => {
15
- state = next;
16
- setText(caption, next.label);
17
- node.className = `pui-button pui-${next.intent ?? 'neutral'} pui-${next.density ?? 'normal'}`;
18
- node.disabled = !!(next.disabled || next.pending || localPending);
19
- setControlPending(node, !!(next.pending || localPending));
20
- node.setAttribute('aria-busy', String(!!(next.pending || localPending)));
21
- node.title = next.title ?? '';
22
- if (currentIcon !== (next.icon ?? '')) {
23
- glyph?.remove();
24
- currentIcon = next.icon ?? '';
25
- glyph = next.icon ? iconElement(next.icon) : undefined;
26
- if (glyph)
27
- node.prepend(glyph);
28
- }
12
+ action.update({ ...next, intent: next.intent ?? 'neutral', density: next.density ?? 'normal',
13
+ disabled: !!next.disabled, pending: !!next.pending, title: next.title ?? '', icon: next.icon ?? '',
14
+ appearance: next.appearance ?? 'standard' });
29
15
  };
30
- life.listen(node, 'click', (() => {
31
- if (!options.onAction || node.disabled)
32
- return;
33
- const seq = generation.next();
34
- localPending = true;
35
- update(state);
36
- const complete = (result) => {
37
- if (generation.current(seq) && result && !result.ok)
38
- options.onError?.(result.error);
39
- };
40
- const failed = (error) => { if (generation.current(seq))
41
- options.onError?.(error instanceof Error ? error.message : String(error)); };
42
- const finish = () => { if (generation.current(seq)) {
43
- localPending = false;
44
- update(state);
45
- } };
46
- try {
47
- const result = options.onAction();
48
- if (result && 'then' in result)
49
- void result.then(complete).catch(failed).finally(finish);
50
- else {
51
- complete(result);
52
- finish();
53
- }
54
- }
55
- catch (error) {
56
- failed(error);
57
- finish();
58
- }
59
- }));
60
16
  update(options);
61
- return { element: node, update, destroy() { generation.destroy(); life.destroy(); } };
17
+ return { element: node, update, destroy() { action.destroy(); } };
62
18
  }
63
19
  export function createIconButton(options) {
64
20
  const block = createButton(options);
@@ -140,11 +96,11 @@ export function createKeyValue(options) {
140
96
  }
141
97
  export function createToolbar(options) {
142
98
  const root = element('div', 'pui-toolbar');
143
- root.setAttribute('role', 'group');
144
99
  root.append(...(options.children ?? []));
145
- const update = (state) => { root.setAttribute('aria-label', state.label); root.classList.toggle('pui-compact', state.density === 'compact'); };
100
+ const layout = adoptActionLayout(root, 'toolbar');
101
+ const update = (state) => { layout.update({ ...state, density: state.density ?? 'normal' }); };
146
102
  update(options);
147
- return { element: root, update, destroy() { } };
103
+ return { element: root, update, destroy() { layout.destroy(); } };
148
104
  }
149
105
  export function createScrollArea(options) {
150
106
  const root = element('div', 'pui-scroll-area', options.content);
@@ -160,33 +116,19 @@ export function createField(options) {
160
116
  const description = element('div', 'pui-field-description');
161
117
  const error = element('div', 'pui-field-error');
162
118
  const control = options.control;
163
- if (!control.id)
164
- control.id = uniqueId('pui-input');
165
- label.htmlFor = control.id;
166
119
  description.id = uniqueId('pui-description');
167
120
  error.id = uniqueId('pui-error');
168
- error.setAttribute('role', 'alert');
169
121
  control.classList.add('pui-control');
170
122
  root.append(label, control, description, error);
123
+ const field = adoptNativeControl(control, { label, description, error });
171
124
  const update = (state) => {
172
- setText(label, `${state.label}${state.required ? ' *' : ''}`);
125
+ field.update({ ...state, label: `${state.label}${state.required ? ' *' : ''}`,
126
+ description: state.description ?? '', error: state.error ?? '', disabled: !!state.disabled, required: !!state.required });
173
127
  label.hidden = !state.label;
174
- setText(description, state.description);
175
- description.hidden = !state.description;
176
- setText(error, state.error);
177
- error.hidden = !state.error;
178
- control.disabled = !!state.disabled;
179
- control.required = !!state.required;
180
- control.setAttribute('aria-invalid', String(!!state.error));
181
- const describedBy = [state.description ? description.id : '', state.error ? error.id : ''].filter(Boolean).join(' ');
182
- if (describedBy)
183
- control.setAttribute('aria-describedby', describedBy);
184
- else
185
- control.removeAttribute('aria-describedby');
186
128
  root.classList.toggle('pui-invalid', !!state.error);
187
129
  };
188
130
  update(options);
189
- return { element: root, control, update, destroy() { } };
131
+ return { element: root, control, update, destroy() { field.destroy(); } };
190
132
  }
191
133
  function textControl(options, type) {
192
134
  const control = type === 'textarea' ? element('textarea') : element('input');
@@ -203,20 +145,16 @@ function textControl(options, type) {
203
145
  control.setAttribute('autocomplete', options.autocomplete);
204
146
  control.value = options.value ?? '';
205
147
  const field = createField({ ...options, control });
206
- const life = new Lifetime();
207
- life.listen(control, 'input', (event => options.onInput?.(control.value, event)));
208
- life.listen(control, 'change', (event => options.onChange?.(control.value, event)));
148
+ const input = adoptNativeControl(control, { onInput: options.onInput, onChange: options.onChange });
209
149
  const update = (state) => {
210
150
  field.update(state);
211
- control.placeholder = state.placeholder ?? '';
212
- control.readOnly = !!state.readOnly;
151
+ input.update({ placeholder: state.placeholder ?? '', readOnly: !!state.readOnly });
213
152
  control.classList.toggle('pui-compact', state.density === 'compact');
214
153
  };
215
154
  update(options);
216
- return { element: field.element, control, update, getValue: () => control.value,
217
- reset(value) { if (control.value !== value)
218
- control.value = value; }, focus() { control.focus(); },
219
- destroy() { life.destroy(); field.destroy(); } };
155
+ return { element: field.element, control, update, getValue: input.getValue,
156
+ reset: input.reset, focus: input.focus,
157
+ destroy() { input.destroy(); field.destroy(); } };
220
158
  }
221
159
  export const createTextInput = (options) => textControl(options, 'text');
222
160
  export const createSearchInput = (options) => textControl(options, 'search');
@@ -246,7 +184,7 @@ export function createSelect(options) {
246
184
  if (options.size)
247
185
  control.size = options.size;
248
186
  const field = createField({ ...options, control });
249
- const life = new Lifetime();
187
+ const input = adoptNativeControl(control, { onChange: () => options.onChange?.(control.value) });
250
188
  let signature = '';
251
189
  const update = (state) => {
252
190
  field.update(state);
@@ -254,23 +192,16 @@ export function createSelect(options) {
254
192
  const next = JSON.stringify(state.options);
255
193
  if (next !== signature) {
256
194
  signature = next;
257
- const selected = new Set([...control.selectedOptions].map(option => option.value));
258
- const nodes = state.options.map(item => {
259
- const option = element('option', '', item.label);
260
- option.value = item.value;
261
- option.disabled = !!item.disabled;
262
- option.selected = selected.has(item.value);
263
- return option;
264
- });
265
- control.replaceChildren(...nodes);
195
+ reconcileSelectOptions(control, state.options);
196
+ if (!control.multiple && control.selectedIndex < 0 && control.options.length)
197
+ control.selectedIndex = 0;
266
198
  }
267
199
  };
268
- life.listen(control, 'change', (() => options.onChange?.(control.value)));
269
200
  update(options);
270
201
  if (options.value != null)
271
202
  control.value = options.value;
272
- return { element: field.element, control, update, getValue: () => control.value,
273
- reset(value) { control.value = value; }, focus() { control.focus(); }, destroy() { life.destroy(); } };
203
+ return { element: field.element, control, update, getValue: input.getValue,
204
+ reset: input.reset, focus: input.focus, destroy() { input.destroy(); field.destroy(); } };
274
205
  }
275
206
  export function createCheckbox(options) {
276
207
  const control = element('input');
@@ -279,34 +210,29 @@ export function createCheckbox(options) {
279
210
  const caption = element('span');
280
211
  const description = element('small');
281
212
  const root = element('label', 'pui-checkbox', control, element('span', '', caption, description));
282
- const life = new Lifetime();
213
+ const input = adoptNativeControl(control, { onChange: () => options.onChange?.(control.checked) });
283
214
  const update = (state) => {
284
215
  setText(caption, state.label);
285
216
  setText(description, state.description);
286
217
  description.hidden = !state.description;
287
- control.disabled = !!state.disabled;
218
+ input.update({ disabled: !!state.disabled });
288
219
  };
289
- life.listen(control, 'change', (() => options.onChange?.(control.checked)));
290
220
  update(options);
291
- return { element: root, control, update, getValue: () => control.checked,
292
- reset(value) { control.checked = value; }, focus() { control.focus(); }, destroy() { life.destroy(); } };
221
+ return { element: root, control, update, getValue: input.getChecked,
222
+ reset: input.reset, focus: input.focus, destroy() { input.destroy(); } };
293
223
  }
294
224
  export function createNotice(state) {
295
225
  const root = element('div', 'pui-notice');
296
- const update = (next) => {
297
- root.className = `pui-notice pui-notice-${next.tone ?? 'info'}`;
298
- root.setAttribute('role', next.tone === 'error' || next.tone === 'warning' ? 'alert' : 'status');
299
- root.hidden = !next.message;
300
- setText(root, next.message);
301
- };
302
- update(state);
303
- return { element: root, update, destroy() { } };
226
+ const notice = adoptFeedback(root, { presentation: 'notice' });
227
+ notice.update(state);
228
+ return notice;
304
229
  }
305
230
  export function createStatus(state) {
306
231
  const root = element('span', 'pui-status');
307
- const update = (next) => { root.className = `pui-status pui-status-${next.tone ?? 'neutral'}`; setText(root, next.label); };
232
+ const status = adoptFeedback(root, { presentation: 'status', autoHide: false });
233
+ const update = (next) => { status.update({ message: next.label, tone: next.tone }); };
308
234
  update(state);
309
- return { element: root, update, destroy() { } };
235
+ return { element: root, update, destroy() { status.destroy(); } };
310
236
  }
311
237
  export function createProgress(state) {
312
238
  const control = element('progress');
@@ -336,9 +262,10 @@ export function createFormSection(options) {
336
262
  export function createActions(options = {}) {
337
263
  const root = element('div', 'pui-actions');
338
264
  root.append(...(options.children ?? []));
339
- const update = (state) => { root.dataset.align = state.align ?? 'end'; };
265
+ const layout = adoptActionLayout(root, 'actions');
266
+ const update = (state) => { layout.update({ align: state.align ?? 'end' }); };
340
267
  update(options);
341
- return { element: root, update, destroy() { } };
268
+ return { element: root, update, destroy() { layout.destroy(); } };
342
269
  }
343
270
  export function createDisclosure(options) {
344
271
  const root = element('details', 'pui-disclosure');
@@ -424,9 +351,7 @@ export function createDialog(options) {
424
351
  root.replaceChildren(heading, body);
425
352
  if (options.actions)
426
353
  root.append(options.actions);
427
- const life = new Lifetime();
428
354
  let state = options;
429
- let previousFocus = null;
430
355
  let disposed = false;
431
356
  const update = (next) => {
432
357
  state = next;
@@ -441,39 +366,14 @@ export function createDialog(options) {
441
366
  root.removeAttribute('aria-describedby');
442
367
  close.update({ label: `Close ${next.title}`, icon: 'close', disabled: next.closeDisabled });
443
368
  };
444
- life.listen(root, 'cancel', (event => { if (state.closeDisabled)
445
- event.preventDefault(); }));
446
- life.listen(root, 'click', (event => {
447
- if (!options.closeOnBackdrop || state.closeDisabled || event.target !== root)
448
- return;
449
- const pointer = event;
450
- const rect = root.getBoundingClientRect();
451
- if (pointer.clientX < rect.left || pointer.clientX > rect.right || pointer.clientY < rect.top || pointer.clientY > rect.bottom)
452
- root.close();
453
- }));
454
- life.listen(root, 'close', (() => {
455
- // Native close events are queued; an old close must not affect a reopened dialog.
456
- if (root.open)
457
- return;
458
- options.onClose?.();
459
- if (previousFocus?.isConnected)
460
- previousFocus.focus({ preventScroll: true });
461
- previousFocus = null;
462
- }));
369
+ const surface = installDialogLifecycle(root, { closeOnBackdrop: options.closeOnBackdrop ?? !!options.dialog,
370
+ canClose: () => !state.closeDisabled, onClose: options.onClose });
463
371
  update(options);
464
372
  return { element: root, body, update,
465
- open() {
466
- if (disposed || root.open)
467
- return;
468
- previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
469
- if (!root.isConnected)
470
- document.body.append(root);
471
- root.showModal();
472
- }, close() { if (root.open)
473
- root.close(); },
373
+ open() { if (!disposed)
374
+ surface.open(); }, close() { surface.close(); },
474
375
  destroy() { if (disposed)
475
- return; disposed = true; if (root.open)
476
- root.close(); life.destroy(); close.destroy(); root.remove(); } };
376
+ return; disposed = true; surface.close(); surface.destroy(); close.destroy(); root.remove(); } };
477
377
  }
478
378
  export function createTextEditDialog(options = {}) {
479
379
  // The editor has one form-level error, so it does not need the general field's
@@ -483,6 +383,7 @@ export function createTextEditDialog(options = {}) {
483
383
  control.id = options.ids?.control ?? uniqueId('pui-input');
484
384
  const label = element('label', 'pui-field-label', options.label ?? 'Message');
485
385
  label.htmlFor = control.id;
386
+ const input = adoptNativeControl(control, { label, onInput: value => options.onInput?.(value) });
486
387
  const field = element('div', 'pui-field', label, control);
487
388
  const note = element('p', 'pui-muted');
488
389
  const error = createNotice({ message: '', tone: 'error' });
@@ -515,9 +416,8 @@ export function createTextEditDialog(options = {}) {
515
416
  note.hidden = !next.note;
516
417
  error.update({ message: next.error ?? '', tone: 'error' });
517
418
  submit.update({ label: next.submitLabel ?? options.submitLabel ?? 'Save', intent: 'primary', disabled: next.disabled, pending: !!(next.pending || pending) });
518
- control.disabled = !!next.inputDisabled;
419
+ input.update({ disabled: !!next.inputDisabled });
519
420
  };
520
- life.listen(control, 'input', () => options.onInput?.(control.value));
521
421
  life.listen(form, 'submit', (event => {
522
422
  event.preventDefault();
523
423
  if (pending || state.pending || state.disabled || submit.element.disabled || !options.onSubmit)
@@ -544,15 +444,15 @@ export function createTextEditDialog(options = {}) {
544
444
  }));
545
445
  update(state);
546
446
  return { element: dialog.element, control, submitButton: submit.element, update,
547
- open(input) {
447
+ open(target) {
548
448
  generation.invalidate();
549
449
  pending = false;
550
- update({ ...state, error: '', title: input.title ?? options.title ?? 'Edit message', description: input.description ?? options.description, note: input.note ?? options.note });
551
- control.value = input.value;
450
+ update({ ...state, error: '', title: target.title ?? options.title ?? 'Edit message', description: target.description ?? options.description, note: target.note ?? options.note });
451
+ input.reset(target.value);
552
452
  dialog.open();
553
- control.focus();
554
- }, close() { generation.invalidate(); dialog.close(); }, getValue: () => control.value, focus() { control.focus(); },
555
- destroy() { generation.destroy(); life.destroy(); dialog.destroy(); cancel.destroy(); submit.destroy(); } };
453
+ input.focus();
454
+ }, close() { generation.invalidate(); dialog.close(); }, getValue: input.getValue, focus: input.focus,
455
+ destroy() { generation.destroy(); life.destroy(); input.destroy(); dialog.destroy(); cancel.destroy(); submit.destroy(); } };
556
456
  }
557
457
  export function createConfirmDialog(options) {
558
458
  const message = element('p', 'pui-modal-description');
@@ -618,12 +518,12 @@ export function createConfirmDialog(options) {
618
518
  setText(hint, next.hint);
619
519
  hint.hidden = !next.hint;
620
520
  error.update({ message: next.error ?? '', tone: 'error' });
621
- confirm.update({ label: next.confirmLabel ?? 'Remove', intent: 'danger', disabled: next.disabled, pending: !!(next.pending || pending) });
521
+ confirm.update({ label: next.confirmLabel ?? 'Remove', intent: 'danger', disabled: !!(next.disabled || options.unavailable?.()), pending: !!(next.pending || pending) });
622
522
  };
623
523
  life.listen(form, 'submit', (event => { event.preventDefault(); void run(); }));
624
524
  update(options);
625
525
  return { element: dialog.element, confirmButton: confirm.element, cancelButton: cancel.element, update, open(next) { generation.invalidate(); pending = false; update({ ...(next ?? state), error: '' }); dialog.open(); cancel.element.focus(); },
626
- close() { generation.invalidate(); dialog.close(); }, destroy() { generation.destroy(); life.destroy(); dialog.destroy(); confirm.destroy(); cancel.destroy(); } };
526
+ close() { generation.invalidate(); dialog.close(); }, refreshAvailability() { update(state); }, destroy() { generation.destroy(); life.destroy(); dialog.destroy(); confirm.destroy(); cancel.destroy(); } };
627
527
  }
628
528
  export function createPopover(options) {
629
529
  let open = false;
@@ -720,22 +620,14 @@ export function createMenu(options) {
720
620
  if (key === 'ArrowDown' || key === 'ArrowUp') {
721
621
  event.preventDefault();
722
622
  popover.open();
723
- const targets = focusableElements(content);
724
- (key === 'ArrowDown' ? targets[0] : targets.at(-1))?.focus();
623
+ navigation.focus(key === 'ArrowUp' ? 'last' : 'first');
725
624
  }
726
625
  }));
727
- life.listen(content, 'keydown', (event => {
728
- const key = event.key;
729
- if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key))
730
- return;
731
- event.preventDefault();
732
- const targets = focusableElements(content);
733
- const index = targets.indexOf(document.activeElement);
734
- const next = key === 'Home' ? 0 : key === 'End' ? targets.length - 1 : (index + (key === 'ArrowUp' ? -1 : 1) + targets.length) % targets.length;
735
- targets[next]?.focus();
736
- }));
626
+ const navigation = installMenuNavigation(content, { trigger: popover.trigger, isOpen: popover.isOpen,
627
+ onDismiss(restoreFocus) { popover.close(); if (restoreFocus)
628
+ popover.trigger.focus(); } });
737
629
  update(options);
738
- return { ...popover, update, destroy() { life.destroy(); popover.destroy(); for (const block of blocks.values())
630
+ return { ...popover, update, destroy() { navigation.destroy(); life.destroy(); popover.destroy(); for (const block of blocks.values())
739
631
  block.destroy(); } };
740
632
  }
741
633
  //# sourceMappingURL=primitives.js.map