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

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 +439 -55
  2. package/_adev_assets/aria-combobox.json +343 -35
  3. package/_adev_assets/aria-grid.json +345 -77
  4. package/_adev_assets/aria-listbox.json +113 -33
  5. package/_adev_assets/aria-menu.json +366 -141
  6. package/_adev_assets/aria-tabs.json +261 -77
  7. package/_adev_assets/aria-toolbar.json +72 -33
  8. package/_adev_assets/aria-tree.json +169 -26
  9. package/fesm2022/_widget-chunk.mjs +388 -57
  10. package/fesm2022/_widget-chunk.mjs.map +1 -1
  11. package/fesm2022/accordion.mjs +121 -68
  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 +132 -21
  16. package/fesm2022/combobox.mjs.map +1 -1
  17. package/fesm2022/grid.mjs +198 -61
  18. package/fesm2022/grid.mjs.map +1 -1
  19. package/fesm2022/listbox.mjs +42 -31
  20. package/fesm2022/listbox.mjs.map +1 -1
  21. package/fesm2022/menu.mjs +173 -67
  22. package/fesm2022/menu.mjs.map +1 -1
  23. package/fesm2022/private.mjs +415 -439
  24. package/fesm2022/private.mjs.map +1 -1
  25. package/fesm2022/tabs.mjs +86 -55
  26. package/fesm2022/tabs.mjs.map +1 -1
  27. package/fesm2022/toolbar.mjs +13 -25
  28. package/fesm2022/toolbar.mjs.map +1 -1
  29. package/fesm2022/tree.mjs +86 -44
  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 +133 -34
  34. package/types/combobox.d.ts +145 -12
  35. package/types/grid.d.ts +149 -31
  36. package/types/listbox.d.ts +58 -26
  37. package/types/menu.d.ts +130 -46
  38. package/types/private.d.ts +210 -250
  39. package/types/tabs.d.ts +119 -39
  40. package/types/toolbar.d.ts +49 -29
  41. package/types/tree.d.ts +113 -41
@@ -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();
@@ -233,11 +298,15 @@ class ComboboxPattern {
233
298
  }
234
299
  this.close();
235
300
  if (!this.readonly()) {
236
- this.inputs.popupControls()?.clearSelection();
301
+ popupControls?.clearSelection();
237
302
  }
238
303
  }
239
304
  open(nav) {
240
305
  this.expanded.set(true);
306
+ const popupControls = this.inputs.popupControls();
307
+ if (popupControls instanceof ComboboxDialogPattern) {
308
+ return;
309
+ }
241
310
  const inputEl = this.inputs.inputEl();
242
311
  if (inputEl && this.inputs.filterMode() === 'highlight') {
243
312
  const isHighlighting = inputEl.selectionStart !== inputEl.value.length;
@@ -253,21 +322,21 @@ class ComboboxPattern {
253
322
  this.last();
254
323
  }
255
324
  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();
325
+ const selectedItem = popupControls?.items().find(i => popupControls?.getSelectedItems().includes(i));
326
+ selectedItem ? popupControls?.focus(selectedItem) : this.first();
258
327
  }
259
328
  }
260
329
  next() {
261
- this._navigate(() => this.inputs.popupControls()?.next());
330
+ this._navigate(() => this.listControls()?.next());
262
331
  }
263
332
  prev() {
264
- this._navigate(() => this.inputs.popupControls()?.prev());
333
+ this._navigate(() => this.listControls()?.prev());
265
334
  }
266
335
  first() {
267
- this._navigate(() => this.inputs.popupControls()?.first());
336
+ this._navigate(() => this.listControls()?.first());
268
337
  }
269
338
  last() {
270
- this._navigate(() => this.inputs.popupControls()?.last());
339
+ this._navigate(() => this.listControls()?.last());
271
340
  }
