@angular/aria 21.0.0-rc.1 → 21.0.0-rc.3

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 (41) hide show
  1. package/_adev_assets/aria-accordion.json +429 -59
  2. package/_adev_assets/aria-combobox.json +261 -41
  3. package/_adev_assets/aria-grid.json +339 -85
  4. package/_adev_assets/aria-listbox.json +99 -70
  5. package/_adev_assets/aria-menu.json +355 -158
  6. package/_adev_assets/aria-tabs.json +198 -305
  7. package/_adev_assets/aria-toolbar.json +70 -221
  8. package/_adev_assets/aria-tree.json +153 -363
  9. package/fesm2022/_widget-chunk.mjs +388 -57
  10. package/fesm2022/_widget-chunk.mjs.map +1 -1
  11. package/fesm2022/accordion.mjs +125 -72
  12. package/fesm2022/accordion.mjs.map +1 -1
  13. package/fesm2022/aria.mjs +1 -1
  14. package/fesm2022/aria.mjs.map +1 -1
  15. package/fesm2022/combobox.mjs +129 -24
  16. package/fesm2022/combobox.mjs.map +1 -1
  17. package/fesm2022/grid.mjs +203 -65
  18. package/fesm2022/grid.mjs.map +1 -1
  19. package/fesm2022/listbox.mjs +50 -39
  20. package/fesm2022/listbox.mjs.map +1 -1
  21. package/fesm2022/menu.mjs +179 -71
  22. package/fesm2022/menu.mjs.map +1 -1
  23. package/fesm2022/private.mjs +418 -440
  24. package/fesm2022/private.mjs.map +1 -1
  25. package/fesm2022/tabs.mjs +105 -73
  26. package/fesm2022/tabs.mjs.map +1 -1
  27. package/fesm2022/toolbar.mjs +52 -44
  28. package/fesm2022/toolbar.mjs.map +1 -1
  29. package/fesm2022/tree.mjs +106 -63
  30. package/fesm2022/tree.mjs.map +1 -1
  31. package/package.json +2 -2
  32. package/types/_grid-chunk.d.ts +216 -35
  33. package/types/accordion.d.ts +134 -35
  34. package/types/combobox.d.ts +141 -12
  35. package/types/grid.d.ts +150 -32
  36. package/types/listbox.d.ts +60 -28
  37. package/types/menu.d.ts +133 -49
  38. package/types/private.d.ts +210 -250
  39. package/types/tabs.d.ts +124 -44
  40. package/types/toolbar.d.ts +58 -36
  41. package/types/tree.d.ts +121 -49
@@ -1,29 +1,68 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, computed, model, Directive, inject, TemplateRef, ViewContainerRef, afterRenderEffect } from '@angular/core';
3
- import { KeyboardEventManager, PointerEventManager, Modifier } from './_widget-chunk.mjs';
3
+ import { PointerEventManager, KeyboardEventManager, ListFocus, ListNavigation, Modifier } from './_widget-chunk.mjs';
4
4
  export { GridCellPattern, GridCellWidgetPattern, GridPattern, GridRowPattern } from './_widget-chunk.mjs';
5
5
 