272
341
  collapseItem() {
273
342
  const controls = this.inputs.popupControls();
@@ -278,7 +347,7 @@ class ComboboxPattern {
278
347
  this._navigate(() => controls?.expandItem());
279
348
  }
280
349
  select(opts = {}) {
281
- const controls = this.inputs.popupControls();
350
+ const controls = this.listControls();
282
351
  if (opts.item) {
283
352
  controls?.focus(opts.item, {
284
353
  focusElement: false
@@ -294,7 +363,7 @@ class ComboboxPattern {
294
363
  }
295
364
  commit() {
296
365
  const inputEl = this.inputs.inputEl();
297
- const selectedItems = this.inputs.popupControls()?.getSelectedItems();
366
+ const selectedItems = this.listControls()?.getSelectedItems();
298
367
  if (!inputEl) {
299
368
  return;
300
369
  }
@@ -311,7 +380,7 @@ class ComboboxPattern {
311
380
  this.select();
312
381
  }
313
382
  if (this.inputs.filterMode() === 'highlight') {
314
- const selectedItem = this.inputs.popupControls()?.getSelectedItems()[0];
383
+ const selectedItem = this.listControls()?.getSelectedItems()[0];
315
384
  if (!selectedItem) {
316
385
  return;
317
386
  }
@@ -324,116 +393,23 @@ class ComboboxPattern {
324
393
  }
325
394
  }
326
395
  }
327
-
328
- class ListFocus {
396
+ class ComboboxDialogPattern {
329
397
  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;
398
+ id = () => this.inputs.id();
399
+ role = () => 'dialog';
400
+ keydown = computed(() => {
401
+ return new KeyboardEventManager().on('Escape', () => this.inputs.combobox.close());
336
402
  });
337
403
  constructor(inputs) {
338
404
  this.inputs = inputs;
339
405
  }
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;
406
+ onKeydown(event) {
407
+ this.keydown().handle(event);
425
408
  }
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
- }
409
+ onClick(event) {
410
+ if (event.target === this.inputs.element()) {
411
+ this.inputs.combobox.close();
435
412
  }
436
- return;
437
413
  }
438
414
  }
439
415
 
@@ -441,7 +417,7 @@ class ListSelection {
441
417
  inputs;
442
418
  rangeStartIndex = signal(0);
443
419
  rangeEndIndex = signal(0);
444
- selectedItems = computed(() => this.inputs.items().filter(item => this.inputs.value().includes(item.value())));
420
+ selectedItems = computed(() => this.inputs.items().filter(item => this.inputs.values().includes(item.value())));
445
421
  constructor(inputs) {
446
422
  this.inputs = inputs;
447
423
  }
@@ -449,7 +425,7 @@ class ListSelection {
449
425
  anchor: true
450
426
  }) {
451
427
  item = item ?? this.inputs.focusManager.inputs.activeItem();
452
- if (!item || item.disabled() || !item.selectable() || this.inputs.value().includes(item.value())) {
428
+ if (!item || item.disabled() || !item.selectable() || this.inputs.values().includes(item.value())) {
453
429
  return;
454
430
  }
455
431
  if (!this.inputs.multi()) {
@@ -459,24 +435,24 @@ class ListSelection {
459
435
  if (opts.anchor) {
460
436
  this.beginRangeSelection(index);
461
437
  }
462
- this.inputs.value.update(values => values.concat(item.value()));
438
+ this.inputs.values.update(values => values.concat(item.value()));
463
439
  }
464
440
  deselect(item) {
465
441
  item = item ?? this.inputs.focusManager.inputs.activeItem();
466
442
  if (item && !item.disabled() && item.selectable()) {
467
- this.inputs.value.update(values => values.filter(value => value !== item.value()));
443
+ this.inputs.values.update(values => values.filter(value => value !== item.value()));
468
444
  }
469
445
  }
470
446
  toggle(item) {
471
447
  item = item ?? this.inputs.focusManager.inputs.activeItem();
472
448
  if (item) {
473
- this.inputs.value().includes(item.value()) ? this.deselect(item) : this.select(item);
449
+ this.inputs.values().includes(item.value()) ? this.deselect(item) : this.select(item);
474
450
  }
475
451
  }
476
452
  toggleOne() {
477
453
  const item = this.inputs.focusManager.inputs.activeItem();
478
454
  if (item) {
479
- this.inputs.value().includes(item.value()) ? this.deselect() : this.selectOne();
455
+ this.inputs.values().includes(item.value()) ? this.deselect() : this.selectOne();
480
456
  }
481
457
  }
482
458
  selectAll() {
@@ -491,14 +467,14 @@ class ListSelection {
491
467
  this.beginRangeSelection();
492
468
  }
493
469
  deselectAll() {
494
- for (const value of this.inputs.value()) {
470
+ for (const value of this.inputs.values()) {
495
471
  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));
472
+ item ? this.deselect(item) : this.inputs.values.update(values => values.filter(v => v !== value));
497
473
  }
498
474
  }
499
475
  toggleAll() {
500
476
  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();
477
+ selectableValues.every(i => this.inputs.values().includes(i)) ? this.deselectAll() : this.selectAll();
502
478
  }
503
479
  selectOne() {
504
480
  const item = this.inputs.focusManager.inputs.activeItem();
@@ -506,7 +482,7 @@ class ListSelection {
506
482
  return;
507
483
  }
508
484
  this.deselectAll();
509
- if (this.inputs.value().length > 0 && !this.inputs.multi()) {
485
+ if (this.inputs.values().length > 0 && !this.inputs.multi()) {
510
486
  return;
511
487
  }
512
488
  this.select();
@@ -585,7 +561,7 @@ class ListTypeahead {
585
561
  this.timeout = setTimeout(() => {
586
562
  this._query.set('');
587
563
  this._startIndex.set(undefined);
588
- }, this.inputs.typeaheadDelay() * 1000);
564
+ }, this.inputs.typeaheadDelay());
589
565
  return true;
590
566
  }
591
567
  _getItem() {
@@ -843,8 +819,8 @@ class ListboxPattern {
843
819
  }
844
820
  validate() {
845
821
  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(', ')}`);
822
+ if (!this.inputs.multi() && this.inputs.values().length > 1) {
823
+ violations.push(`A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.values().join(', ')}`);
848
824
  }
849
825
  return violations;
850
826
  }
@@ -889,7 +865,7 @@ class OptionPattern {
889
865
  value;
890
866
  index = computed(() => this.listbox()?.inputs.items().indexOf(this) ?? -1);
891
867
  active = computed(() => this.listbox()?.inputs.activeItem() === this);
892
- selected = computed(() => this.listbox()?.inputs.value().includes(this.value()));
868
+ selected = computed(() => this.listbox()?.inputs.values().includes(this.value()));
893
869
  selectable = () => true;
894
870
  disabled;
895
871
  searchTerm;
@@ -930,6 +906,7 @@ class ComboboxListboxPattern extends ListboxPattern {
930
906
  focus = (item, opts) => {
931
907
  this.listBehavior.goto(item, opts);
932
908
  };
909
+ getActiveItem = () => this.inputs.activeItem();
933
910
  next = () => this.listBehavior.next();
934
911
  prev = () => this.listBehavior.prev();
935
912
  last = () => this.listBehavior.last();
@@ -941,7 +918,7 @@ class ComboboxListboxPattern extends ListboxPattern {
941
918
  getItem = e => this._getItem(e);
942
919
  getSelectedItems = () => {
943
920
  const items = [];
944
- for (const value of this.inputs.value()) {
921
+ for (const value of this.inputs.values()) {
945
922
  const item = this.items().find(i => i.value() === value);
946
923
  if (item) {
947
924
  items.push(item);
@@ -949,17 +926,21 @@ class ComboboxListboxPattern extends ListboxPattern {
949
926
  }
950
927
  return items;
951
928
  };
952
- setValue = value => this.inputs.value.set(value ? [value] : []);
929
+ setValue = value => this.inputs.values.set(value ? [value] : []);
953
930
  }
954
931
 
955
932
  class MenuPattern {
956
933
  inputs;
957
934
  id;
958
935
  role = () => 'menu';
959
- isVisible = computed(() => this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true);
936
+ disabled = () => this.inputs.disabled();
937
+ visible = computed(() => this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true);
960
938
  listBehavior;
961
939
  isFocused = signal(false);
962
940
  hasBeenFocused = signal(false);
941
+ _openTimeout;
942
+ _closeTimeout;
943
+ tabIndex = () => this.listBehavior.tabIndex();
963
944
  shouldFocus = computed(() => {
964
945
  const root = this.root();
965
946
  if (root instanceof MenuTriggerPattern) {
@@ -1000,40 +981,67 @@ class MenuPattern {
1000
981
  this.id = inputs.id;
1001
982
  this.listBehavior = new List({
1002
983
  ...inputs,
1003
- value: signal([]),
1004
- disabled: () => false
984
+ values: signal([])
1005
985
  });
1006
986
  }
1007
987
  setDefaultState() {
1008
988
  if (!this.inputs.parent()) {
1009
- this.inputs.activeItem.set(this.inputs.items()[0]);
989
+ this.listBehavior.goto(this.inputs.items()[0], {
990
+ focusElement: false
991
+ });
1010
992
  }
1011
993
  }
1012
994
  onKeydown(event) {
1013
995
  this.keydownManager().handle(event);
1014
996
  }
1015
997
  onMouseOver(event) {
1016
- if (!this.isVisible()) {
998
+ if (!this.visible()) {
1017
999
  return;
1018
1000
  }
1019
1001
  const item = this.inputs.items().find(i => i.element()?.contains(event.target));
1020
1002
  if (!item) {
1021
1003
  return;
1022
1004
  }
1005
+ const parent = this.inputs.parent();
1023
1006
  const activeItem = this?.inputs.activeItem();
1007
+ if (parent instanceof MenuItemPattern) {
1008
+ const grandparent = parent.inputs.parent();
1009
+ if (grandparent instanceof MenuPattern) {
1010
+ grandparent._clearTimeouts();
1011
+ grandparent.listBehavior.goto(parent, {
1012
+ focusElement: false
1013
+ });
1014
+ }
1015
+ }
1024
1016
  if (activeItem && activeItem !== item) {
1025
- activeItem.close();
1017
+ this._closeItem(activeItem);
1026
1018
  }
1027
- if (item.expanded() && item.submenu()?.inputs.activeItem()) {
1028
- item.submenu()?.inputs.activeItem()?.close();
1029
- item.submenu()?.listBehavior.unfocus();
1019
+ if (item.expanded()) {
1020
+ this._clearCloseTimeout();
1030
1021
  }
1031
- item.open();
1022
+ this._openItem(item);
1032
1023
  this.listBehavior.goto(item, {
1033
1024
  focusElement: this.shouldFocus()
1034
1025
  });
1035
1026
  }
1027
+ _closeItem(item) {
1028
+ this._clearOpenTimeout();
1029
+ if (!this._closeTimeout) {
1030
+ this._closeTimeout = setTimeout(() => {
1031
+ item.close();
1032
+ this._closeTimeout = undefined;
1033
+ }, this.inputs.expansionDelay());
1034
+ }
1035
+ }
1036
+ _openItem(item) {
1037
+ this._clearOpenTimeout();
1038
+ this._openTimeout = setTimeout(() => {
1039
+ item.open();
1040
+ this._openTimeout = undefined;
1041
+ }, this.inputs.expansionDelay());
1042
+ }
1036
1043
  onMouseOut(event) {
1044
+ this._clearOpenTimeout();
1037
1045
  if (this.isFocused()) {
1038
1046
  return;
1039
1047
  }
@@ -1082,7 +1090,7 @@ class MenuPattern {
1082
1090
  return;
1083
1091
  }
1084
1092
  }
1085
- if (this.isVisible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
1093
+ if (this.visible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
1086
1094
  this.isFocused.set(false);
1087
1095
  this.inputs.parent()?.close();
1088
1096
  }
@@ -1153,6 +1161,9 @@ class MenuPattern {
1153
1161
  root.next();
1154
1162
  }
1155
1163
  }
1164
+ close() {
1165
+ this.inputs.parent()?.close();
1166
+ }
1156
1167
  closeAll() {
1157
1168
  const root = this.root();
1158
1169
  if (root instanceof MenuTriggerPattern) {
@@ -1169,10 +1180,27 @@ class MenuPattern {
1169
1180
  });
1170
1181
  }
1171
1182
  }
1183
+ _clearTimeouts() {
1184
+ this._clearOpenTimeout();
1185
+ this._clearCloseTimeout();
1186
+ }
1187
+ _clearOpenTimeout() {
1188
+ if (this._openTimeout) {
1189
+ clearTimeout(this._openTimeout);
1190
+ this._openTimeout = undefined;
1191
+ }
1192
+ }
1193
+ _clearCloseTimeout() {
1194
+ if (this._closeTimeout) {
1195
+ clearTimeout(this._closeTimeout);
1196
+ this._closeTimeout = undefined;
1197
+ }
1198
+ }
1172
1199
  }
1173
1200
  class MenuBarPattern {
1174
1201
  inputs;
1175
1202
  listBehavior;
1203
+ tabIndex = () => this.listBehavior.tabIndex();
1176
1204
  _nextKey = computed(() => {
1177
1205
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1178
1206
  });
@@ -1183,6 +1211,7 @@ class MenuBarPattern {
1183
1211
  typeaheadRegexp = /^.$/;
1184
1212
  isFocused = signal(false);
1185
1213
  hasBeenFocused = signal(false);
1214
+ disabled = () => this.inputs.disabled();
1186
1215
  keydownManager = computed(() => {
1187
1216
  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
1217
  first: true
@@ -1196,10 +1225,7 @@ class MenuBarPattern {
1196
1225
  });
1197
1226
  constructor(inputs) {
1198
1227
  this.inputs = inputs;
1199
- this.listBehavior = new List({
1200
- ...inputs,
1201
- disabled: () => false
1202
- });
1228
+ this.listBehavior = new List(inputs);
1203
1229
  }
1204
1230
  setDefaultState() {
1205
1231
  this.inputs.activeItem.set(this.inputs.items()[0]);
@@ -1277,10 +1303,12 @@ class MenuBarPattern {
1277
1303
  class MenuTriggerPattern {
1278
1304
  inputs;
1279
1305
  expanded = signal(false);
1306
+ hasBeenFocused = signal(false);
1280
1307
  role = () => 'button';
1281
1308
  hasPopup = () => true;
1282
1309
  menu;
1283
1310
  tabIndex = computed(() => this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0);
1311
+ disabled = () => this.inputs.disabled();
1284
1312
  keydownManager = computed(() => {
1285
1313
  return new KeyboardEventManager().on(' ', () => this.open({
1286
1314
  first: true
@@ -1299,12 +1327,19 @@ class MenuTriggerPattern {
1299
1327
  this.menu = this.inputs.menu;
1300
1328
  }
1301
1329
  onKeydown(event) {
1302
- this.keydownManager().handle(event);
1330
+ if (!this.inputs.disabled()) {
1331
+ this.keydownManager().handle(event);
1332
+ }
1303
1333
  }
1304
1334
  onClick() {
1305
- this.expanded() ? this.close() : this.open({
1306
- first: true
1307
- });
1335
+ if (!this.inputs.disabled()) {
1336
+ this.expanded() ? this.close() : this.open({
1337
+ first: true
1338
+ });
1339
+ }
1340
+ }
1341
+ onFocusIn() {
1342
+ this.hasBeenFocused.set(true);
1308
1343
  }
1309
1344
  onFocusOut(event) {
1310
1345
  const element = this.inputs.element();
@@ -1340,10 +1375,11 @@ class MenuItemPattern {
1340
1375
  inputs;
1341
1376
  value;
1342
1377
  id;
1343
- disabled;
1378
+ disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled();
1344
1379
  searchTerm;
1345
1380
  element;
1346
- isActive = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1381
+ active = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1382
+ hasBeenFocused = signal(false);
1347
1383
  tabIndex = computed(() => {
1348
1384
  if (this.submenu() && this.submenu()?.inputs.activeItem()) {
1349
1385
  return -1;
@@ -1363,12 +1399,14 @@ class MenuItemPattern {
1363
1399
  this.id = inputs.id;
1364
1400
  this.value = inputs.value;
1365
1401
  this.element = inputs.element;
1366
- this.disabled = inputs.disabled;
1367
1402
  this.submenu = this.inputs.submenu;
1368
1403
  this.searchTerm = inputs.searchTerm;
1369
1404
  this.selectable = computed(() => !this.submenu());
1370
1405
  }
1371
1406
  open(opts) {
1407
+ if (this.disabled()) {
1408
+ return;
1409
+ }
1372
1410
  this._expanded.set(true);
1373
1411
  if (opts?.first) {
1374
1412
  this.submenu()?.first();
@@ -1388,8 +1426,15 @@ class MenuItemPattern {
1388
1426
  menuitem?._expanded.set(false);
1389
1427
  menuitem?.inputs.parent()?.listBehavior.unfocus();
1390
1428
  menuitems = menuitems.concat(menuitem?.submenu()?.inputs.items() ?? []);
1429
+ const parent = menuitem?.inputs.parent();
1430
+ if (parent instanceof MenuPattern) {
1431
+ parent._clearTimeouts();
1432
+ }
1391
1433
  }
1392
1434
  }
1435
+ onFocusIn() {
1436
+ this.hasBeenFocused.set(true);
1437
+ }
1393
1438
  }
1394
1439
 
1395
1440
  function convertGetterSetterToWritableSignalLike(getter, setter) {
@@ -1399,48 +1444,27 @@ function convertGetterSetterToWritableSignalLike(getter, setter) {
1399
1444
  });
1400
1445
  }
1401
1446
 
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
1447
  class ListExpansion {
1423
1448
  inputs;
1424
- expandedIds;
1425
1449
  constructor(inputs) {
1426
1450
  this.inputs = inputs;
1427
- this.expandedIds = inputs.expandedIds;
1428
1451
  }
1429
1452
  open(item) {
1430
- if (!this.isExpandable(item)) return;
1431
- if (this.isExpanded(item)) return;
1453
+ if (!this.isExpandable(item)) return false;
1454
+ if (item.expanded()) return false;
1432
1455
  if (!this.inputs.multiExpandable()) {
1433
1456
  this.closeAll();
1434
1457
  }
1435
- this.expandedIds.update(ids => ids.concat(item.expansionId()));
1458
+ item.expanded.set(true);
1459
+ return true;
1436
1460
  }
1437
1461
  close(item) {
1438
- if (this.isExpandable(item)) {
1439
- this.expandedIds.update(ids => ids.filter(id => id !== item.expansionId()));
1440
- }
1462
+ if (!this.isExpandable(item)) return false;
1463
+ item.expanded.set(false);
1464
+ return true;
1441
1465
  }
1442
1466
  toggle(item) {
1443
- this.expandedIds().includes(item.expansionId()) ? this.close(item) : this.open(item);
1467
+ return item.expanded() ? this.close(item) : this.open(item);
1444
1468
  }
1445
1469
  openAll() {
1446
1470
  if (this.inputs.multiExpandable()) {
@@ -1457,9 +1481,6 @@ class ListExpansion {
1457
1481
  isExpandable(item) {
1458
1482
  return !this.inputs.disabled() && !item.disabled() && item.expandable();
1459
1483
  }
1460
- isExpanded(item) {
1461
- return this.expandedIds().includes(item.expansionId());
1462
- }
1463
1484
  }
1464
1485
 
1465
1486
  class LabelControl {
@@ -1484,47 +1505,35 @@ class LabelControl {
1484
1505
 
1485
1506
  class TabPattern {
1486
1507
  inputs;
1487
- expansion;
1488
- id;
1508
+ id = () => this.inputs.id();
1489
1509
  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());
1510
+ value = () => this.inputs.value();
1511
+ disabled = () => this.inputs.disabled();
1512
+ element = () => this.inputs.element();
1513
+ expandable = () => true;
1514
+ expanded;
1498
1515
  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));
1516
+ selected = computed(() => this.inputs.tablist().selectedTab() === this);
1517
+ tabIndex = computed(() => this.inputs.tablist().focusBehavior.getItemTabIndex(this));
1501
1518
  controls = computed(() => this.inputs.tabpanel()?.id());
1502
1519
  constructor(inputs) {
1503
1520
  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
- });
1521
+ this.expanded = inputs.expanded;
1522
+ }
1523
+ open() {
1524
+ return this.inputs.tablist().open(this);
1514
1525
  }
1515
1526
  }
1516
1527
  class TabPanelPattern {
1517
1528
  inputs;
1518
- id;
1519
- value;
1529
+ id = () => this.inputs.id();
1530
+ value = () => this.inputs.value();
1520
1531
  labelManager;
1521
1532
  hidden = computed(() => this.inputs.tab()?.expanded() === false);
1522
1533
  tabIndex = computed(() => this.hidden() ? -1 : 0);
1523
1534
  labelledBy = computed(() => this.labelManager.labelledBy().length > 0 ? this.labelManager.labelledBy().join(' ') : undefined);
1524
1535
  constructor(inputs) {
1525
1536
  this.inputs = inputs;
1526
- this.id = inputs.id;
1527
- this.value = inputs.value;
1528
1537
  this.labelManager = new LabelControl({
1529
1538
  ...inputs,
1530
1539
  defaultLabelledBy: computed(() => this.inputs.tab() ? [this.inputs.tab().id()] : [])
@@ -1533,12 +1542,15 @@ class TabPanelPattern {
1533
1542
  }
1534
1543
  class TabListPattern {
1535
1544
  inputs;
1536
- listBehavior;
1537
- expansionManager;
1538
- orientation;
1539
- disabled;
1540
- tabIndex = computed(() => this.listBehavior.tabIndex());
1541
- activeDescendant = computed(() => this.listBehavior.activeDescendant());
1545
+ focusBehavior;
1546
+ navigationBehavior;
1547
+ expansionBehavior;
1548
+ activeTab = () => this.inputs.activeItem();
1549
+ selectedTab = signal(undefined);
1550
+ orientation = () => this.inputs.orientation();
1551
+ disabled = () => this.inputs.disabled();
1552
+ tabIndex = computed(() => this.focusBehavior.getListTabIndex());
1553
+ activeDescendant = computed(() => this.focusBehavior.getActiveDescendant());
1542
1554
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1543
1555
  prevKey = computed(() => {
1544
1556
  if (this.inputs.orientation() === 'vertical') {
@@ -1553,40 +1565,27 @@ class TabListPattern {
1553
1565
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1554
1566
  });
1555
1567
  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());
1568
+ 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
1569
  });
1566
1570
  pointerdown = computed(() => {
1567
- return new PointerEventManager().on(e => this.listBehavior.goto(this._getItem(e), {
1568
- select: true
1569
- }));
1571
+ return new PointerEventManager().on(e => this._navigate(() => this.navigationBehavior.goto(this._getItem(e)), true));
1570
1572
  });
1571
1573
  constructor(inputs) {
1572
1574
  this.inputs = inputs;
1573
- this.disabled = inputs.disabled;
1574
- this.orientation = inputs.orientation;
1575
- this.listBehavior = new List({
1575
+ this.focusBehavior = new ListFocus(inputs);
1576
+ this.navigationBehavior = new ListNavigation({
1576
1577
  ...inputs,
1577
- multi: () => false,
1578
- typeaheadDelay: () => 0
1578
+ focusManager: this.focusBehavior
1579
1579
  });
1580
- this.expansionManager = new ListExpansion({
1580
+ this.expansionBehavior = new ListExpansion({
1581
1581
  ...inputs,
1582
- multiExpandable: () => false,
1583
- expandedIds: this.inputs.value
1582
+ multiExpandable: () => false
1584
1583
  });
1585
1584
  }
1586
1585
  setDefaultState() {
1587
1586
  let firstItem;
1588
1587
  for (const item of this.inputs.items()) {
1589
- if (!this.listBehavior.isFocusable(item)) continue;
1588
+ if (!this.focusBehavior.isFocusable(item)) continue;
1590
1589
  if (firstItem === undefined) {
1591
1590
  firstItem = item;
1592
1591
  }
@@ -1609,6 +1608,24 @@ class TabListPattern {
1609
1608
  this.pointerdown().handle(event);
1610
1609
  }
1611
1610
  }
1611
+ open(tab) {
1612
+ tab ??= this.activeTab();
1613
+ if (typeof tab === 'string') {
1614
+ tab = this.inputs.items().find(t => t.value() === tab);
1615
+ }
1616
+ if (tab === undefined) return false;
1617
+ const success = this.expansionBehavior.open(tab);
1618
+ if (success) {
1619
+ this.selectedTab.set(tab);
1620
+ }
1621
+ return success;
1622
+ }
1623
+ _navigate(op, shouldExpand = false) {
1624
+ const success = op();
1625
+ if (success && shouldExpand) {
1626
+ this.open();
1627
+ }
1628
+ }
1612
1629
  _getItem(e) {
1613
1630
  if (!(e.target instanceof HTMLElement)) {
1614
1631
  return;
@@ -1702,7 +1719,7 @@ class ToolbarPattern {
1702
1719
  multi: () => true,
1703
1720
  focusMode: () => 'roving',
1704
1721
  selectionMode: () => 'explicit',
1705
- value: signal([]),
1722
+ values: signal([]),
1706
1723
  typeaheadDelay: () => 0
1707
1724
  });
1708
1725
  }
@@ -1741,7 +1758,7 @@ class ToolbarWidgetPattern {
1741
1758
  value = () => this.inputs.value();
1742
1759
  selectable = () => true;
1743
1760
  index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1);
1744
- selected = computed(() => this.toolbar().listBehavior.inputs.value().includes(this.value()));
1761
+ selected = computed(() => this.toolbar().listBehavior.inputs.values().includes(this.value()));
1745
1762
  active = computed(() => this.toolbar().activeItem() === this);
1746
1763
  constructor(inputs) {
1747
1764
  this.inputs = inputs;
@@ -1765,82 +1782,45 @@ class ToolbarWidgetGroupPattern {
1765
1782
  const focusMode = () => 'roving';
1766
1783
  class AccordionGroupPattern {
1767
1784
  inputs;
1768
- navigation;
1769
- focusManager;
1770
- expansionManager;
1785
+ navigationBehavior;
1786
+ focusBehavior;
1787
+ expansionBehavior;
1771
1788
  constructor(inputs) {
1772
1789
  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({
1790
+ this.focusBehavior = new ListFocus({
1783
1791
  ...inputs,
1784
1792
  focusMode
1785
1793
  });
1786
- this.navigation = new ListNavigation({
1794
+ this.navigationBehavior = new ListNavigation({
1787
1795
  ...inputs,
1788
1796
  focusMode,
1789
- focusManager: this.focusManager
1797
+ focusManager: this.focusBehavior
1790
1798
  });
1791
- this.expansionManager = new ListExpansion({
1799
+ this.expansionBehavior = new ListExpansion({
1792
1800
  ...inputs
1793
1801
  });
1794
1802
  }
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
1803
  prevKey = computed(() => {
1823
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1804
+ if (this.inputs.orientation() === 'vertical') {
1824
1805
  return 'ArrowUp';
1825
1806
  }
1826
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1807
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1827
1808
  });
1828
1809
  nextKey = computed(() => {
1829
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1810
+ if (this.inputs.orientation() === 'vertical') {
1830
1811
  return 'ArrowDown';
1831
1812
  }
1832
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1813
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1833
1814
  });
1834
1815
  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());
1816
+ 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
1817
  });
1837
1818
  pointerdown = computed(() => {
1838
1819
  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
- }
1820
+ const item = this.inputs.getItem(e.target);
1821
+ if (!item) return;
1822
+ this.navigationBehavior.goto(item);
1823
+ this.expansionBehavior.toggle(item);
1844
1824
  });
1845
1825
  });
1846
1826
  onKeydown(event) {
@@ -1850,26 +1830,51 @@ class AccordionTriggerPattern {
1850
1830
  this.pointerdown().handle(event);
1851
1831
  }
1852
1832
  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
- }
1833
+ const item = this.inputs.getItem(event.target);
1834
+ if (!item) return;
1835
+ if (!this.focusBehavior.isFocusable(item)) return;
1836
+ this.focusBehavior.focus(item);
1857
1837
  }
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);
1838
+ toggle() {
1839
+ const activeItem = this.inputs.activeItem();
1840
+ if (activeItem === undefined) return;
1841
+ this.expansionBehavior.toggle(activeItem);
1842
+ }
1843
+ }
1844
+ class AccordionTriggerPattern {
1845
+ inputs;
1846
+ id = () => this.inputs.id();
1847
+ element = () => this.inputs.element();
1848
+ expandable = () => true;
1849
+ expanded;
1850
+ active = computed(() => this.inputs.accordionGroup().inputs.activeItem() === this);
1851
+ controls = computed(() => this.inputs.accordionPanel()?.inputs.id());
1852
+ tabIndex = computed(() => this.inputs.accordionGroup().focusBehavior.isFocusable(this) ? 0 : -1);
1853
+ disabled = computed(() => this.inputs.disabled() || this.inputs.accordionGroup().inputs.disabled());
1854
+ hardDisabled = computed(() => this.disabled() && !this.inputs.accordionGroup().inputs.softDisabled());
1855
+ index = computed(() => this.inputs.accordionGroup().inputs.items().indexOf(this));
1856
+ constructor(inputs) {
1857
+ this.inputs = inputs;
1858
+ this.expanded = inputs.expanded;
1859
+ }
1860
+ open() {
1861
+ this.inputs.accordionGroup().expansionBehavior.open(this);
1862
+ }
1863
+ close() {
1864
+ this.inputs.accordionGroup().expansionBehavior.close(this);
1865
+ }
1866
+ toggle() {
1867
+ this.inputs.accordionGroup().expansionBehavior.toggle(this);
1864
1868
  }
1865
1869
  }
1866
1870
  class AccordionPanelPattern {
1867
1871
  inputs;
1872
+ id;
1873
+ accordionTrigger;
1868
1874
  hidden;
1869
1875
  constructor(inputs) {
1870
1876
  this.inputs = inputs;
1871
1877
  this.id = inputs.id;
1872
- this.value = inputs.value;
1873
1878
  this.accordionTrigger = inputs.accordionTrigger;
1874
1879
  this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false);
1875
1880
  }
@@ -1877,22 +1882,20 @@ class AccordionPanelPattern {
1877
1882
 
1878
1883
  class TreeItemPattern {
1879
1884
  inputs;
1880
- id;
1881
- value;
1882
- element;
1883
- disabled;
1884
- searchTerm;
1885
- tree;
1886
- parent;
1887
- children;
1885
+ id = () => this.inputs.id();
1886
+ value = () => this.inputs.value();
1887
+ element = () => this.inputs.element();
1888
+ disabled = () => this.inputs.disabled();
1889
+ searchTerm = () => this.inputs.searchTerm();
1890
+ tree = () => this.inputs.tree();
1891
+ parent = () => this.inputs.parent();
1892
+ children = () => this.inputs.children();
1888
1893
  index = computed(() => this.tree().visibleItems().indexOf(this));
1889
- expansionId;
1890
- expansionManager;
1891
- expansion;
1892
- expandable;
1893
- selectable;
1894
+ expansionBehavior;
1895
+ expandable = () => this.inputs.hasChildren();
1896
+ selectable = () => this.inputs.selectable();
1897
+ expanded;
1894
1898
  level = computed(() => this.parent().level() + 1);
1895
- expanded = computed(() => this.expansion.isExpanded());
1896
1899
  visible = computed(() => this.parent().expanded() && this.parent().visible());
1897
1900
  setsize = computed(() => this.parent().children().length);
1898
1901
  posinset = computed(() => this.parent().children().indexOf(this) + 1);
@@ -1905,7 +1908,7 @@ class TreeItemPattern {
1905
1908
  if (!this.selectable()) {
1906
1909
  return undefined;
1907
1910
  }
1908
- return this.tree().value().includes(this.value());
1911
+ return this.tree().values().includes(this.value());
1909
1912
  });
1910
1913
  current = computed(() => {
1911
1914
  if (!this.tree().nav()) {
@@ -1914,31 +1917,14 @@ class TreeItemPattern {
1914
1917
  if (!this.selectable()) {
1915
1918
  return undefined;
1916
1919
  }
1917
- return this.tree().value().includes(this.value()) ? this.tree().currentType() : undefined;
1920
+ return this.tree().values().includes(this.value()) ? this.tree().currentType() : undefined;
1918
1921
  });
1919
1922
  constructor(inputs) {
1920
1923
  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({
1924
+ this.expanded = inputs.expanded;
1925
+ this.expansionBehavior = new ListExpansion({
1939
1926
  ...inputs,
1940
1927
  multiExpandable: () => true,
1941
- expandedIds: signal([]),
1942
1928
  items: this.children,
1943
1929
  disabled: computed(() => this.tree()?.disabled() ?? false)
1944
1930
  });
@@ -1947,7 +1933,7 @@ class TreeItemPattern {
1947
1933
  class TreePattern {
1948
1934
  inputs;
1949
1935
  listBehavior;
1950
- expansionManager;
1936
+ expansionBehavior;
1951
1937
  level = () => 0;
1952
1938
  expanded = () => true;
1953
1939
  visible = () => true;
@@ -1956,29 +1942,30 @@ class TreePattern {
1956
1942
  children = computed(() => this.inputs.allItems().filter(item => item.level() === this.level() + 1));
1957
1943
  visibleItems = computed(() => this.inputs.allItems().filter(item => item.visible()));
1958
1944
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1945
+ isRtl = computed(() => this.inputs.textDirection() === 'rtl');
1959
1946
  prevKey = computed(() => {
1960
1947
  if (this.inputs.orientation() === 'vertical') {
1961
1948
  return 'ArrowUp';
1962
1949
  }
1963
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1950
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
1964
1951
  });
1965
1952
  nextKey = computed(() => {
1966
1953
  if (this.inputs.orientation() === 'vertical') {
1967
1954
  return 'ArrowDown';
1968
1955
  }
1969
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1956
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
1970
1957
  });
1971
1958
  collapseKey = computed(() => {
1972
1959
  if (this.inputs.orientation() === 'horizontal') {
1973
1960
  return 'ArrowUp';
1974
1961
  }
1975
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1962
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
1976
1963
  });
1977
1964
  expandKey = computed(() => {
1978
1965
  if (this.inputs.orientation() === 'horizontal') {
1979
1966
  return 'ArrowDown';
1980
1967
  }
1981
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1968
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
1982
1969
  });
1983
1970
  dynamicSpaceKey = computed(() => this.listBehavior.isTyping() ? '' : ' ');
1984
1971
  typeaheadRegexp = /^.$/;
@@ -2064,45 +2051,33 @@ class TreePattern {
2064
2051
  }
2065
2052
  return manager;
2066
2053
  });
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;
2054
+ id = () => this.inputs.id();
2055
+ element = () => this.inputs.element();
2056
+ nav = () => this.inputs.nav();
2057
+ currentType = () => this.inputs.currentType();
2058
+ allItems = () => this.inputs.allItems();
2059
+ focusMode = () => this.inputs.focusMode();
2060
+ disabled = () => this.inputs.disabled();
2061
+ activeItem;
2062
+ softDisabled = () => this.inputs.softDisabled();
2063
+ wrap = () => this.inputs.wrap();
2064
+ orientation = () => this.inputs.orientation();
2065
+ textDirection = () => this.textDirection();
2066
+ multi = computed(() => this.nav() ? false : this.inputs.multi());
2067
+ selectionMode = () => this.inputs.selectionMode();
2068
+ typeaheadDelay = () => this.inputs.typeaheadDelay();
2069
+ values;
2081
2070
  constructor(inputs) {
2082
2071
  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
2072
  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;
2073
+ this.values = inputs.values;
2098
2074
  this.listBehavior = new List({
2099
2075
  ...inputs,
2100
2076
  items: this.visibleItems,
2101
2077
  multi: this.multi
2102
2078
  });
2103
- this.expansionManager = new ListExpansion({
2079
+ this.expansionBehavior = new ListExpansion({
2104
2080
  multiExpandable: () => true,
2105
- expandedIds: signal([]),
2106
2081
  items: this.children,
2107
2082
  disabled: this.disabled
2108
2083
  });
@@ -2147,14 +2122,14 @@ class TreePattern {
2147
2122
  if (item.expanded()) {
2148
2123
  this.collapse();
2149
2124
  } else {
2150
- item.expansion.open();
2125
+ this.expansionBehavior.open(item);
2151
2126
  }
2152
2127
  }
2153
2128
  expand(opts) {
2154
2129
  const item = this.activeItem();
2155
2130
  if (!item || !this.listBehavior.isFocusable(item)) return;
2156
2131
  if (item.expandable() && !item.expanded()) {
2157
- item.expansion.open();
2132
+ this.expansionBehavior.open(item);
2158
2133
  } else if (item.expanded() && item.children().some(item => this.listBehavior.isFocusable(item))) {
2159
2134
  this.listBehavior.next(opts);
2160
2135
  }
@@ -2162,13 +2137,13 @@ class TreePattern {
2162
2137
  expandSiblings(item) {
2163
2138
  item ??= this.activeItem();
2164
2139
  const siblings = item?.parent()?.children();
2165
- siblings?.forEach(item => item.expansion.open());
2140
+ siblings?.forEach(item => this.expansionBehavior.open(item));
2166
2141
  }
2167
2142
  collapse(opts) {
2168
2143
  const item = this.activeItem();
2169
2144
  if (!item || !this.listBehavior.isFocusable(item)) return;
2170
2145
  if (item.expandable() && item.expanded()) {
2171
- item.expansion.close();
2146
+ this.expansionBehavior.close(item);
2172
2147
  } else if (item.parent() && item.parent() !== this) {
2173
2148
  const parentItem = item.parent();
2174
2149
  if (parentItem instanceof TreeItemPattern && this.listBehavior.isFocusable(parentItem)) {
@@ -2190,6 +2165,7 @@ class ComboboxTreePattern extends TreePattern {
2190
2165
  isItemCollapsible = () => this.inputs.activeItem()?.parent() instanceof TreeItemPattern;
2191
2166
  role = () => 'tree';
2192
2167
  activeId = computed(() => this.listBehavior.activeDescendant());
2168
+ getActiveItem = () => this.inputs.activeItem();
2193
2169
  items = computed(() => this.inputs.allItems());
2194
2170
  tabIndex = () => -1;
2195
2171
  constructor(inputs) {
@@ -2215,14 +2191,14 @@ class ComboboxTreePattern extends TreePattern {
2215
2191
  clearSelection = () => this.listBehavior.deselectAll();
2216
2192
  getItem = e => this._getItem(e);
2217
2193
  getSelectedItems = () => this.inputs.allItems().filter(item => item.selected());
2218
- setValue = value => this.inputs.value.set(value ? [value] : []);
2194
+ setValue = value => this.inputs.values.set(value ? [value] : []);
2219
2195
  expandItem = () => this.expand();
2220
2196
  collapseItem = () => this.collapse();
2221
2197
  isItemExpandable(item = this.inputs.activeItem()) {
2222
2198
  return item ? item.expandable() : false;
2223
2199
  }
2224
- expandAll = () => this.items().forEach(item => item.expansion.open());
2225
- collapseAll = () => this.items().forEach(item => item.expansion.close());
2200
+ expandAll = () => this.items().forEach(item => this.expansionBehavior.open(item));
2201
+ collapseAll = () => this.items().forEach(item => item.expansionBehavior.close(item));
2226
2202
  isItemSelectable = (item = this.inputs.activeItem()) => {
2227
2203
  return item ? item.selectable() : false;
2228
2204
  };
@@ -2334,5 +2310,5 @@ i0.ɵɵngDeclareClassMetadata({
2334
2310
  ctorParameters: () => []
2335
2311
  });
2336
2312
 
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 };
2313
+ 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
2314
  //# sourceMappingURL=private.mjs.map