6
6
  class ComboboxPattern {
7
7
  inputs;
8
8
  expanded = signal(false);
9
- activeDescendant = computed(() => this.inputs.popupControls()?.activeId() ?? null);
9
+ disabled = () => this.inputs.disabled();
10
+ activeDescendant = computed(() => {
11
+ const popupControls = this.inputs.popupControls();
12
+ if (popupControls instanceof ComboboxDialogPattern) {
13
+ return null;
14
+ }
15
+ return popupControls?.activeId() ?? null;
16
+ });
10
17
  highlightedItem = signal(undefined);
11
18
  isDeleting = false;
12
19
  isFocused = signal(false);
20
+ hasBeenFocused = signal(false);
13
21
  expandKey = computed(() => this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight');
14
22
  collapseKey = computed(() => this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft');
15
23
  popupId = computed(() => this.inputs.popupControls()?.id() || null);
16
24
  autocomplete = computed(() => this.inputs.filterMode() === 'highlight' ? 'both' : 'list');
17
25
  hasPopup = computed(() => this.inputs.popupControls()?.role() || null);
18
- readonly = computed(() => this.inputs.readonly() || null);
26
+ readonly = computed(() => this.inputs.readonly() || this.inputs.disabled() || null);
27
+ listControls = () => {
28
+ const popupControls = this.inputs.popupControls();
29
+ if (popupControls instanceof ComboboxDialogPattern) {
30
+ return null;
31
+ }
32
+ return popupControls;
33
+ };
34
+ treeControls = () => {
35
+ const popupControls = this.inputs.popupControls();
36
+ if (popupControls?.role() === 'tree') {
37
+ return popupControls;
38
+ }
39
+ return null;
40
+ };
19
41
  keydown = computed(() => {
42
+ const manager = new KeyboardEventManager();
43
+ const popupControls = this.inputs.popupControls();
44
+ if (!popupControls) {
45
+ return manager;
46
+ }
47
+ if (popupControls instanceof ComboboxDialogPattern) {
48
+ if (!this.expanded()) {
49
+ manager.on('ArrowUp', () => this.open()).on('ArrowDown', () => this.open());
50
+ if (this.readonly()) {
51
+ manager.on('Enter', () => this.open()).on(' ', () => this.open());
52
+ }
53
+ }
54
+ return manager;
55
+ }
56
+ if (!this.inputs.alwaysExpanded()) {
57
+ manager.on('Escape', () => this.close({
58
+ reset: !this.readonly()
59
+ }));
60
+ }
20
61
  if (!this.expanded()) {
21
- const manager = new KeyboardEventManager().on('ArrowDown', () => this.open({
62
+ manager.on('ArrowDown', () => this.open({
22
63
  first: true
23
64
  })).on('ArrowUp', () => this.open({
24
65
  last: true
25
- })).on('Escape', () => this.close({
26
- reset: !this.readonly()
27
66
  }));
28
67
  if (this.readonly()) {
29
68
  manager.on('Enter', () => this.open({
@@ -34,13 +73,7 @@ class ComboboxPattern {
34
73
  }
35
74
  return manager;
36
75
  }
37
- const popupControls = this.inputs.popupControls();
38
- if (!popupControls) {
39
- return new KeyboardEventManager();
40
- }
41
- const manager = new KeyboardEventManager().on('ArrowDown', () => this.next()).on('ArrowUp', () => this.prev()).on('Home', () => this.first()).on('End', () => this.last()).on('Escape', () => this.close({
42
- reset: !this.readonly()
43
- }));
76
+ manager.on('ArrowDown', () => this.next()).on('ArrowUp', () => this.prev()).on('Home', () => this.first()).on('End', () => this.last());
44
77
  if (this.readonly()) {
45
78
  manager.on(' ', () => this.select({
46
79
  commit: true,
@@ -48,35 +81,47 @@ class ComboboxPattern {
48
81
  }));
49
82
  }
50
83
  if (popupControls.role() === 'listbox') {
84
+ manager.on('Enter', () => {
85
+ this.select({
86
+ commit: true,
87
+ close: !popupControls.multi()
88
+ });
89
+ });
90
+ }
91
+ const treeControls = this.treeControls();
92
+ if (treeControls?.isItemSelectable()) {
51
93
  manager.on('Enter', () => this.select({
52
94
  commit: true,
53
- close: !popupControls.multi()
95
+ close: true
54
96
  }));
55
97
  }
56
- if (popupControls.role() === 'tree') {
57
- const treeControls = popupControls;
58
- if (treeControls.isItemSelectable()) {
59
- manager.on('Enter', () => this.select({
60
- commit: true,
61
- close: true
62
- }));
63
- } else if (treeControls.isItemExpandable()) {
98
+ if (treeControls?.isItemExpandable()) {
99
+ manager.on(this.expandKey(), () => this.expandItem()).on(this.collapseKey(), () => this.collapseItem());
100
+ if (!treeControls.isItemSelectable()) {
64
101
  manager.on('Enter', () => this.expandItem());
65
102
  }
66
- if (treeControls.isItemExpandable() || treeControls.isItemCollapsible()) {
67
- manager.on(this.collapseKey(), () => this.collapseItem());
68
- }
69
- if (treeControls.isItemExpandable()) {
70
- manager.on(this.expandKey(), () => this.expandItem());
71
- }
103
+ }
104
+ if (treeControls?.isItemCollapsible()) {
105
+ manager.on(this.collapseKey(), () => this.collapseItem());
72
106
  }
73
107
  return manager;
74
108
  });
75
- pointerup = computed(() => new PointerEventManager().on(e => {
76
- const item = this.inputs.popupControls()?.getItem(e);
109
+ click = computed(() => new PointerEventManager().on(e => {
110
+ if (e.target === this.inputs.inputEl()) {
111
+ if (this.readonly()) {
112
+ this.expanded() ? this.close() : this.open({
113
+ selected: true
114
+ });
115
+ }
116
+ }
117
+ const controls = this.inputs.popupControls();
118
+ if (controls instanceof ComboboxDialogPattern) {
119
+ return;
120
+ }
121
+ const item = controls?.getItem(e);
77
122
  if (item) {
78
- if (this.inputs.popupControls()?.role() === 'tree') {
79
- const treeControls = this.inputs.popupControls();
123
+ if (controls?.role() === 'tree') {
124
+ const treeControls = controls;
80
125
  if (treeControls.isItemExpandable(item) && !treeControls.isItemSelectable(item)) {
81
126
  treeControls.toggleExpansion(item);
82
127
  this.inputs.inputEl()?.focus();
@@ -86,17 +131,10 @@ class ComboboxPattern {
86
131
  this.select({
87
132
  item,
88
133
  commit: true,
89
- close: !this.inputs.popupControls()?.multi()
134
+ close: !controls?.multi()
90
135
  });
91
136
  this.inputs.inputEl()?.focus();
92
137
  }
93
- if (e.target === this.inputs.inputEl()) {
94
- if (this.readonly()) {
95
- this.expanded() ? this.close() : this.open({
96
- selected: true
97
- });
98
- }
99
- }
100
138
  }));
101
139
  constructor(inputs) {
102
140
  this.inputs = inputs;
@@ -106,9 +144,9 @@ class ComboboxPattern {
106
144
  this.keydown().handle(event);
107
145
  }
108
146
  }
109
- onPointerup(event) {
147
+ onClick(event) {
110
148
  if (!this.inputs.disabled()) {
111
- this.pointerup().handle(event);
149
+ this.click().handle(event);
112
150
  }
113
151
  }
114
152
  onInput(event) {
@@ -119,27 +157,33 @@ class ComboboxPattern {
119
157
  if (!inputEl) {
120
158
  return;
121
159
  }
160
+ const popupControls = this.inputs.popupControls();
161
+ if (popupControls instanceof ComboboxDialogPattern) {
162
+ return;
163
+ }
122
164
  this.open();
123
165
  this.inputs.inputValue?.set(inputEl.value);
124
166
  this.isDeleting = event instanceof InputEvent && !!event.inputType.match(/^delete/);
125
- if (this.inputs.filterMode() === 'manual') {
126
- const selectedItems = this.inputs.popupControls()?.getSelectedItems();
127
- const searchTerm = selectedItems?.[0]?.searchTerm();
128
- if (searchTerm && this.inputs.inputValue() !== searchTerm) {
129
- this.inputs.popupControls()?.clearSelection();
130
- }
131
- }
132
167
  if (this.inputs.filterMode() === 'highlight' && !this.isDeleting) {
133
168
  this.highlight();
134
169
  }
135
170
  }
136
171
  onFocusIn() {
172
+ if (this.inputs.alwaysExpanded() && !this.hasBeenFocused()) {
173
+ const firstSelectedItem = this.listControls()?.getSelectedItems()[0];
174
+ firstSelectedItem ? this.listControls()?.focus(firstSelectedItem) : this.first();
175
+ }
137
176
  this.isFocused.set(true);
177
+ this.hasBeenFocused.set(true);
138
178
  }
139
179
  onFocusOut(event) {
140
180
  if (this.inputs.disabled()) {
141
181
  return;
142
182
  }
183
+ const popupControls = this.inputs.popupControls();
184
+ if (popupControls instanceof ComboboxDialogPattern) {
185
+ return;
186
+ }
143
187
  if (!(event.relatedTarget instanceof HTMLElement) || !this.inputs.containerEl()?.contains(event.relatedTarget)) {
144
188
  this.isFocused.set(false);
145
189
  if (this.readonly()) {
@@ -149,7 +193,7 @@ class ComboboxPattern {
149
193
  if (this.inputs.filterMode() !== 'manual') {
150
194
  this.commit();
151
195
  } else {
152
- const item = this.inputs.popupControls()?.items().find(i => i.searchTerm() === this.inputs.inputEl()?.value);
196
+ const item = popupControls?.items().find(i => i.searchTerm() === this.inputs.inputEl()?.value);
153
197
  if (item) {
154
198
  this.select({
155
199
  item
@@ -160,15 +204,19 @@ class ComboboxPattern {
160
204
  }
161
205
  }
162
206
  firstMatch = computed(() => {
163
- if (this.inputs.popupControls()?.role() === 'listbox') {
164
- return this.inputs.popupControls()?.items()[0];
207
+ if (this.listControls()?.role() === 'listbox') {
208
+ return this.listControls()?.items()[0];
165
209
  }
166
- return this.inputs.popupControls()?.items().find(i => i.value() === this.inputs.firstMatch());
210
+ return this.listControls()?.items().find(i => i.value() === this.inputs.firstMatch());
167
211
  });
168
212
  onFilter() {
169
213
  if (this.readonly()) {
170
214
  return;
171
215
  }
216
+ const popupControls = this.inputs.popupControls();
217
+ if (popupControls instanceof ComboboxDialogPattern) {
218
+ return;
219
+ }
172
220
  const isInitialRender = !this.inputs.inputValue?.().length && !this.isDeleting;
173
221
  if (isInitialRender) {
174
222
  return;
@@ -182,11 +230,11 @@ class ComboboxPattern {
182
230
  }
183
231
  const item = this.firstMatch();
184
232
  if (!item) {
185
- this.inputs.popupControls()?.clearSelection();
186
- this.inputs.popupControls()?.unfocus();
233
+ popupControls?.clearSelection();
234
+ popupControls?.unfocus();
187
235
  return;
188
236
  }
189
- this.inputs.popupControls()?.focus(item);
237
+ popupControls?.focus(item);
190
238
  if (this.inputs.filterMode() !== 'manual') {
191
239
  this.select({
192
240
  item
@@ -198,7 +246,7 @@ class ComboboxPattern {
198
246
  }
199
247
  highlight() {
200
248
  const inputEl = this.inputs.inputEl();
201
- const selectedItems = this.inputs.popupControls()?.getSelectedItems();
249
+ const selectedItems = this.listControls()?.getSelectedItems();
202
250
  const item = selectedItems?.[0];
203
251
  if (!inputEl || !item) {
204
252
  return;
@@ -211,12 +259,29 @@ class ComboboxPattern {
211
259
  }
212
260
  }
213
261
  close(opts) {
262
+ const popupControls = this.inputs.popupControls();
263
+ if (this.inputs.alwaysExpanded()) {
264
+ return;
265
+ }
266
+ if (popupControls instanceof ComboboxDialogPattern) {
267
+ this.expanded.set(false);
268
+ return;
269
+ }
270
+ if (this.readonly()) {
271
+ this.expanded.set(false);
272
+ popupControls?.unfocus();
273
+ return;
274
+ }
214
275
  if (!opts?.reset) {
276
+ if (this.inputs.filterMode() === 'manual') {
277
+ if (!this.listControls()?.items().some(i => i.searchTerm() === this.inputs.inputEl()?.value)) {
278
+ this.listControls()?.clearSelection();
279
+ }
280
+ }
215
281
  this.expanded.set(false);
216
- this.inputs.popupControls()?.unfocus();
282
+ popupControls?.unfocus();
217
283
  return;
218
284
  }
219
- const popupControls = this.inputs.popupControls();
220
285
  if (!this.expanded()) {
221
286
  this.inputs.inputValue?.set('');
222
287
  popupControls?.clearSelection();
@@ -225,19 +290,24 @@ class ComboboxPattern {
225
290
  inputEl.value = '';
226
291
  }
227
292
  } else if (this.expanded()) {
228
- this.close();
293
+ this.expanded.set(false);
229
294
  const selectedItem = popupControls?.getSelectedItems()?.[0];
230
295
  if (selectedItem?.searchTerm() !== this.inputs.inputValue()) {
231
296
  popupControls?.clearSelection();
232
297
  }
298
+ return;
233
299
  }
234
300
  this.close();
235
301
  if (!this.readonly()) {
236
- this.inputs.popupControls()?.clearSelection();
302
+ popupControls?.clearSelection();
237
303
  }
238
304
  }
239
305
  open(nav) {
240
306
  this.expanded.set(true);
307
+ const popupControls = this.inputs.popupControls();
308
+ if (popupControls instanceof ComboboxDialogPattern) {
309
+ return;
310
+ }
241
311
  const inputEl = this.inputs.inputEl();
242
312
  if (inputEl && this.inputs.filterMode() === 'highlight') {
243
313
  const isHighlighting = inputEl.selectionStart !== inputEl.value.length;
@@ -253,21 +323,23 @@ class ComboboxPattern {
253
323
  this.last();
254
324
  }
255
325
  if (nav?.selected) {
256
- const selectedItem = this.inputs.popupControls()?.items().find(i => this.inputs.popupControls()?.getSelectedItems().includes(i));
257
- selectedItem ? this.inputs.popupControls()?.focus(selectedItem) : this.first();
326
+ const selectedItem = popupControls?.items().find(i => popupControls?.getSelectedItems().includes(i));
327
+ if (selectedItem) {
328
+ popupControls?.focus(selectedItem);
329
+ }
258
330
  }
259
331
  }
260
332
  next() {
261
- this._navigate(() => this.inputs.popupControls()?.next());
333
+ this._navigate(() => this.listControls()?.next());
262
334
  }
263
335
  prev() {
264
- this._navigate(() => this.inputs.popupControls()?.prev());
336
+ this._navigate(() => this.listControls()?.prev());
265
337
  }
266
338
  first() {
267
- this._navigate(() => this.inputs.popupControls()?.first());
339
+ this._navigate(() => this.listControls()?.first());
268
340
  }
269
341
  last() {
270
- this._navigate(() => this.inputs.popupControls()?.last());
342
+ this._navigate(() => this.listControls()?.last());
271
343
  }
272
344
  collapseItem() {
273
345
  const controls = this.inputs.popupControls();
@@ -278,7 +350,7 @@ class ComboboxPattern {
278
350
  this._navigate(() => controls?.expandItem());
279
351
  }
280
352
  select(opts = {}) {
281
- const controls = this.inputs.popupControls();
353
+ const controls = this.listControls();
282
354
  if (opts.item) {
283
355
  controls?.focus(opts.item, {
284
356
  focusElement: false
@@ -294,7 +366,7 @@ class ComboboxPattern {
294
366
  }
295
367
  commit() {
296
368
  const inputEl = this.inputs.inputEl();
297
- const selectedItems = this.inputs.popupControls()?.getSelectedItems();
369
+ const selectedItems = this.listControls()?.getSelectedItems();
298
370
  if (!inputEl) {
299
371
  return;
300
372
  }
@@ -311,7 +383,7 @@ class ComboboxPattern {
311
383
  this.select();
312
384
  }
313
385
  if (this.inputs.filterMode() === 'highlight') {
314
- const selectedItem = this.inputs.popupControls()?.getSelectedItems()[0];
386
+ const selectedItem = this.listControls()?.getSelectedItems()[0];
315
387
  if (!selectedItem) {
316
388
  return;
317
389
  }
@@ -324,116 +396,23 @@ class ComboboxPattern {
324
396
  }
325
397
  }
326
398
  }
327
-
328
- class ListFocus {
399
+ class ComboboxDialogPattern {
329
400
  inputs;
330
- prevActiveItem = signal(undefined);
331
- prevActiveIndex = computed(() => {
332
- return this.prevActiveItem() ? this.inputs.items().indexOf(this.prevActiveItem()) : -1;
333
- });
334
- activeIndex = computed(() => {
335
- return this.inputs.activeItem() ? this.inputs.items().indexOf(this.inputs.activeItem()) : -1;
401
+ id = () => this.inputs.id();
402
+ role = () => 'dialog';
403
+ keydown = computed(() => {
404
+ return new KeyboardEventManager().on('Escape', () => this.inputs.combobox.close());
336
405
  });
337
406
  constructor(inputs) {
338
407
  this.inputs = inputs;
339
408
  }
340
- isListDisabled() {
341
- return this.inputs.disabled() || this.inputs.items().every(i => i.disabled());
342
- }
343
- getActiveDescendant() {
344
- if (this.isListDisabled()) {
345
- return undefined;
346
- }
347
- if (this.inputs.focusMode() === 'roving') {
348
- return undefined;
349
- }
350
- return this.inputs.activeItem()?.id() ?? undefined;
351
- }
352
- getListTabIndex() {
353
- if (this.isListDisabled()) {
354
- return 0;
355
- }
356
- return this.inputs.focusMode() === 'activedescendant' ? 0 : -1;
357
- }
358
- getItemTabIndex(item) {
359
- if (this.isListDisabled()) {
360
- return -1;
361
- }
362
- if (this.inputs.focusMode() === 'activedescendant') {
363
- return -1;
364
- }
365
- return this.inputs.activeItem() === item ? 0 : -1;
366
- }
367
- focus(item, opts) {
368
- if (this.isListDisabled() || !this.isFocusable(item)) {
369
- return false;
370
- }
371
- this.prevActiveItem.set(this.inputs.activeItem());
372
- this.inputs.activeItem.set(item);
373
- if (opts?.focusElement || opts?.focusElement === undefined) {
374
- this.inputs.focusMode() === 'roving' ? item.element()?.focus() : this.inputs.element()?.focus();
375
- }
376
- return true;
377
- }
378
- isFocusable(item) {
379
- return !item.disabled() || this.inputs.softDisabled();
380
- }
381
- }
382
-
383
- class ListNavigation {
384
- inputs;
385
- constructor(inputs) {
386
- this.inputs = inputs;
387
- }
388
- goto(item, opts) {
389
- return item ? this.inputs.focusManager.focus(item, opts) : false;
390
- }
391
- next(opts) {
392
- return this._advance(1, opts);
393
- }
394
- peekNext() {
395
- return this._peek(1);
396
- }
397
- prev(opts) {
398
- return this._advance(-1, opts);
399
- }
400
- peekPrev() {
401
- return this._peek(-1);
402
- }
403
- first(opts) {
404
- const item = this.peekFirst();
405
- return item ? this.goto(item, opts) : false;
406
- }
407
- last(opts) {
408
- const item = this.peekLast();
409
- return item ? this.goto(item, opts) : false;
410
- }
411
- peekFirst(items = this.inputs.items()) {
412
- return items.find(i => this.inputs.focusManager.isFocusable(i));
413
- }
414
- peekLast(items = this.inputs.items()) {
415
- for (let i = items.length - 1; i >= 0; i--) {
416
- if (this.inputs.focusManager.isFocusable(items[i])) {
417
- return items[i];
418
- }
419
- }
420
- return;
421
- }
422
- _advance(delta, opts) {
423
- const item = this._peek(delta);
424
- return item ? this.goto(item, opts) : false;
409
+ onKeydown(event) {
410
+ this.keydown().handle(event);
425
411
  }
426
- _peek(delta) {
427
- const items = this.inputs.items();
428
- const itemCount = items.length;
429
- const startIndex = this.inputs.focusManager.activeIndex();
430
- const step = i => this.inputs.wrap() ? (i + delta + itemCount) % itemCount : i + delta;
431
- for (let i = step(startIndex); i !== startIndex && i < itemCount && i >= 0; i = step(i)) {
432
- if (this.inputs.focusManager.isFocusable(items[i])) {
433
- return items[i];
434
- }
412
+ onClick(event) {
413
+ if (event.target === this.inputs.element()) {
414
+ this.inputs.combobox.close();
435
415
  }
436
- return;
437
416
  }
438
417
  }
439
418
 
@@ -441,7 +420,7 @@ class ListSelection {
441
420
  inputs;
442
421
  rangeStartIndex = signal(0);
443
422
  rangeEndIndex = signal(0);
444
- selectedItems = computed(() => this.inputs.items().filter(item => this.inputs.value().includes(item.value())));
423
+ selectedItems = computed(() => this.inputs.items().filter(item => this.inputs.values().includes(item.value())));
445
424
  constructor(inputs) {
446
425
  this.inputs = inputs;
447
426
  }
@@ -449,7 +428,7 @@ class ListSelection {
449
428
  anchor: true
450
429
  }) {
451
430
  item = item ?? this.inputs.focusManager.inputs.activeItem();
452
- if (!item || item.disabled() || !item.selectable() || this.inputs.value().includes(item.value())) {
431
+ if (!item || item.disabled() || !item.selectable() || this.inputs.values().includes(item.value())) {
453
432
  return;
454
433
  }
455
434
  if (!this.inputs.multi()) {
@@ -459,24 +438,24 @@ class ListSelection {
459
438
  if (opts.anchor) {
460
439
  this.beginRangeSelection(index);
461
440
  }
462
- this.inputs.value.update(values => values.concat(item.value()));
441
+ this.inputs.values.update(values => values.concat(item.value()));
463
442
  }
464
443
  deselect(item) {
465
444
  item = item ?? this.inputs.focusManager.inputs.activeItem();
466
445
  if (item && !item.disabled() && item.selectable()) {
467
- this.inputs.value.update(values => values.filter(value => value !== item.value()));
446
+ this.inputs.values.update(values => values.filter(value => value !== item.value()));
468
447
  }
469
448
  }
470
449
  toggle(item) {
471
450
  item = item ?? this.inputs.focusManager.inputs.activeItem();
472
451
  if (item) {
473
- this.inputs.value().includes(item.value()) ? this.deselect(item) : this.select(item);
452
+ this.inputs.values().includes(item.value()) ? this.deselect(item) : this.select(item);
474
453
  }
475
454
  }
476
455
  toggleOne() {
477
456
  const item = this.inputs.focusManager.inputs.activeItem();
478
457
  if (item) {
479
- this.inputs.value().includes(item.value()) ? this.deselect() : this.selectOne();
458
+ this.inputs.values().includes(item.value()) ? this.deselect() : this.selectOne();
480
459
  }
481
460
  }
482
461
  selectAll() {
@@ -491,14 +470,14 @@ class ListSelection {
491
470
  this.beginRangeSelection();
492
471
  }
493
472
  deselectAll() {
494
- for (const value of this.inputs.value()) {
473
+ for (const value of this.inputs.values()) {
495
474
  const item = this.inputs.items().find(i => i.value() === value);
496
- item ? this.deselect(item) : this.inputs.value.update(values => values.filter(v => v !== value));
475
+ item ? this.deselect(item) : this.inputs.values.update(values => values.filter(v => v !== value));
497
476
  }
498
477
  }
499
478
  toggleAll() {
500
479
  const selectableValues = this.inputs.items().filter(i => !i.disabled() && i.selectable()).map(i => i.value());
501
- selectableValues.every(i => this.inputs.value().includes(i)) ? this.deselectAll() : this.selectAll();
480
+ selectableValues.every(i => this.inputs.values().includes(i)) ? this.deselectAll() : this.selectAll();
502
481
  }
503
482
  selectOne() {
504
483
  const item = this.inputs.focusManager.inputs.activeItem();
@@ -506,7 +485,7 @@ class ListSelection {
506
485
  return;
507
486
  }
508
487
  this.deselectAll();
509
- if (this.inputs.value().length > 0 && !this.inputs.multi()) {
488
+ if (this.inputs.values().length > 0 && !this.inputs.multi()) {
510
489
  return;
511
490
  }
512
491
  this.select();
@@ -585,7 +564,7 @@ class ListTypeahead {
585
564
  this.timeout = setTimeout(() => {
586
565
  this._query.set('');
587
566
  this._startIndex.set(undefined);
588
- }, this.inputs.typeaheadDelay() * 1000);
567
+ }, this.inputs.typeaheadDelay());
589
568
  return true;
590
569
  }
591
570
  _getItem() {
@@ -843,8 +822,8 @@ class ListboxPattern {
843
822
  }
844
823
  validate() {
845
824
  const violations = [];
846
- if (!this.inputs.multi() && this.inputs.value().length > 1) {
847
- violations.push(`A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.value().join(', ')}`);
825
+ if (!this.inputs.multi() && this.inputs.values().length > 1) {
826
+ violations.push(`A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.values().join(', ')}`);
848
827
  }
849
828
  return violations;
850
829
  }
@@ -889,7 +868,7 @@ class OptionPattern {
889
868
  value;
890
869
  index = computed(() => this.listbox()?.inputs.items().indexOf(this) ?? -1);
891
870
  active = computed(() => this.listbox()?.inputs.activeItem() === this);
892
- selected = computed(() => this.listbox()?.inputs.value().includes(this.value()));
871
+ selected = computed(() => this.listbox()?.inputs.values().includes(this.value()));
893
872
  selectable = () => true;
894
873
  disabled;
895
874
  searchTerm;
@@ -930,6 +909,7 @@ class ComboboxListboxPattern extends ListboxPattern {
930
909
  focus = (item, opts) => {
931
910
  this.listBehavior.goto(item, opts);
932
911
  };
912
+ getActiveItem = () => this.inputs.activeItem();
933
913
  next = () => this.listBehavior.next();
934
914
  prev = () => this.listBehavior.prev();
935
915
  last = () => this.listBehavior.last();
@@ -941,7 +921,7 @@ class ComboboxListboxPattern extends ListboxPattern {
941
921
  getItem = e => this._getItem(e);
942
922
  getSelectedItems = () => {
943
923
  const items = [];
944
- for (const value of this.inputs.value()) {
924
+ for (const value of this.inputs.values()) {
945
925
  const item = this.items().find(i => i.value() === value);
946
926
  if (item) {
947
927
  items.push(item);
@@ -949,17 +929,21 @@ class ComboboxListboxPattern extends ListboxPattern {
949
929
  }
950
930
  return items;
951
931
  };
952
- setValue = value => this.inputs.value.set(value ? [value] : []);
932
+ setValue = value => this.inputs.values.set(value ? [value] : []);
953
933
  }
954
934
 
955
935
  class MenuPattern {
956
936
  inputs;
957
937
  id;
958
938
  role = () => 'menu';
959
- isVisible = computed(() => this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true);
939
+ disabled = () => this.inputs.disabled();
940
+ visible = computed(() => this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true);
960
941
  listBehavior;
961
942
  isFocused = signal(false);
962
943
  hasBeenFocused = signal(false);
944
+ _openTimeout;
945
+ _closeTimeout;
946
+ tabIndex = () => this.listBehavior.tabIndex();
963
947
  shouldFocus = computed(() => {
964
948
  const root = this.root();
965
949
  if (root instanceof MenuTriggerPattern) {
@@ -1000,40 +984,67 @@ class MenuPattern {
1000
984
  this.id = inputs.id;
1001
985
  this.listBehavior = new List({
1002
986
  ...inputs,
1003
- value: signal([]),
1004
- disabled: () => false
987
+ values: signal([])
1005
988
  });
1006
989
  }
1007
990
  setDefaultState() {
1008
991
  if (!this.inputs.parent()) {
1009
- this.inputs.activeItem.set(this.inputs.items()[0]);
992
+ this.listBehavior.goto(this.inputs.items()[0], {
993
+ focusElement: false
994
+ });
1010
995
  }
1011
996
  }
1012
997
  onKeydown(event) {
1013
998
  this.keydownManager().handle(event);
1014
999
  }
1015
1000
  onMouseOver(event) {
1016
- if (!this.isVisible()) {
1001
+ if (!this.visible()) {
1017
1002
  return;
1018
1003
  }
1019
1004
  const item = this.inputs.items().find(i => i.element()?.contains(event.target));
1020
1005
  if (!item) {
1021
1006
  return;
1022
1007
  }
1008
+ const parent = this.inputs.parent();
1023
1009
  const activeItem = this?.inputs.activeItem();
1010
+ if (parent instanceof MenuItemPattern) {
1011
+ const grandparent = parent.inputs.parent();
1012
+ if (grandparent instanceof MenuPattern) {
1013
+ grandparent._clearTimeouts();
1014
+ grandparent.listBehavior.goto(parent, {
1015
+ focusElement: false
1016
+ });
1017
+ }
1018
+ }
1024
1019
  if (activeItem && activeItem !== item) {
1025
- activeItem.close();
1020
+ this._closeItem(activeItem);
1026
1021
  }
1027
- if (item.expanded() && item.submenu()?.inputs.activeItem()) {
1028
- item.submenu()?.inputs.activeItem()?.close();
1029
- item.submenu()?.listBehavior.unfocus();
1022
+ if (item.expanded()) {
1023
+ this._clearCloseTimeout();
1030
1024
  }
1031
- item.open();
1025
+ this._openItem(item);
1032
1026
  this.listBehavior.goto(item, {
1033
1027
  focusElement: this.shouldFocus()
1034
1028
  });
1035
1029
  }
1030
+ _closeItem(item) {
1031
+ this._clearOpenTimeout();
1032
+ if (!this._closeTimeout) {
1033
+ this._closeTimeout = setTimeout(() => {
1034
+ item.close();
1035
+ this._closeTimeout = undefined;
1036
+ }, this.inputs.expansionDelay());
1037
+ }
1038
+ }
1039
+ _openItem(item) {
1040
+ this._clearOpenTimeout();
1041
+ this._openTimeout = setTimeout(() => {
1042
+ item.open();
1043
+ this._openTimeout = undefined;
1044
+ }, this.inputs.expansionDelay());
1045
+ }
1036
1046
  onMouseOut(event) {
1047
+ this._clearOpenTimeout();
1037
1048
  if (this.isFocused()) {
1038
1049
  return;
1039
1050
  }
@@ -1082,7 +1093,7 @@ class MenuPattern {
1082
1093
  return;
1083
1094
  }
1084
1095
  }
1085
- if (this.isVisible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
1096
+ if (this.visible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
1086
1097
  this.isFocused.set(false);
1087
1098
  this.inputs.parent()?.close();
1088
1099
  }
@@ -1153,6 +1164,9 @@ class MenuPattern {
1153
1164
  root.next();
1154
1165
  }
1155
1166
  }
1167
+ close() {
1168
+ this.inputs.parent()?.close();
1169
+ }
1156
1170
  closeAll() {
1157
1171
  const root = this.root();
1158
1172
  if (root instanceof MenuTriggerPattern) {
@@ -1169,10 +1183,27 @@ class MenuPattern {
1169
1183
  });
1170
1184
  }
1171
1185
  }
1186
+ _clearTimeouts() {
1187
+ this._clearOpenTimeout();
1188
+ this._clearCloseTimeout();
1189
+ }
1190
+ _clearOpenTimeout() {
1191
+ if (this._openTimeout) {
1192
+ clearTimeout(this._openTimeout);
1193
+ this._openTimeout = undefined;
1194
+ }
1195
+ }
1196
+ _clearCloseTimeout() {
1197
+ if (this._closeTimeout) {
1198
+ clearTimeout(this._closeTimeout);
1199
+ this._closeTimeout = undefined;
1200
+ }
1201
+ }
1172
1202
  }
1173
1203
  class MenuBarPattern {
1174
1204
  inputs;
1175
1205
  listBehavior;
1206
+ tabIndex = () => this.listBehavior.tabIndex();
1176
1207
  _nextKey = computed(() => {
1177
1208
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1178
1209
  });
@@ -1183,6 +1214,7 @@ class MenuBarPattern {
1183
1214
  typeaheadRegexp = /^.$/;
1184
1215
  isFocused = signal(false);
1185
1216
  hasBeenFocused = signal(false);
1217
+ disabled = () => this.inputs.disabled();
1186
1218
  keydownManager = computed(() => {
1187
1219
  return new KeyboardEventManager().on(this._nextKey, () => this.next()).on(this._previousKey, () => this.prev()).on('End', () => this.listBehavior.last()).on('Home', () => this.listBehavior.first()).on('Enter', () => this.inputs.activeItem()?.open({
1188
1220
  first: true
@@ -1196,10 +1228,7 @@ class MenuBarPattern {
1196
1228
  });
1197
1229
  constructor(inputs) {
1198
1230
  this.inputs = inputs;
1199
- this.listBehavior = new List({
1200
- ...inputs,
1201
- disabled: () => false
1202
- });
1231
+ this.listBehavior = new List(inputs);
1203
1232
  }
1204
1233
  setDefaultState() {
1205
1234
  this.inputs.activeItem.set(this.inputs.items()[0]);
@@ -1277,10 +1306,12 @@ class MenuBarPattern {
1277
1306
  class MenuTriggerPattern {
1278
1307
  inputs;
1279
1308
  expanded = signal(false);
1309
+ hasBeenFocused = signal(false);
1280
1310
  role = () => 'button';
1281
1311
  hasPopup = () => true;
1282
1312
  menu;
1283
1313
  tabIndex = computed(() => this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0);
1314
+ disabled = () => this.inputs.disabled();
1284
1315
  keydownManager = computed(() => {
1285
1316
  return new KeyboardEventManager().on(' ', () => this.open({
1286
1317
  first: true
@@ -1299,12 +1330,19 @@ class MenuTriggerPattern {
1299
1330
  this.menu = this.inputs.menu;
1300
1331
  }
1301
1332
  onKeydown(event) {
1302
- this.keydownManager().handle(event);
1333
+ if (!this.inputs.disabled()) {
1334
+ this.keydownManager().handle(event);
1335
+ }
1303
1336
  }
1304
1337
  onClick() {
1305
- this.expanded() ? this.close() : this.open({
1306
- first: true
1307
- });
1338
+ if (!this.inputs.disabled()) {
1339
+ this.expanded() ? this.close() : this.open({
1340
+ first: true
1341
+ });
1342
+ }
1343
+ }
1344
+ onFocusIn() {
1345
+ this.hasBeenFocused.set(true);
1308
1346
  }
1309
1347
  onFocusOut(event) {
1310
1348
  const element = this.inputs.element();
@@ -1340,10 +1378,11 @@ class MenuItemPattern {
1340
1378
  inputs;
1341
1379
  value;
1342
1380
  id;
1343
- disabled;
1381
+ disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled();
1344
1382
  searchTerm;
1345
1383
  element;
1346
- isActive = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1384
+ active = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1385
+ hasBeenFocused = signal(false);
1347
1386
  tabIndex = computed(() => {
1348
1387
  if (this.submenu() && this.submenu()?.inputs.activeItem()) {
1349
1388
  return -1;
@@ -1363,12 +1402,14 @@ class MenuItemPattern {
1363
1402
  this.id = inputs.id;
1364
1403
  this.value = inputs.value;
1365
1404
  this.element = inputs.element;
1366
- this.disabled = inputs.disabled;
1367
1405
  this.submenu = this.inputs.submenu;
1368
1406
  this.searchTerm = inputs.searchTerm;
1369
1407
  this.selectable = computed(() => !this.submenu());
1370
1408
  }
1371
1409
  open(opts) {
1410
+ if (this.disabled()) {
1411
+ return;
1412
+ }
1372
1413
  this._expanded.set(true);
1373
1414
  if (opts?.first) {
1374
1415
  this.submenu()?.first();
@@ -1388,8 +1429,15 @@ class MenuItemPattern {
1388
1429
  menuitem?._expanded.set(false);
1389
1430
  menuitem?.inputs.parent()?.listBehavior.unfocus();
1390
1431
  menuitems = menuitems.concat(menuitem?.submenu()?.inputs.items() ?? []);
1432
+ const parent = menuitem?.inputs.parent();
1433
+ if (parent instanceof MenuPattern) {
1434
+ parent._clearTimeouts();
1435
+ }
1391
1436
  }
1392
1437
  }
1438
+ onFocusIn() {
1439
+ this.hasBeenFocused.set(true);
1440
+ }
1393
1441
  }
1394
1442
 
1395
1443
  function convertGetterSetterToWritableSignalLike(getter, setter) {
@@ -1399,48 +1447,27 @@ function convertGetterSetterToWritableSignalLike(getter, setter) {
1399
1447
  });
1400
1448
  }
1401
1449
 
1402
- class ExpansionControl {
1403
- inputs;
1404
- isExpanded = computed(() => this.inputs.expansionManager.isExpanded(this));
1405
- isExpandable = computed(() => this.inputs.expansionManager.isExpandable(this));
1406
- constructor(inputs) {
1407
- this.inputs = inputs;
1408
- this.expansionId = inputs.expansionId;
1409
- this.expandable = inputs.expandable;
1410
- this.disabled = inputs.disabled;
1411
- }
1412
- open() {
1413
- this.inputs.expansionManager.open(this);
1414
- }
1415
- close() {
1416
- this.inputs.expansionManager.close(this);
1417
- }
1418
- toggle() {
1419
- this.inputs.expansionManager.toggle(this);
1420
- }
1421
- }
1422
1450
  class ListExpansion {
1423
1451
  inputs;
1424
- expandedIds;
1425
1452
  constructor(inputs) {
1426
1453
  this.inputs = inputs;
1427
- this.expandedIds = inputs.expandedIds;
1428
1454
  }
1429
1455
  open(item) {
1430
- if (!this.isExpandable(item)) return;
1431
- if (this.isExpanded(item)) return;
1456
+ if (!this.isExpandable(item)) return false;
1457
+ if (item.expanded()) return false;
1432
1458
  if (!this.inputs.multiExpandable()) {
1433
1459
  this.closeAll();
1434
1460
  }
1435
- this.expandedIds.update(ids => ids.concat(item.expansionId()));
1461
+ item.expanded.set(true);
1462
+ return true;
1436
1463
  }
1437
1464
  close(item) {
1438
- if (this.isExpandable(item)) {
1439
- this.expandedIds.update(ids => ids.filter(id => id !== item.expansionId()));
1440
- }
1465
+ if (!this.isExpandable(item)) return false;
1466
+ item.expanded.set(false);
1467
+ return true;
1441
1468
  }
1442
1469
  toggle(item) {
1443
- this.expandedIds().includes(item.expansionId()) ? this.close(item) : this.open(item);
1470
+ return item.expanded() ? this.close(item) : this.open(item);
1444
1471
  }
1445
1472
  openAll() {
1446
1473
  if (this.inputs.multiExpandable()) {
@@ -1457,9 +1484,6 @@ class ListExpansion {
1457
1484
  isExpandable(item) {
1458
1485
  return !this.inputs.disabled() && !item.disabled() && item.expandable();
1459
1486
  }
1460
- isExpanded(item) {
1461
- return this.expandedIds().includes(item.expansionId());
1462
- }
1463
1487
  }
1464
1488
 
1465
1489
  class LabelControl {
@@ -1484,47 +1508,35 @@ class LabelControl {
1484
1508
 
1485
1509
  class TabPattern {
1486
1510
  inputs;
1487
- expansion;
1488
- id;
1511
+ id = () => this.inputs.id();
1489
1512
  index = computed(() => this.inputs.tablist().inputs.items().indexOf(this));
1490
- value;
1491
- disabled;
1492
- element;
1493
- selectable = () => true;
1494
- searchTerm = () => '';
1495
- expandable = computed(() => this.expansion.expandable());
1496
- expansionId = computed(() => this.expansion.expansionId());
1497
- expanded = computed(() => this.expansion.isExpanded());
1513
+ value = () => this.inputs.value();
1514
+ disabled = () => this.inputs.disabled();
1515
+ element = () => this.inputs.element();
1516
+ expandable = () => true;
1517
+ expanded;
1498
1518
  active = computed(() => this.inputs.tablist().inputs.activeItem() === this);
1499
- selected = computed(() => !!this.inputs.tablist().inputs.value().includes(this.value()));
1500
- tabIndex = computed(() => this.inputs.tablist().listBehavior.getItemTabindex(this));
1519
+ selected = computed(() => this.inputs.tablist().selectedTab() === this);
1520
+ tabIndex = computed(() => this.inputs.tablist().focusBehavior.getItemTabIndex(this));
1501
1521
  controls = computed(() => this.inputs.tabpanel()?.id());
1502
1522
  constructor(inputs) {
1503
1523
  this.inputs = inputs;
1504
- this.id = inputs.id;
1505
- this.value = inputs.value;
1506
- this.disabled = inputs.disabled;
1507
- this.element = inputs.element;
1508
- this.expansion = new ExpansionControl({
1509
- ...inputs,
1510
- expansionId: inputs.value,
1511
- expandable: () => true,
1512
- expansionManager: inputs.tablist().expansionManager
1513
- });
1524
+ this.expanded = inputs.expanded;
1525
+ }
1526
+ open() {
1527
+ return this.inputs.tablist().open(this);
1514
1528
  }
1515
1529
  }
1516
1530
  class TabPanelPattern {
1517
1531
  inputs;
1518
- id;
1519
- value;
1532
+ id = () => this.inputs.id();
1533
+ value = () => this.inputs.value();
1520
1534
  labelManager;
1521
1535
  hidden = computed(() => this.inputs.tab()?.expanded() === false);
1522
1536
  tabIndex = computed(() => this.hidden() ? -1 : 0);
1523
1537
  labelledBy = computed(() => this.labelManager.labelledBy().length > 0 ? this.labelManager.labelledBy().join(' ') : undefined);
1524
1538
  constructor(inputs) {
1525
1539
  this.inputs = inputs;
1526
- this.id = inputs.id;
1527
- this.value = inputs.value;
1528
1540
  this.labelManager = new LabelControl({
1529
1541
  ...inputs,
1530
1542
  defaultLabelledBy: computed(() => this.inputs.tab() ? [this.inputs.tab().id()] : [])
@@ -1533,12 +1545,15 @@ class TabPanelPattern {
1533
1545
  }
1534
1546
  class TabListPattern {
1535
1547
  inputs;
1536
- listBehavior;
1537
- expansionManager;
1538
- orientation;
1539
- disabled;
1540
- tabIndex = computed(() => this.listBehavior.tabIndex());
1541
- activeDescendant = computed(() => this.listBehavior.activeDescendant());
1548
+ focusBehavior;
1549
+ navigationBehavior;
1550
+ expansionBehavior;
1551
+ activeTab = () => this.inputs.activeItem();
1552
+ selectedTab = signal(undefined);
1553
+ orientation = () => this.inputs.orientation();
1554
+ disabled = () => this.inputs.disabled();
1555
+ tabIndex = computed(() => this.focusBehavior.getListTabIndex());
1556
+ activeDescendant = computed(() => this.focusBehavior.getActiveDescendant());
1542
1557
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1543
1558
  prevKey = computed(() => {
1544
1559
  if (this.inputs.orientation() === 'vertical') {
@@ -1553,40 +1568,27 @@ class TabListPattern {
1553
1568
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1554
1569
  });
1555
1570
  keydown = computed(() => {
1556
- return new KeyboardEventManager().on(this.prevKey, () => this.listBehavior.prev({
1557
- select: this.followFocus()
1558
- })).on(this.nextKey, () => this.listBehavior.next({
1559
- select: this.followFocus()
1560
- })).on('Home', () => this.listBehavior.first({
1561
- select: this.followFocus()
1562
- })).on('End', () => this.listBehavior.last({
1563
- select: this.followFocus()
1564
- })).on(' ', () => this.listBehavior.select()).on('Enter', () => this.listBehavior.select());
1571
+ return new KeyboardEventManager().on(this.prevKey, () => this._navigate(() => this.navigationBehavior.prev(), this.followFocus())).on(this.nextKey, () => this._navigate(() => this.navigationBehavior.next(), this.followFocus())).on('Home', () => this._navigate(() => this.navigationBehavior.first(), this.followFocus())).on('End', () => this._navigate(() => this.navigationBehavior.last(), this.followFocus())).on(' ', () => this.open()).on('Enter', () => this.open());
1565
1572
  });
1566
1573
  pointerdown = computed(() => {
1567
- return new PointerEventManager().on(e => this.listBehavior.goto(this._getItem(e), {
1568
- select: true
1569
- }));
1574
+ return new PointerEventManager().on(e => this._navigate(() => this.navigationBehavior.goto(this._getItem(e)), true));
1570
1575
  });
1571
1576
  constructor(inputs) {
1572
1577
  this.inputs = inputs;
1573
- this.disabled = inputs.disabled;
1574
- this.orientation = inputs.orientation;
1575
- this.listBehavior = new List({
1578
+ this.focusBehavior = new ListFocus(inputs);
1579
+ this.navigationBehavior = new ListNavigation({
1576
1580
  ...inputs,
1577
- multi: () => false,
1578
- typeaheadDelay: () => 0
1581
+ focusManager: this.focusBehavior
1579
1582
  });
1580
- this.expansionManager = new ListExpansion({
1583
+ this.expansionBehavior = new ListExpansion({
1581
1584
  ...inputs,
1582
- multiExpandable: () => false,
1583
- expandedIds: this.inputs.value
1585
+ multiExpandable: () => false
1584
1586
  });
1585
1587
  }
1586
1588
  setDefaultState() {
1587
1589
  let firstItem;
1588
1590
  for (const item of this.inputs.items()) {
1589
- if (!this.listBehavior.isFocusable(item)) continue;
1591
+ if (!this.focusBehavior.isFocusable(item)) continue;
1590
1592
  if (firstItem === undefined) {
1591
1593
  firstItem = item;
1592
1594
  }
@@ -1609,6 +1611,24 @@ class TabListPattern {
1609
1611
  this.pointerdown().handle(event);
1610
1612
  }
1611
1613
  }
1614
+ open(tab) {
1615
+ tab ??= this.activeTab();
1616
+ if (typeof tab === 'string') {
1617
+ tab = this.inputs.items().find(t => t.value() === tab);
1618
+ }
1619
+ if (tab === undefined) return false;
1620
+ const success = this.expansionBehavior.open(tab);
1621
+ if (success) {
1622
+ this.selectedTab.set(tab);
1623
+ }
1624
+ return success;
1625
+ }
1626
+ _navigate(op, shouldExpand = false) {
1627
+ const success = op();
1628
+ if (success && shouldExpand) {
1629
+ this.open();
1630
+ }
1631
+ }
1612
1632
  _getItem(e) {
1613
1633
  if (!(e.target instanceof HTMLElement)) {
1614
1634
  return;
@@ -1702,7 +1722,6 @@ class ToolbarPattern {
1702
1722
  multi: () => true,
1703
1723
  focusMode: () => 'roving',
1704
1724
  selectionMode: () => 'explicit',
1705
- value: signal([]),
1706
1725
  typeaheadDelay: () => 0
1707
1726
  });
1708
1727
  }
@@ -1741,7 +1760,7 @@ class ToolbarWidgetPattern {
1741
1760
  value = () => this.inputs.value();
1742
1761
  selectable = () => true;
1743
1762
  index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1);
1744
- selected = computed(() => this.toolbar().listBehavior.inputs.value().includes(this.value()));
1763
+ selected = computed(() => this.toolbar().listBehavior.inputs.values().includes(this.value()));
1745
1764
  active = computed(() => this.toolbar().activeItem() === this);
1746
1765
  constructor(inputs) {
1747
1766
  this.inputs = inputs;
@@ -1765,82 +1784,45 @@ class ToolbarWidgetGroupPattern {
1765
1784
  const focusMode = () => 'roving';
1766
1785
  class AccordionGroupPattern {
1767
1786
  inputs;
1768
- navigation;
1769
- focusManager;
1770
- expansionManager;
1787
+ navigationBehavior;
1788
+ focusBehavior;
1789
+ expansionBehavior;
1771
1790
  constructor(inputs) {
1772
1791
  this.inputs = inputs;
1773
- this.wrap = inputs.wrap;
1774
- this.orientation = inputs.orientation;
1775
- this.textDirection = inputs.textDirection;
1776
- this.activeItem = inputs.activeItem;
1777
- this.disabled = inputs.disabled;
1778
- this.multiExpandable = inputs.multiExpandable;
1779
- this.items = inputs.items;
1780
- this.expandedIds = inputs.expandedIds;
1781
- this.softDisabled = inputs.softDisabled;
1782
- this.focusManager = new ListFocus({
1792
+ this.focusBehavior = new ListFocus({
1783
1793
  ...inputs,
1784
1794
  focusMode
1785
1795
  });
1786
- this.navigation = new ListNavigation({
1796
+ this.navigationBehavior = new ListNavigation({
1787
1797
  ...inputs,
1788
1798
  focusMode,
1789
- focusManager: this.focusManager
1799
+ focusManager: this.focusBehavior
1790
1800
  });
1791
- this.expansionManager = new ListExpansion({
1801
+ this.expansionBehavior = new ListExpansion({
1792
1802
  ...inputs
1793
1803
  });
1794
1804
  }
1795
- }
1796
- class AccordionTriggerPattern {
1797
- inputs;
1798
- expandable;
1799
- expansionId;
1800
- expanded;
1801
- expansionControl;
1802
- active = computed(() => this.inputs.accordionGroup().activeItem() === this);
1803
- controls = computed(() => this.inputs.accordionPanel()?.id());
1804
- tabIndex = computed(() => this.inputs.accordionGroup().focusManager.isFocusable(this) ? 0 : -1);
1805
- disabled = computed(() => this.inputs.disabled() || this.inputs.accordionGroup().disabled());
1806
- index = computed(() => this.inputs.accordionGroup().items().indexOf(this));
1807
- constructor(inputs) {
1808
- this.inputs = inputs;
1809
- this.id = inputs.id;
1810
- this.element = inputs.element;
1811
- this.value = inputs.value;
1812
- this.expansionControl = new ExpansionControl({
1813
- ...inputs,
1814
- expansionId: inputs.value,
1815
- expandable: () => true,
1816
- expansionManager: inputs.accordionGroup().expansionManager
1817
- });
1818
- this.expandable = this.expansionControl.isExpandable;
1819
- this.expansionId = this.expansionControl.expansionId;
1820
- this.expanded = this.expansionControl.isExpanded;
1821
- }
1822
1805
  prevKey = computed(() => {
1823
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1806
+ if (this.inputs.orientation() === 'vertical') {
1824
1807
  return 'ArrowUp';
1825
1808
  }
1826
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1809
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1827
1810
  });
1828
1811
  nextKey = computed(() => {
1829
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1812
+ if (this.inputs.orientation() === 'vertical') {
1830
1813
  return 'ArrowDown';
1831
1814
  }
1832
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1815
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1833
1816
  });
1834
1817
  keydown = computed(() => {
1835
- return new KeyboardEventManager().on(this.prevKey, () => this.inputs.accordionGroup().navigation.prev()).on(this.nextKey, () => this.inputs.accordionGroup().navigation.next()).on('Home', () => this.inputs.accordionGroup().navigation.first()).on('End', () => this.inputs.accordionGroup().navigation.last()).on(' ', () => this.expansionControl.toggle()).on('Enter', () => this.expansionControl.toggle());
1818
+ return new KeyboardEventManager().on(this.prevKey, () => this.navigationBehavior.prev()).on(this.nextKey, () => this.navigationBehavior.next()).on('Home', () => this.navigationBehavior.first()).on('End', () => this.navigationBehavior.last()).on(' ', () => this.toggle()).on('Enter', () => this.toggle());
1836
1819
  });
1837
1820
  pointerdown = computed(() => {
1838
1821
  return new PointerEventManager().on(e => {
1839
- const item = this._getItem(e);
1840
- if (item) {
1841
- this.inputs.accordionGroup().navigation.goto(item);
1842
- this.expansionControl.toggle();
1843
- }
1822
+ const item = this.inputs.getItem(e.target);
1823
+ if (!item) return;
1824
+ this.navigationBehavior.goto(item);
1825
+ this.expansionBehavior.toggle(item);
1844
1826
  });
1845
1827
  });
1846
1828
  onKeydown(event) {
@@ -1850,26 +1832,51 @@ class AccordionTriggerPattern {
1850
1832
  this.pointerdown().handle(event);
1851
1833
  }
1852
1834
  onFocus(event) {
1853
- const item = this._getItem(event);
1854
- if (item && this.inputs.accordionGroup().focusManager.isFocusable(item)) {
1855
- this.inputs.accordionGroup().focusManager.focus(item);
1856
- }
1835
+ const item = this.inputs.getItem(event.target);
1836
+ if (!item) return;
1837
+ if (!this.focusBehavior.isFocusable(item)) return;
1838
+ this.focusBehavior.focus(item);
1857
1839
  }
1858
- _getItem(e) {
1859
- if (!(e.target instanceof HTMLElement)) {
1860
- return;
1861
- }
1862
- const element = e.target.closest('[role="button"]');
1863
- return this.inputs.accordionGroup().items().find(i => i.element() === element);
1840
+ toggle() {
1841
+ const activeItem = this.inputs.activeItem();
1842
+ if (activeItem === undefined) return;
1843
+ this.expansionBehavior.toggle(activeItem);
1844
+ }
1845
+ }
1846
+ class AccordionTriggerPattern {
1847
+ inputs;
1848
+ id = () => this.inputs.id();
1849
+ element = () => this.inputs.element();
1850
+ expandable = () => true;
1851
+ expanded;
1852
+ active = computed(() => this.inputs.accordionGroup().inputs.activeItem() === this);
1853
+ controls = computed(() => this.inputs.accordionPanel()?.inputs.id());
1854
+ tabIndex = computed(() => this.inputs.accordionGroup().focusBehavior.isFocusable(this) ? 0 : -1);
1855
+ disabled = computed(() => this.inputs.disabled() || this.inputs.accordionGroup().inputs.disabled());
1856
+ hardDisabled = computed(() => this.disabled() && !this.inputs.accordionGroup().inputs.softDisabled());
1857
+ index = computed(() => this.inputs.accordionGroup().inputs.items().indexOf(this));
1858
+ constructor(inputs) {
1859
+ this.inputs = inputs;
1860
+ this.expanded = inputs.expanded;
1861
+ }
1862
+ open() {
1863
+ this.inputs.accordionGroup().expansionBehavior.open(this);
1864
+ }
1865
+ close() {
1866
+ this.inputs.accordionGroup().expansionBehavior.close(this);
1867
+ }
1868
+ toggle() {
1869
+ this.inputs.accordionGroup().expansionBehavior.toggle(this);
1864
1870
  }
1865
1871
  }
1866
1872
  class AccordionPanelPattern {
1867
1873
  inputs;
1874
+ id;
1875
+ accordionTrigger;
1868
1876
  hidden;
1869
1877
  constructor(inputs) {
1870
1878
  this.inputs = inputs;
1871
1879
  this.id = inputs.id;
1872
- this.value = inputs.value;
1873
1880
  this.accordionTrigger = inputs.accordionTrigger;
1874
1881
  this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false);
1875
1882
  }
@@ -1877,22 +1884,20 @@ class AccordionPanelPattern {
1877
1884
 
1878
1885
  class TreeItemPattern {
1879
1886
  inputs;
1880
- id;
1881
- value;
1882
- element;
1883
- disabled;
1884
- searchTerm;
1885
- tree;
1886
- parent;
1887
- children;
1887
+ id = () => this.inputs.id();
1888
+ value = () => this.inputs.value();
1889
+ element = () => this.inputs.element();
1890
+ disabled = () => this.inputs.disabled();
1891
+ searchTerm = () => this.inputs.searchTerm();
1892
+ tree = () => this.inputs.tree();
1893
+ parent = () => this.inputs.parent();
1894
+ children = () => this.inputs.children();
1888
1895
  index = computed(() => this.tree().visibleItems().indexOf(this));
1889
- expansionId;
1890
- expansionManager;
1891
- expansion;
1892
- expandable;
1893
- selectable;
1896
+ expansionBehavior;
1897
+ expandable = () => this.inputs.hasChildren();
1898
+ selectable = () => this.inputs.selectable();
1899
+ expanded;
1894
1900
  level = computed(() => this.parent().level() + 1);
1895
- expanded = computed(() => this.expansion.isExpanded());
1896
1901
  visible = computed(() => this.parent().expanded() && this.parent().visible());
1897
1902
  setsize = computed(() => this.parent().children().length);
1898
1903
  posinset = computed(() => this.parent().children().indexOf(this) + 1);
@@ -1905,7 +1910,7 @@ class TreeItemPattern {
1905
1910
  if (!this.selectable()) {
1906
1911
  return undefined;
1907
1912
  }
1908
- return this.tree().value().includes(this.value());
1913
+ return this.tree().values().includes(this.value());
1909
1914
  });
1910
1915
  current = computed(() => {
1911
1916
  if (!this.tree().nav()) {
@@ -1914,31 +1919,14 @@ class TreeItemPattern {
1914
1919
  if (!this.selectable()) {
1915
1920
  return undefined;
1916
1921
  }
1917
- return this.tree().value().includes(this.value()) ? this.tree().currentType() : undefined;
1922
+ return this.tree().values().includes(this.value()) ? this.tree().currentType() : undefined;
1918
1923
  });
1919
1924
  constructor(inputs) {
1920
1925
  this.inputs = inputs;
1921
- this.id = inputs.id;
1922
- this.value = inputs.value;
1923
- this.element = inputs.element;
1924
- this.disabled = inputs.disabled;
1925
- this.searchTerm = inputs.searchTerm;
1926
- this.expansionId = inputs.id;
1927
- this.tree = inputs.tree;
1928
- this.parent = inputs.parent;
1929
- this.children = inputs.children;
1930
- this.expandable = inputs.hasChildren;
1931
- this.selectable = inputs.selectable;
1932
- this.expansion = new ExpansionControl({
1933
- ...inputs,
1934
- expandable: this.expandable,
1935
- expansionId: this.expansionId,
1936
- expansionManager: this.parent().expansionManager
1937
- });
1938
- this.expansionManager = new ListExpansion({
1926
+ this.expanded = inputs.expanded;
1927
+ this.expansionBehavior = new ListExpansion({
1939
1928
  ...inputs,
1940
1929
  multiExpandable: () => true,
1941
- expandedIds: signal([]),
1942
1930
  items: this.children,
1943
1931
  disabled: computed(() => this.tree()?.disabled() ?? false)
1944
1932
  });
@@ -1947,7 +1935,7 @@ class TreeItemPattern {
1947
1935
  class TreePattern {
1948
1936
  inputs;
1949
1937
  listBehavior;
1950
- expansionManager;
1938
+ expansionBehavior;
1951
1939
  level = () => 0;
1952
1940
  expanded = () => true;
1953
1941
  visible = () => true;
@@ -1956,29 +1944,30 @@ class TreePattern {
1956
1944
  children = computed(() => this.inputs.allItems().filter(item => item.level() === this.level() + 1));
1957
1945
  visibleItems = computed(() => this.inputs.allItems().filter(item => item.visible()));
1958
1946
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1947
+ isRtl = computed(() => this.inputs.textDirection() === 'rtl');
1959
1948
  prevKey = computed(() => {
1960
1949
  if (this.inputs.orientation() === 'vertical') {
1961
1950
  return 'ArrowUp';
1962
1951
  }
1963
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1952
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
1964
1953
  });
1965
1954
  nextKey = computed(() => {
1966
1955
  if (this.inputs.orientation() === 'vertical') {
1967
1956
  return 'ArrowDown';
1968
1957
  }
1969
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1958
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
1970
1959
  });
1971
1960
  collapseKey = computed(() => {
1972
1961
  if (this.inputs.orientation() === 'horizontal') {
1973
1962
  return 'ArrowUp';
1974
1963
  }
1975
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1964
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
1976
1965
  });
1977
1966
  expandKey = computed(() => {
1978
1967
  if (this.inputs.orientation() === 'horizontal') {
1979
1968
  return 'ArrowDown';
1980
1969
  }
1981
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1970
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
1982
1971
  });
1983
1972
  dynamicSpaceKey = computed(() => this.listBehavior.isTyping() ? '' : ' ');
1984
1973
  typeaheadRegexp = /^.$/;
@@ -2064,45 +2053,33 @@ class TreePattern {
2064
2053
  }
2065
2054
  return manager;
2066
2055
  });
2067
- id;
2068
- nav;
2069
- currentType;
2070
- allItems;
2071
- disabled;
2072
- activeItem = signal(undefined);
2073
- softDisabled;
2074
- wrap;
2075
- orientation;
2076
- textDirection;
2077
- multi;
2078
- selectionMode;
2079
- typeaheadDelay;
2080
- value;
2056
+ id = () => this.inputs.id();
2057
+ element = () => this.inputs.element();
2058
+ nav = () => this.inputs.nav();
2059
+ currentType = () => this.inputs.currentType();
2060
+ allItems = () => this.inputs.allItems();
2061
+ focusMode = () => this.inputs.focusMode();
2062
+ disabled = () => this.inputs.disabled();
2063
+ activeItem;
2064
+ softDisabled = () => this.inputs.softDisabled();
2065
+ wrap = () => this.inputs.wrap();
2066
+ orientation = () => this.inputs.orientation();
2067
+ textDirection = () => this.textDirection();
2068
+ multi = computed(() => this.nav() ? false : this.inputs.multi());
2069
+ selectionMode = () => this.inputs.selectionMode();
2070
+ typeaheadDelay = () => this.inputs.typeaheadDelay();
2071
+ values;
2081
2072
  constructor(inputs) {
2082
2073
  this.inputs = inputs;
2083
- this.id = inputs.id;
2084
- this.nav = inputs.nav;
2085
- this.currentType = inputs.currentType;
2086
- this.allItems = inputs.allItems;
2087
- this.focusMode = inputs.focusMode;
2088
- this.disabled = inputs.disabled;
2089
2074
  this.activeItem = inputs.activeItem;
2090
- this.softDisabled = inputs.softDisabled;
2091
- this.wrap = inputs.wrap;
2092
- this.orientation = inputs.orientation;
2093
- this.textDirection = inputs.textDirection;
2094
- this.multi = computed(() => this.nav() ? false : this.inputs.multi());
2095
- this.selectionMode = inputs.selectionMode;
2096
- this.typeaheadDelay = inputs.typeaheadDelay;
2097
- this.value = inputs.value;
2075
+ this.values = inputs.values;
2098
2076
  this.listBehavior = new List({
2099
2077
  ...inputs,
2100
2078
  items: this.visibleItems,
2101
2079
  multi: this.multi
2102
2080
  });
2103
- this.expansionManager = new ListExpansion({
2081
+ this.expansionBehavior = new ListExpansion({
2104
2082
  multiExpandable: () => true,
2105
- expandedIds: signal([]),
2106
2083
  items: this.children,
2107
2084
  disabled: this.disabled
2108
2085
  });
@@ -2147,14 +2124,14 @@ class TreePattern {
2147
2124
  if (item.expanded()) {
2148
2125
  this.collapse();
2149
2126
  } else {
2150
- item.expansion.open();
2127
+ this.expansionBehavior.open(item);
2151
2128
  }
2152
2129
  }
2153
2130
  expand(opts) {
2154
2131
  const item = this.activeItem();
2155
2132
  if (!item || !this.listBehavior.isFocusable(item)) return;
2156
2133
  if (item.expandable() && !item.expanded()) {
2157
- item.expansion.open();
2134
+ this.expansionBehavior.open(item);
2158
2135
  } else if (item.expanded() && item.children().some(item => this.listBehavior.isFocusable(item))) {
2159
2136
  this.listBehavior.next(opts);
2160
2137
  }
@@ -2162,13 +2139,13 @@ class TreePattern {
2162
2139
  expandSiblings(item) {
2163
2140
  item ??= this.activeItem();
2164
2141
  const siblings = item?.parent()?.children();
2165
- siblings?.forEach(item => item.expansion.open());
2142
+ siblings?.forEach(item => this.expansionBehavior.open(item));
2166
2143
  }
2167
2144
  collapse(opts) {
2168
2145
  const item = this.activeItem();
2169
2146
  if (!item || !this.listBehavior.isFocusable(item)) return;
2170
2147
  if (item.expandable() && item.expanded()) {
2171
- item.expansion.close();
2148
+ this.expansionBehavior.close(item);
2172
2149
  } else if (item.parent() && item.parent() !== this) {
2173
2150
  const parentItem = item.parent();
2174
2151
  if (parentItem instanceof TreeItemPattern && this.listBehavior.isFocusable(parentItem)) {
@@ -2190,6 +2167,7 @@ class ComboboxTreePattern extends TreePattern {
2190
2167
  isItemCollapsible = () => this.inputs.activeItem()?.parent() instanceof TreeItemPattern;
2191
2168
  role = () => 'tree';
2192
2169
  activeId = computed(() => this.listBehavior.activeDescendant());
2170
+ getActiveItem = () => this.inputs.activeItem();
2193
2171
  items = computed(() => this.inputs.allItems());
2194
2172
  tabIndex = () => -1;
2195
2173
  constructor(inputs) {
@@ -2215,14 +2193,14 @@ class ComboboxTreePattern extends TreePattern {
2215
2193
  clearSelection = () => this.listBehavior.deselectAll();
2216
2194
  getItem = e => this._getItem(e);
2217
2195
  getSelectedItems = () => this.inputs.allItems().filter(item => item.selected());
2218
- setValue = value => this.inputs.value.set(value ? [value] : []);
2196
+ setValue = value => this.inputs.values.set(value ? [value] : []);
2219
2197
  expandItem = () => this.expand();
2220
2198
  collapseItem = () => this.collapse();
2221
2199
  isItemExpandable(item = this.inputs.activeItem()) {
2222
2200
  return item ? item.expandable() : false;
2223
2201
  }
2224
- expandAll = () => this.items().forEach(item => item.expansion.open());
2225
- collapseAll = () => this.items().forEach(item => item.expansion.close());
2202
+ expandAll = () => this.items().forEach(item => this.expansionBehavior.open(item));
2203
+ collapseAll = () => this.items().forEach(item => item.expansionBehavior.close(item));
2226
2204
  isItemSelectable = (item = this.inputs.activeItem()) => {
2227
2205
  return item ? item.selectable() : false;
2228
2206
  };
@@ -2334,5 +2312,5 @@ i0.ɵɵngDeclareClassMetadata({
2334
2312
  ctorParameters: () => []
2335
2313
  });
2336
2314
 
2337
- export { AccordionGroupPattern, AccordionPanelPattern, AccordionTriggerPattern, ComboboxListboxPattern, ComboboxPattern, ComboboxTreePattern, DeferredContent, DeferredContentAware, ListboxPattern, MenuBarPattern, MenuItemPattern, MenuPattern, MenuTriggerPattern, OptionPattern, TabListPattern, TabPanelPattern, TabPattern, ToolbarPattern, ToolbarWidgetGroupPattern, ToolbarWidgetPattern, TreeItemPattern, TreePattern, convertGetterSetterToWritableSignalLike };
2315
+ export { AccordionGroupPattern, AccordionPanelPattern, AccordionTriggerPattern, ComboboxDialogPattern, ComboboxListboxPattern, ComboboxPattern, ComboboxTreePattern, DeferredContent, DeferredContentAware, ListboxPattern, MenuBarPattern, MenuItemPattern, MenuPattern, MenuTriggerPattern, OptionPattern, TabListPattern, TabPanelPattern, TabPattern, ToolbarPattern, ToolbarWidgetGroupPattern, ToolbarWidgetPattern, TreeItemPattern, TreePattern, convertGetterSetterToWritableSignalLike };
2338
2316
  //# sourceMappingURL=private.mjs.map