@angular/aria 21.0.0-rc.0 → 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 (48) hide show
  1. package/_adev_assets/aria-accordion.json +443 -59
  2. package/_adev_assets/aria-combobox.json +345 -37
  3. package/_adev_assets/aria-grid.json +408 -71
  4. package/_adev_assets/aria-listbox.json +115 -35
  5. package/_adev_assets/aria-menu.json +492 -167
  6. package/_adev_assets/aria-tabs.json +272 -88
  7. package/_adev_assets/aria-toolbar.json +151 -133
  8. package/_adev_assets/aria-tree.json +182 -35
  9. package/fesm2022/_widget-chunk.mjs +643 -190
  10. package/fesm2022/_widget-chunk.mjs.map +1 -1
  11. package/fesm2022/accordion.mjs +129 -77
  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 +140 -27
  16. package/fesm2022/combobox.mjs.map +1 -1
  17. package/fesm2022/grid.mjs +254 -68
  18. package/fesm2022/grid.mjs.map +1 -1
  19. package/fesm2022/listbox.mjs +54 -44
  20. package/fesm2022/listbox.mjs.map +1 -1
  21. package/fesm2022/menu.mjs +270 -108
  22. package/fesm2022/menu.mjs.map +1 -1
  23. package/fesm2022/private.mjs +752 -785
  24. package/fesm2022/private.mjs.map +1 -1
  25. package/fesm2022/tabs.mjs +101 -71
  26. package/fesm2022/tabs.mjs.map +1 -1
  27. package/fesm2022/toolbar.mjs +87 -64
  28. package/fesm2022/toolbar.mjs.map +1 -1
  29. package/fesm2022/tree.mjs +105 -60
  30. package/fesm2022/tree.mjs.map +1 -1
  31. package/package.json +2 -10
  32. package/types/_grid-chunk.d.ts +326 -83
  33. package/types/accordion.d.ts +134 -35
  34. package/types/combobox.d.ts +146 -13
  35. package/types/grid.d.ts +159 -32
  36. package/types/listbox.d.ts +59 -28
  37. package/types/menu.d.ts +151 -55
  38. package/types/private.d.ts +449 -567
  39. package/types/tabs.d.ts +121 -41
  40. package/types/toolbar.d.ts +74 -51
  41. package/types/tree.d.ts +116 -45
  42. package/_adev_assets/aria-radio-group.json +0 -389
  43. package/fesm2022/deferred-content.mjs +0 -99
  44. package/fesm2022/deferred-content.mjs.map +0 -1
  45. package/fesm2022/radio-group.mjs +0 -338
  46. package/fesm2022/radio-group.mjs.map +0 -1
  47. package/types/deferred-content.d.ts +0 -38
  48. package/types/radio-group.d.ts +0 -84
@@ -1,117 +1,199 @@
1
- import { signal, computed } from '@angular/core';
2
- import { KeyboardEventManager, PointerEventManager, Modifier } from './_widget-chunk.mjs';
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, model, Directive, inject, TemplateRef, ViewContainerRef, afterRenderEffect } from '@angular/core';
3
+ import { PointerEventManager, KeyboardEventManager, ListFocus, ListNavigation, Modifier } from './_widget-chunk.mjs';
3
4
  export { GridCellPattern, GridCellWidgetPattern, GridPattern, GridRowPattern } from './_widget-chunk.mjs';
4
5
 
5
6
  class ComboboxPattern {
6
7
  inputs;
7
8
  expanded = signal(false);
8
- 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
+ });
9
17
  highlightedItem = signal(undefined);
10
18
  isDeleting = false;
11
19
  isFocused = signal(false);
20
+ hasBeenFocused = signal(false);
12
21
  expandKey = computed(() => this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight');
13
22
  collapseKey = computed(() => this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft');
14
23
  popupId = computed(() => this.inputs.popupControls()?.id() || null);
15
24
  autocomplete = computed(() => this.inputs.filterMode() === 'highlight' ? 'both' : 'list');
16
25
  hasPopup = computed(() => this.inputs.popupControls()?.role() || null);
17
- isInteractive = computed(() => !this.inputs.disabled() && !this.inputs.readonly());
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
+ };
18
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
+ }
19
61
  if (!this.expanded()) {
20
- return new KeyboardEventManager().on('ArrowDown', () => this.open({
62
+ manager.on('ArrowDown', () => this.open({
21
63
  first: true
22
64
  })).on('ArrowUp', () => this.open({
23
65
  last: true
24
66
  }));
67
+ if (this.readonly()) {
68
+ manager.on('Enter', () => this.open({
69
+ selected: true
70
+ })).on(' ', () => this.open({
71
+ selected: true
72
+ }));
73
+ }
74
+ return manager;
25
75
  }
26
- const popupControls = this.inputs.popupControls();
27
- if (!popupControls) {
28
- return new KeyboardEventManager();
76
+ manager.on('ArrowDown', () => this.next()).on('ArrowUp', () => this.prev()).on('Home', () => this.first()).on('End', () => this.last());
77
+ if (this.readonly()) {
78
+ manager.on(' ', () => this.select({
79
+ commit: true,
80
+ close: !popupControls.multi()
81
+ }));
29
82
  }
30
- const manager = new KeyboardEventManager().on('ArrowDown', () => this.next()).on('ArrowUp', () => this.prev()).on('Home', () => this.first()).on('End', () => this.last()).on('Escape', () => {
31
- if (this.inputs.filterMode() === 'highlight' && popupControls.activeId()) {
32
- popupControls.unfocus();
33
- popupControls.clearSelection();
34
- const inputEl = this.inputs.inputEl();
35
- if (inputEl) {
36
- inputEl.value = this.inputs.inputValue();
37
- }
38
- } else {
39
- this.close();
40
- this.inputs.popupControls()?.clearSelection();
41
- }
42
- }).on('Enter', () => this.select({
43
- commit: true,
44
- close: true
45
- }));
46
- if (popupControls.role() === 'tree') {
47
- const treeControls = popupControls;
48
- if (treeControls.isItemExpandable() || treeControls.isItemCollapsible()) {
49
- manager.on(this.collapseKey(), () => this.collapseItem());
50
- }
51
- if (treeControls.isItemExpandable()) {
52
- manager.on(this.expandKey(), () => this.expandItem());
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()) {
93
+ manager.on('Enter', () => this.select({
94
+ commit: true,
95
+ close: true
96
+ }));
97
+ }
98
+ if (treeControls?.isItemExpandable()) {
99
+ manager.on(this.expandKey(), () => this.expandItem()).on(this.collapseKey(), () => this.collapseItem());
100
+ if (!treeControls.isItemSelectable()) {
101
+ manager.on('Enter', () => this.expandItem());
53
102
  }
54
103
  }
104
+ if (treeControls?.isItemCollapsible()) {
105
+ manager.on(this.collapseKey(), () => this.collapseItem());
106
+ }
55
107
  return manager;
56
108
  });
57
- pointerup = computed(() => new PointerEventManager().on(e => {
58
- 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);
59
122
  if (item) {
123
+ if (controls?.role() === 'tree') {
124
+ const treeControls = controls;
125
+ if (treeControls.isItemExpandable(item) && !treeControls.isItemSelectable(item)) {
126
+ treeControls.toggleExpansion(item);
127
+ this.inputs.inputEl()?.focus();
128
+ return;
129
+ }
130
+ }
60
131
  this.select({
61
132
  item,
62
133
  commit: true,
63
- close: true
134
+ close: !controls?.multi()
64
135
  });
65
136
  this.inputs.inputEl()?.focus();
66
137
  }
67
- if (e.target === this.inputs.inputEl()) {
68
- this.open();
69
- }
70
138
  }));
71
139
  constructor(inputs) {
72
140
  this.inputs = inputs;
73
141
  }
74
142
  onKeydown(event) {
75
- if (this.isInteractive()) {
143
+ if (!this.inputs.disabled()) {
76
144
  this.keydown().handle(event);
77
145
  }
78
146
  }
79
- onPointerup(event) {
80
- if (this.isInteractive()) {
81
- this.pointerup().handle(event);
147
+ onClick(event) {
148
+ if (!this.inputs.disabled()) {
149
+ this.click().handle(event);
82
150
  }
83
151
  }
84
152
  onInput(event) {
85
- if (!this.isInteractive()) {
153
+ if (this.inputs.disabled() || this.inputs.readonly()) {
86
154
  return;
87
155
  }
88
156
  const inputEl = this.inputs.inputEl();
89
157
  if (!inputEl) {
90
158
  return;
91
159
  }
160
+ const popupControls = this.inputs.popupControls();
161
+ if (popupControls instanceof ComboboxDialogPattern) {
162
+ return;
163
+ }
92
164
  this.open();
93
165
  this.inputs.inputValue?.set(inputEl.value);
94
166
  this.isDeleting = event instanceof InputEvent && !!event.inputType.match(/^delete/);
95
- if (this.inputs.filterMode() === 'manual') {
96
- const searchTerm = this.inputs.popupControls()?.getSelectedItem()?.searchTerm();
97
- if (searchTerm && this.inputs.inputValue() !== searchTerm) {
98
- this.inputs.popupControls()?.clearSelection();
99
- }
167
+ if (this.inputs.filterMode() === 'highlight' && !this.isDeleting) {
168
+ this.highlight();
100
169
  }
101
170
  }
102
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
+ }
103
176
  this.isFocused.set(true);
177
+ this.hasBeenFocused.set(true);
104
178
  }
105
179
  onFocusOut(event) {
106
- if (this.inputs.disabled() || this.inputs.readonly()) {
180
+ if (this.inputs.disabled()) {
181
+ return;
182
+ }
183
+ const popupControls = this.inputs.popupControls();
184
+ if (popupControls instanceof ComboboxDialogPattern) {
107
185
  return;
108
186
  }
109
187
  if (!(event.relatedTarget instanceof HTMLElement) || !this.inputs.containerEl()?.contains(event.relatedTarget)) {
110
188
  this.isFocused.set(false);
189
+ if (this.readonly()) {
190
+ this.close();
191
+ return;
192
+ }
111
193
  if (this.inputs.filterMode() !== 'manual') {
112
194
  this.commit();
113
195
  } else {
114
- 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);
115
197
  if (item) {
116
198
  this.select({
117
199
  item
@@ -122,12 +204,19 @@ class ComboboxPattern {
122
204
  }
123
205
  }
124
206
  firstMatch = computed(() => {
125
- if (this.inputs.popupControls()?.role() === 'listbox') {
126
- return this.inputs.popupControls()?.items()[0];
207
+ if (this.listControls()?.role() === 'listbox') {
208
+ return this.listControls()?.items()[0];
127
209
  }
128
- return this.inputs.popupControls()?.items().find(i => i.value() === this.inputs.firstMatch());
210
+ return this.listControls()?.items().find(i => i.value() === this.inputs.firstMatch());
129
211
  });
130
212
  onFilter() {
213
+ if (this.readonly()) {
214
+ return;
215
+ }
216
+ const popupControls = this.inputs.popupControls();
217
+ if (popupControls instanceof ComboboxDialogPattern) {
218
+ return;
219
+ }
131
220
  const isInitialRender = !this.inputs.inputValue?.().length && !this.isDeleting;
132
221
  if (isInitialRender) {
133
222
  return;
@@ -141,11 +230,11 @@ class ComboboxPattern {
141
230
  }
142
231
  const item = this.firstMatch();
143
232
  if (!item) {
144
- this.inputs.popupControls()?.clearSelection();
145
- this.inputs.popupControls()?.unfocus();
233
+ popupControls?.clearSelection();
234
+ popupControls?.unfocus();
146
235
  return;
147
236
  }
148
- this.inputs.popupControls()?.focus(item);
237
+ popupControls?.focus(item);
149
238
  if (this.inputs.filterMode() !== 'manual') {
150
239
  this.select({
151
240
  item
@@ -157,7 +246,8 @@ class ComboboxPattern {
157
246
  }
158
247
  highlight() {
159
248
  const inputEl = this.inputs.inputEl();
160
- const item = this.inputs.popupControls()?.getSelectedItem();
249
+ const selectedItems = this.listControls()?.getSelectedItems();
250
+ const item = selectedItems?.[0];
161
251
  if (!inputEl || !item) {
162
252
  return;
163
253
  }
@@ -168,30 +258,85 @@ class ComboboxPattern {
168
258
  this.highlightedItem.set(item);
169
259
  }
170
260
  }
171
- close() {
172
- this.expanded.set(false);
173
- this.inputs.popupControls()?.unfocus();
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
+ }
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
+ }
281
+ this.expanded.set(false);
282
+ popupControls?.unfocus();
283
+ return;
284
+ }
285
+ if (!this.expanded()) {
286
+ this.inputs.inputValue?.set('');
287
+ popupControls?.clearSelection();
288
+ const inputEl = this.inputs.inputEl();
289
+ if (inputEl) {
290
+ inputEl.value = '';
291
+ }
292
+ } else if (this.expanded()) {
293
+ this.close();
294
+ const selectedItem = popupControls?.getSelectedItems()?.[0];
295
+ if (selectedItem?.searchTerm() !== this.inputs.inputValue()) {
296
+ popupControls?.clearSelection();
297
+ }
298
+ }
299
+ this.close();
300
+ if (!this.readonly()) {
301
+ popupControls?.clearSelection();
302
+ }
174
303
  }
175
304
  open(nav) {
176
305
  this.expanded.set(true);
306
+ const popupControls = this.inputs.popupControls();
307
+ if (popupControls instanceof ComboboxDialogPattern) {
308
+ return;
309
+ }
310
+ const inputEl = this.inputs.inputEl();
311
+ if (inputEl && this.inputs.filterMode() === 'highlight') {
312
+ const isHighlighting = inputEl.selectionStart !== inputEl.value.length;
313
+ this.inputs.inputValue?.set(inputEl.value.slice(0, inputEl.selectionStart || 0));
314
+ if (!isHighlighting) {
315
+ this.highlightedItem.set(undefined);
316
+ }
317
+ }
177
318
  if (nav?.first) {
178
319
  this.first();
179
320
  }
180
321
  if (nav?.last) {
181
322
  this.last();
182
323
  }
324
+ if (nav?.selected) {
325
+ const selectedItem = popupControls?.items().find(i => popupControls?.getSelectedItems().includes(i));
326
+ selectedItem ? popupControls?.focus(selectedItem) : this.first();
327
+ }
183
328
  }
184
329
  next() {
185
- this._navigate(() => this.inputs.popupControls()?.next());
330
+ this._navigate(() => this.listControls()?.next());
186
331
  }
187
332
  prev() {
188
- this._navigate(() => this.inputs.popupControls()?.prev());
333
+ this._navigate(() => this.listControls()?.prev());
189
334
  }
190
335
  first() {
191
- this._navigate(() => this.inputs.popupControls()?.first());
336
+ this._navigate(() => this.listControls()?.first());
192
337
  }
193
338
  last() {
194
- this._navigate(() => this.inputs.popupControls()?.last());
339
+ this._navigate(() => this.listControls()?.last());
195
340
  }
196
341
  collapseItem() {
197
342
  const controls = this.inputs.popupControls();
@@ -202,7 +347,13 @@ class ComboboxPattern {
202
347
  this._navigate(() => controls?.expandItem());
203
348
  }
204
349
  select(opts = {}) {
205
- this.inputs.popupControls()?.select(opts.item);
350
+ const controls = this.listControls();
351
+ if (opts.item) {
352
+ controls?.focus(opts.item, {
353
+ focusElement: false
354
+ });
355
+ }
356
+ controls?.multi() ? controls.toggle(opts.item) : controls?.select(opts.item);
206
357
  if (opts.commit) {
207
358
  this.commit();
208
359
  }
@@ -212,14 +363,15 @@ class ComboboxPattern {
212
363
  }
213
364
  commit() {
214
365
  const inputEl = this.inputs.inputEl();
215
- const item = this.inputs.popupControls()?.getSelectedItem();
216
- if (inputEl && item) {
217
- inputEl.value = item.searchTerm();
218
- this.inputs.inputValue?.set(item.searchTerm());
219
- if (this.inputs.filterMode() === 'highlight') {
220
- const length = inputEl.value.length;
221
- inputEl.setSelectionRange(length, length);
222
- }
366
+ const selectedItems = this.listControls()?.getSelectedItems();
367
+ if (!inputEl) {
368
+ return;
369
+ }
370
+ inputEl.value = selectedItems?.map(i => i.searchTerm()).join(', ') || '';
371
+ this.inputs.inputValue?.set(inputEl.value);
372
+ if (this.inputs.filterMode() === 'highlight' && !this.readonly()) {
373
+ const length = inputEl.value.length;
374
+ inputEl.setSelectionRange(length, length);
223
375
  }
224
376
  }
225
377
  _navigate(operation) {
@@ -228,7 +380,7 @@ class ComboboxPattern {
228
380
  this.select();
229
381
  }
230
382
  if (this.inputs.filterMode() === 'highlight') {
231
- const selectedItem = this.inputs.popupControls()?.getSelectedItem();
383
+ const selectedItem = this.listControls()?.getSelectedItems()[0];
232
384
  if (!selectedItem) {
233
385
  return;
234
386
  }
@@ -241,110 +393,23 @@ class ComboboxPattern {
241
393
  }
242
394
  }
243
395
  }
244
-
245
- class ListFocus {
396
+ class ComboboxDialogPattern {
246
397
  inputs;
247
- prevActiveItem = signal(undefined);
248
- prevActiveIndex = computed(() => {
249
- return this.prevActiveItem() ? this.inputs.items().indexOf(this.prevActiveItem()) : -1;
250
- });
251
- activeIndex = computed(() => {
252
- 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());
253
402
  });
254
403
  constructor(inputs) {
255
404
  this.inputs = inputs;
256
405
  }
257
- isListDisabled() {
258
- return this.inputs.disabled() || this.inputs.items().every(i => i.disabled());
259
- }
260
- getActiveDescendant() {
261
- if (this.isListDisabled()) {
262
- return undefined;
263
- }
264
- if (this.inputs.focusMode() === 'roving') {
265
- return undefined;
266
- }
267
- return this.inputs.activeItem()?.id() ?? undefined;
268
- }
269
- getListTabindex() {
270
- if (this.isListDisabled()) {
271
- return 0;
272
- }
273
- return this.inputs.focusMode() === 'activedescendant' ? 0 : -1;
274
- }
275
- getItemTabindex(item) {
276
- if (this.isListDisabled()) {
277
- return -1;
278
- }
279
- if (this.inputs.focusMode() === 'activedescendant') {
280
- return -1;
281
- }
282
- return this.inputs.activeItem() === item ? 0 : -1;
283
- }
284
- focus(item, opts) {
285
- if (this.isListDisabled() || !this.isFocusable(item)) {
286
- return false;
287
- }
288
- this.prevActiveItem.set(this.inputs.activeItem());
289
- this.inputs.activeItem.set(item);
290
- if (opts?.focusElement || opts?.focusElement === undefined) {
291
- this.inputs.focusMode() === 'roving' ? item.element().focus() : this.inputs.element()?.focus();
292
- }
293
- return true;
294
- }
295
- isFocusable(item) {
296
- return !item.disabled() || !this.inputs.skipDisabled();
297
- }
298
- }
299
-
300
- class ListNavigation {
301
- inputs;
302
- constructor(inputs) {
303
- this.inputs = inputs;
304
- }
305
- goto(item, opts) {
306
- return item ? this.inputs.focusManager.focus(item, opts) : false;
307
- }
308
- next(opts) {
309
- return this._advance(1, opts);
310
- }
311
- peekNext() {
312
- return this._peek(1);
313
- }
314
- prev(opts) {
315
- return this._advance(-1, opts);
316
- }
317
- peekPrev() {
318
- return this._peek(-1);
319
- }
320
- first(opts) {
321
- const item = this.inputs.items().find(i => this.inputs.focusManager.isFocusable(i));
322
- return item ? this.goto(item, opts) : false;
323
- }
324
- last(opts) {
325
- const items = this.inputs.items();
326
- for (let i = items.length - 1; i >= 0; i--) {
327
- if (this.inputs.focusManager.isFocusable(items[i])) {
328
- return this.goto(items[i], opts);
329
- }
330
- }
331
- return false;
406
+ onKeydown(event) {
407
+ this.keydown().handle(event);
332
408
  }
333
- _advance(delta, opts) {
334
- const item = this._peek(delta);
335
- return item ? this.goto(item, opts) : false;
336
- }
337
- _peek(delta) {
338
- const items = this.inputs.items();
339
- const itemCount = items.length;
340
- const startIndex = this.inputs.focusManager.activeIndex();
341
- const step = i => this.inputs.wrap() ? (i + delta + itemCount) % itemCount : i + delta;
342
- for (let i = step(startIndex); i !== startIndex && i < itemCount && i >= 0; i = step(i)) {
343
- if (this.inputs.focusManager.isFocusable(items[i])) {
344
- return items[i];
345
- }
409
+ onClick(event) {
410
+ if (event.target === this.inputs.element()) {
411
+ this.inputs.combobox.close();
346
412
  }
347
- return;
348
413
  }
349
414
  }
350
415
 
@@ -352,7 +417,7 @@ class ListSelection {
352
417
  inputs;
353
418
  rangeStartIndex = signal(0);
354
419
  rangeEndIndex = signal(0);
355
- 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())));
356
421
  constructor(inputs) {
357
422
  this.inputs = inputs;
358
423
  }
@@ -360,7 +425,7 @@ class ListSelection {
360
425
  anchor: true
361
426
  }) {
362
427
  item = item ?? this.inputs.focusManager.inputs.activeItem();
363
- 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())) {
364
429
  return;
365
430
  }
366
431
  if (!this.inputs.multi()) {
@@ -370,24 +435,24 @@ class ListSelection {
370
435
  if (opts.anchor) {
371
436
  this.beginRangeSelection(index);
372
437
  }
373
- this.inputs.value.update(values => values.concat(item.value()));
438
+ this.inputs.values.update(values => values.concat(item.value()));
374
439
  }
375
440
  deselect(item) {
376
441
  item = item ?? this.inputs.focusManager.inputs.activeItem();
377
442
  if (item && !item.disabled() && item.selectable()) {
378
- this.inputs.value.update(values => values.filter(value => value !== item.value()));
443
+ this.inputs.values.update(values => values.filter(value => value !== item.value()));
379
444
  }
380
445
  }
381
- toggle() {
382
- const item = this.inputs.focusManager.inputs.activeItem();
446
+ toggle(item) {
447
+ item = item ?? this.inputs.focusManager.inputs.activeItem();
383
448
  if (item) {
384
- this.inputs.value().includes(item.value()) ? this.deselect() : this.select();
449
+ this.inputs.values().includes(item.value()) ? this.deselect(item) : this.select(item);
385
450
  }
386
451
  }
387
452
  toggleOne() {
388
453
  const item = this.inputs.focusManager.inputs.activeItem();
389
454
  if (item) {
390
- this.inputs.value().includes(item.value()) ? this.deselect() : this.selectOne();
455
+ this.inputs.values().includes(item.value()) ? this.deselect() : this.selectOne();
391
456
  }
392
457
  }
393
458
  selectAll() {
@@ -402,14 +467,14 @@ class ListSelection {
402
467
  this.beginRangeSelection();
403
468
  }
404
469
  deselectAll() {
405
- for (const value of this.inputs.value()) {
470
+ for (const value of this.inputs.values()) {
406
471
  const item = this.inputs.items().find(i => i.value() === value);
407
- 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));
408
473
  }
409
474
  }
410
475
  toggleAll() {
411
476
  const selectableValues = this.inputs.items().filter(i => !i.disabled() && i.selectable()).map(i => i.value());
412
- 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();
413
478
  }
414
479
  selectOne() {
415
480
  const item = this.inputs.focusManager.inputs.activeItem();
@@ -417,7 +482,7 @@ class ListSelection {
417
482
  return;
418
483
  }
419
484
  this.deselectAll();
420
- if (this.inputs.value().length > 0 && !this.inputs.multi()) {
485
+ if (this.inputs.values().length > 0 && !this.inputs.multi()) {
421
486
  return;
422
487
  }
423
488
  this.select();
@@ -496,7 +561,7 @@ class ListTypeahead {
496
561
  this.timeout = setTimeout(() => {
497
562
  this._query.set('');
498
563
  this._startIndex.set(undefined);
499
- }, this.inputs.typeaheadDelay() * 1000);
564
+ }, this.inputs.typeaheadDelay());
500
565
  return true;
501
566
  }
502
567
  _getItem() {
@@ -522,8 +587,8 @@ class List {
522
587
  typeaheadBehavior;
523
588
  focusBehavior;
524
589
  disabled = computed(() => this.focusBehavior.isListDisabled());
525
- activedescendant = computed(() => this.focusBehavior.getActiveDescendant());
526
- tabindex = computed(() => this.focusBehavior.getListTabindex());
590
+ activeDescendant = computed(() => this.focusBehavior.getActiveDescendant());
591
+ tabIndex = computed(() => this.focusBehavior.getListTabIndex());
527
592
  activeIndex = computed(() => this.focusBehavior.activeIndex());
528
593
  _anchorIndex = signal(0);
529
594
  _wrap = signal(true);
@@ -545,7 +610,7 @@ class List {
545
610
  });
546
611
  }
547
612
  getItemTabindex(item) {
548
- return this.focusBehavior.getItemTabindex(item);
613
+ return this.focusBehavior.getItemTabIndex(item);
549
614
  }
550
615
  first(opts) {
551
616
  this._navigate(opts, () => this.navigationBehavior.first(opts));
@@ -580,14 +645,14 @@ class List {
580
645
  selectOne() {
581
646
  this.selectionBehavior.selectOne();
582
647
  }
583
- deselect() {
584
- this.selectionBehavior.deselect();
648
+ deselect(item) {
649
+ this.selectionBehavior.deselect(item);
585
650
  }
586
651
  deselectAll() {
587
652
  this.selectionBehavior.deselectAll();
588
653
  }
589
- toggle() {
590
- this.selectionBehavior.toggle();
654
+ toggle(item) {
655
+ this.selectionBehavior.toggle(item);
591
656
  }
592
657
  toggleOne() {
593
658
  this.selectionBehavior.toggleOne();
@@ -636,8 +701,8 @@ class ListboxPattern {
636
701
  orientation;
637
702
  disabled = computed(() => this.listBehavior.disabled());
638
703
  readonly;
639
- tabindex = computed(() => this.listBehavior.tabindex());
640
- activedescendant = computed(() => this.listBehavior.activedescendant());
704
+ tabIndex = computed(() => this.listBehavior.tabIndex());
705
+ activeDescendant = computed(() => this.listBehavior.activeDescendant());
641
706
  multi;
642
707
  setsize = computed(() => this.inputs.items().length);
643
708
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
@@ -754,8 +819,8 @@ class ListboxPattern {
754
819
  }
755
820
  validate() {
756
821
  const violations = [];
757
- if (!this.inputs.multi() && this.inputs.value().length > 1) {
758
- 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(', ')}`);
759
824
  }
760
825
  return violations;
761
826
  }
@@ -800,12 +865,12 @@ class OptionPattern {
800
865
  value;
801
866
  index = computed(() => this.listbox()?.inputs.items().indexOf(this) ?? -1);
802
867
  active = computed(() => this.listbox()?.inputs.activeItem() === this);
803
- selected = computed(() => this.listbox()?.inputs.value().includes(this.value()));
868
+ selected = computed(() => this.listbox()?.inputs.values().includes(this.value()));
804
869
  selectable = () => true;
805
870
  disabled;
806
871
  searchTerm;
807
872
  listbox;
808
- tabindex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this));
873
+ tabIndex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this));
809
874
  element;
810
875
  constructor(args) {
811
876
  this.id = args.id;
@@ -821,12 +886,14 @@ class ComboboxListboxPattern extends ListboxPattern {
821
886
  inputs;
822
887
  id = computed(() => this.inputs.id());
823
888
  role = computed(() => 'listbox');
824
- activeId = computed(() => this.listBehavior.activedescendant());
889
+ activeId = computed(() => this.listBehavior.activeDescendant());
825
890
  items = computed(() => this.inputs.items());
826
- tabindex = () => -1;
891
+ tabIndex = () => -1;
892
+ multi = computed(() => {
893
+ return this.inputs.combobox()?.readonly() ? this.inputs.multi() : false;
894
+ });
827
895
  constructor(inputs) {
828
896
  if (inputs.combobox()) {
829
- inputs.multi = () => false;
830
897
  inputs.focusMode = () => 'activedescendant';
831
898
  inputs.element = inputs.combobox().inputs.inputEl;
832
899
  }
@@ -836,27 +903,44 @@ class ComboboxListboxPattern extends ListboxPattern {
836
903
  onKeydown(_) {}
837
904
  onPointerdown(_) {}
838
905
  setDefaultState() {}
839
- focus = item => this.listBehavior.goto(item);
906
+ focus = (item, opts) => {
907
+ this.listBehavior.goto(item, opts);
908
+ };
909
+ getActiveItem = () => this.inputs.activeItem();
840
910
  next = () => this.listBehavior.next();
841
911
  prev = () => this.listBehavior.prev();
842
912
  last = () => this.listBehavior.last();
843
913
  first = () => this.listBehavior.first();
844
914
  unfocus = () => this.listBehavior.unfocus();
845
915
  select = item => this.listBehavior.select(item);
916
+ toggle = item => this.listBehavior.toggle(item);
846
917
  clearSelection = () => this.listBehavior.deselectAll();
847
918
  getItem = e => this._getItem(e);
848
- getSelectedItem = () => this.inputs.items().find(i => i.selected());
849
- setValue = value => this.inputs.value.set(value ? [value] : []);
919
+ getSelectedItems = () => {
920
+ const items = [];
921
+ for (const value of this.inputs.values()) {
922
+ const item = this.items().find(i => i.value() === value);
923
+ if (item) {
924
+ items.push(item);
925
+ }
926
+ }
927
+ return items;
928
+ };
929
+ setValue = value => this.inputs.values.set(value ? [value] : []);
850
930
  }
851
931
 
852
932
  class MenuPattern {
853
933
  inputs;
854
934
  id;
855
935
  role = () => 'menu';
856
- 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);
857
938
  listBehavior;
858
939
  isFocused = signal(false);
859
940
  hasBeenFocused = signal(false);
941
+ _openTimeout;
942
+ _closeTimeout;
943
+ tabIndex = () => this.listBehavior.tabIndex();
860
944
  shouldFocus = computed(() => {
861
945
  const root = this.root();
862
946
  if (root instanceof MenuTriggerPattern) {
@@ -897,40 +981,67 @@ class MenuPattern {
897
981
  this.id = inputs.id;
898
982
  this.listBehavior = new List({
899
983
  ...inputs,
900
- value: signal([]),
901
- disabled: () => false
984
+ values: signal([])
902
985
  });
903
986
  }
904
987
  setDefaultState() {
905
988
  if (!this.inputs.parent()) {
906
- this.inputs.activeItem.set(this.inputs.items()[0]);
989
+ this.listBehavior.goto(this.inputs.items()[0], {
990
+ focusElement: false
991
+ });
907
992
  }
908
993
  }
909
994
  onKeydown(event) {
910
995
  this.keydownManager().handle(event);
911
996
  }
912
997
  onMouseOver(event) {
913
- if (!this.isVisible()) {
998
+ if (!this.visible()) {
914
999
  return;
915
1000
  }
916
1001
  const item = this.inputs.items().find(i => i.element()?.contains(event.target));
917
1002
  if (!item) {
918
1003
  return;
919
1004
  }
1005
+ const parent = this.inputs.parent();
920
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
+ }
921
1016
  if (activeItem && activeItem !== item) {
922
- activeItem.close();
1017
+ this._closeItem(activeItem);
923
1018
  }
924
- if (item.expanded() && item.submenu()?.inputs.activeItem()) {
925
- item.submenu()?.inputs.activeItem()?.close();
926
- item.submenu()?.listBehavior.unfocus();
1019
+ if (item.expanded()) {
1020
+ this._clearCloseTimeout();
927
1021
  }
928
- item.open();
1022
+ this._openItem(item);
929
1023
  this.listBehavior.goto(item, {
930
1024
  focusElement: this.shouldFocus()
931
1025
  });
932
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
+ }
933
1043
  onMouseOut(event) {
1044
+ this._clearOpenTimeout();
934
1045
  if (this.isFocused()) {
935
1046
  return;
936
1047
  }
@@ -974,12 +1085,12 @@ class MenuPattern {
974
1085
  if (parent instanceof MenuItemPattern) {
975
1086
  const grandparent = parent.inputs.parent();
976
1087
  const siblings = grandparent?.inputs.items().filter(i => i !== parent);
977
- const item = siblings?.find(i => i.element().contains(relatedTarget));
1088
+ const item = siblings?.find(i => i.element()?.contains(relatedTarget));
978
1089
  if (item) {
979
1090
  return;
980
1091
  }
981
1092
  }
982
- if (this.isVisible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
1093
+ if (this.visible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget)) {
983
1094
  this.isFocused.set(false);
984
1095
  this.inputs.parent()?.close();
985
1096
  }
@@ -1011,17 +1122,20 @@ class MenuPattern {
1011
1122
  const isMenu = root instanceof MenuPattern;
1012
1123
  const isMenuBar = root instanceof MenuBarPattern;
1013
1124
  const isMenuTrigger = root instanceof MenuTriggerPattern;
1014
- if (!item.submenu() && (isMenuTrigger || isMenuBar)) {
1125
+ if (!item.submenu() && isMenuTrigger) {
1015
1126
  root.close({
1016
1127
  refocus: true
1017
1128
  });
1018
- root?.inputs.onSubmit?.(item.value());
1129
+ }
1130
+ if (!item.submenu() && isMenuBar) {
1131
+ root.close();
1132
+ root?.inputs.onSelect?.(item.value());
1019
1133
  }
1020
1134
  if (!item.submenu() && isMenu) {
1021
1135
  root.inputs.activeItem()?.close({
1022
1136
  refocus: true
1023
1137
  });
1024
- root?.inputs.onSubmit?.(item.value());
1138
+ root?.inputs.onSelect?.(item.value());
1025
1139
  }
1026
1140
  }
1027
1141
  }
@@ -1047,6 +1161,9 @@ class MenuPattern {
1047
1161
  root.next();
1048
1162
  }
1049
1163
  }
1164
+ close() {
1165
+ this.inputs.parent()?.close();
1166
+ }
1050
1167
  closeAll() {
1051
1168
  const root = this.root();
1052
1169
  if (root instanceof MenuTriggerPattern) {
@@ -1063,10 +1180,27 @@ class MenuPattern {
1063
1180
  });
1064
1181
  }
1065
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
+ }
1066
1199
  }
1067
1200
  class MenuBarPattern {
1068
1201
  inputs;
1069
1202
  listBehavior;
1203
+ tabIndex = () => this.listBehavior.tabIndex();
1070
1204
  _nextKey = computed(() => {
1071
1205
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1072
1206
  });
@@ -1077,6 +1211,7 @@ class MenuBarPattern {
1077
1211
  typeaheadRegexp = /^.$/;
1078
1212
  isFocused = signal(false);
1079
1213
  hasBeenFocused = signal(false);
1214
+ disabled = () => this.inputs.disabled();
1080
1215
  keydownManager = computed(() => {
1081
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({
1082
1217
  first: true
@@ -1090,10 +1225,7 @@ class MenuBarPattern {
1090
1225
  });
1091
1226
  constructor(inputs) {
1092
1227
  this.inputs = inputs;
1093
- this.listBehavior = new List({
1094
- ...inputs,
1095
- disabled: () => false
1096
- });
1228
+ this.listBehavior = new List(inputs);
1097
1229
  }
1098
1230
  setDefaultState() {
1099
1231
  this.inputs.activeItem.set(this.inputs.items()[0]);
@@ -1171,10 +1303,12 @@ class MenuBarPattern {
1171
1303
  class MenuTriggerPattern {
1172
1304
  inputs;
1173
1305
  expanded = signal(false);
1306
+ hasBeenFocused = signal(false);
1174
1307
  role = () => 'button';
1175
1308
  hasPopup = () => true;
1176
- submenu;
1177
- tabindex = computed(() => this.expanded() && this.submenu()?.inputs.activeItem() ? -1 : 0);
1309
+ menu;
1310
+ tabIndex = computed(() => this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0);
1311
+ disabled = () => this.inputs.disabled();
1178
1312
  keydownManager = computed(() => {
1179
1313
  return new KeyboardEventManager().on(' ', () => this.open({
1180
1314
  first: true
@@ -1190,38 +1324,45 @@ class MenuTriggerPattern {
1190
1324
  });
1191
1325
  constructor(inputs) {
1192
1326
  this.inputs = inputs;
1193
- this.submenu = this.inputs.submenu;
1327
+ this.menu = this.inputs.menu;
1194
1328
  }
1195
1329
  onKeydown(event) {
1196
- this.keydownManager().handle(event);
1330
+ if (!this.inputs.disabled()) {
1331
+ this.keydownManager().handle(event);
1332
+ }
1197
1333
  }
1198
1334
  onClick() {
1199
- this.expanded() ? this.close() : this.open({
1200
- first: true
1201
- });
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);
1202
1343
  }
1203
1344
  onFocusOut(event) {
1204
1345
  const element = this.inputs.element();
1205
1346
  const relatedTarget = event.relatedTarget;
1206
- if (this.expanded() && !element?.contains(relatedTarget) && !this.inputs.submenu()?.inputs.element()?.contains(relatedTarget)) {
1347
+ if (this.expanded() && !element?.contains(relatedTarget) && !this.inputs.menu()?.inputs.element()?.contains(relatedTarget)) {
1207
1348
  this.close();
1208
1349
  }
1209
1350
  }
1210
1351
  open(opts) {
1211
1352
  this.expanded.set(true);
1212
1353
  if (opts?.first) {
1213
- this.inputs.submenu()?.first();
1354
+ this.inputs.menu()?.first();
1214
1355
  } else if (opts?.last) {
1215
- this.inputs.submenu()?.last();
1356
+ this.inputs.menu()?.last();
1216
1357
  }
1217
1358
  }
1218
1359
  close(opts = {}) {
1219
1360
  this.expanded.set(false);
1220
- this.submenu()?.listBehavior.unfocus();
1361
+ this.menu()?.listBehavior.unfocus();
1221
1362
  if (opts.refocus) {
1222
1363
  this.inputs.element()?.focus();
1223
1364
  }
1224
- let menuitems = this.inputs.submenu()?.inputs.items() ?? [];
1365
+ let menuitems = this.inputs.menu()?.inputs.items() ?? [];
1225
1366
  while (menuitems.length) {
1226
1367
  const menuitem = menuitems.pop();
1227
1368
  menuitem?._expanded.set(false);
@@ -1234,11 +1375,12 @@ class MenuItemPattern {
1234
1375
  inputs;
1235
1376
  value;
1236
1377
  id;
1237
- disabled;
1378
+ disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled();
1238
1379
  searchTerm;
1239
1380
  element;
1240
- isActive = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1241
- tabindex = computed(() => {
1381
+ active = computed(() => this.inputs.parent()?.inputs.activeItem() === this);
1382
+ hasBeenFocused = signal(false);
1383
+ tabIndex = computed(() => {
1242
1384
  if (this.submenu() && this.submenu()?.inputs.activeItem()) {
1243
1385
  return -1;
1244
1386
  }
@@ -1257,12 +1399,14 @@ class MenuItemPattern {
1257
1399
  this.id = inputs.id;
1258
1400
  this.value = inputs.value;
1259
1401
  this.element = inputs.element;
1260
- this.disabled = inputs.disabled;
1261
1402
  this.submenu = this.inputs.submenu;
1262
1403
  this.searchTerm = inputs.searchTerm;
1263
1404
  this.selectable = computed(() => !this.submenu());
1264
1405
  }
1265
1406
  open(opts) {
1407
+ if (this.disabled()) {
1408
+ return;
1409
+ }
1266
1410
  this._expanded.set(true);
1267
1411
  if (opts?.first) {
1268
1412
  this.submenu()?.first();
@@ -1282,224 +1426,45 @@ class MenuItemPattern {
1282
1426
  menuitem?._expanded.set(false);
1283
1427
  menuitem?.inputs.parent()?.listBehavior.unfocus();
1284
1428
  menuitems = menuitems.concat(menuitem?.submenu()?.inputs.items() ?? []);
1429
+ const parent = menuitem?.inputs.parent();
1430
+ if (parent instanceof MenuPattern) {
1431
+ parent._clearTimeouts();
1432
+ }
1285
1433
  }
1286
1434
  }
1435
+ onFocusIn() {
1436
+ this.hasBeenFocused.set(true);
1437
+ }
1287
1438
  }
1288
1439
 
1289
- class RadioGroupPattern {
1290
- inputs;
1291
- listBehavior;
1292
- orientation;
1293
- wrap = signal(false);
1294
- selectionMode = signal('follow');
1295
- disabled = computed(() => this.inputs.disabled() || this.listBehavior.disabled());
1296
- selectedItem = computed(() => this.listBehavior.selectionBehavior.selectedItems()[0]);
1297
- readonly = computed(() => this.selectedItem()?.disabled() || this.inputs.readonly());
1298
- tabindex = computed(() => this.listBehavior.tabindex());
1299
- activedescendant = computed(() => this.listBehavior.activedescendant());
1300
- _prevKey = computed(() => {
1301
- if (this.inputs.orientation() === 'vertical') {
1302
- return 'ArrowUp';
1303
- }
1304
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1305
- });
1306
- _nextKey = computed(() => {
1307
- if (this.inputs.orientation() === 'vertical') {
1308
- return 'ArrowDown';
1309
- }
1310
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1311
- });
1312
- keydown = computed(() => {
1313
- const manager = new KeyboardEventManager();
1314
- if (this.readonly()) {
1315
- return manager.on(this._prevKey, () => this.listBehavior.prev()).on(this._nextKey, () => this.listBehavior.next()).on('Home', () => this.listBehavior.first()).on('End', () => this.listBehavior.last());
1316
- }
1317
- return manager.on(this._prevKey, () => this.listBehavior.prev({
1318
- selectOne: true
1319
- })).on(this._nextKey, () => this.listBehavior.next({
1320
- selectOne: true
1321
- })).on('Home', () => this.listBehavior.first({
1322
- selectOne: true
1323
- })).on('End', () => this.listBehavior.last({
1324
- selectOne: true
1325
- })).on(' ', () => this.listBehavior.selectOne()).on('Enter', () => this.listBehavior.selectOne());
1326
- });
1327
- pointerdown = computed(() => {
1328
- const manager = new PointerEventManager();
1329
- if (this.readonly()) {
1330
- return manager.on(e => this.listBehavior.goto(this.inputs.getItem(e)));
1331
- }
1332
- return manager.on(e => this.listBehavior.goto(this.inputs.getItem(e), {
1333
- selectOne: true
1334
- }));
1440
+ function convertGetterSetterToWritableSignalLike(getter, setter) {
1441
+ return Object.assign(getter, {
1442
+ set: setter,
1443
+ update: updateCallback => setter(updateCallback(getter()))
1335
1444
  });
1445
+ }
1446
+
1447
+ class ListExpansion {
1448
+ inputs;
1336
1449
  constructor(inputs) {
1337
1450
  this.inputs = inputs;
1338
- this.orientation = inputs.orientation;
1339
- this.listBehavior = new List({
1340
- ...inputs,
1341
- wrap: this.wrap,
1342
- selectionMode: this.selectionMode,
1343
- multi: () => false,
1344
- typeaheadDelay: () => 0
1345
- });
1346
- }
1347
- onKeydown(event) {
1348
- if (!this.disabled()) {
1349
- this.keydown().handle(event);
1350
- }
1351
- }
1352
- onPointerdown(event) {
1353
- if (!this.disabled()) {
1354
- this.pointerdown().handle(event);
1355
- }
1356
- }
1357
- setDefaultState() {
1358
- let firstItem = null;
1359
- for (const item of this.inputs.items()) {
1360
- if (this.listBehavior.isFocusable(item)) {
1361
- if (!firstItem) {
1362
- firstItem = item;
1363
- }
1364
- if (item.selected()) {
1365
- this.inputs.activeItem.set(item);
1366
- return;
1367
- }
1368
- }
1369
- }
1370
- if (firstItem) {
1371
- this.inputs.activeItem.set(firstItem);
1372
- }
1373
- }
1374
- validate() {
1375
- const violations = [];
1376
- if (this.selectedItem()?.disabled() && this.inputs.skipDisabled()) {
1377
- violations.push("Accessibility Violation: The selected radio button is disabled while 'skipDisabled' is true, making the selection unreachable via keyboard.");
1378
- }
1379
- return violations;
1380
- }
1381
- }
1382
-
1383
- class RadioButtonPattern {
1384
- inputs;
1385
- id;
1386
- value;
1387
- index = computed(() => this.group()?.listBehavior.inputs.items().indexOf(this) ?? -1);
1388
- active = computed(() => this.group()?.listBehavior.inputs.activeItem() === this);
1389
- selected = computed(() => !!this.group()?.listBehavior.inputs.value().includes(this.value()));
1390
- selectable = () => true;
1391
- disabled;
1392
- group;
1393
- tabindex = computed(() => this.group()?.listBehavior.getItemTabindex(this));
1394
- element;
1395
- searchTerm = () => '';
1396
- constructor(inputs) {
1397
- this.inputs = inputs;
1398
- this.id = inputs.id;
1399
- this.value = inputs.value;
1400
- this.group = inputs.group;
1401
- this.element = inputs.element;
1402
- this.disabled = inputs.disabled;
1403
- }
1404
- }
1405
-
1406
- class ToolbarRadioGroupPattern extends RadioGroupPattern {
1407
- inputs;
1408
- constructor(inputs) {
1409
- if (!!inputs.toolbar()) {
1410
- inputs.orientation = inputs.toolbar().orientation;
1411
- inputs.skipDisabled = inputs.toolbar().skipDisabled;
1412
- }
1413
- super(inputs);
1414
- this.inputs = inputs;
1415
- }
1416
- onKeydown(_) {}
1417
- onPointerdown(_) {}
1418
- isOnFirstItem() {
1419
- return this.listBehavior.navigationBehavior.peekPrev() === undefined;
1420
- }
1421
- isOnLastItem() {
1422
- return this.listBehavior.navigationBehavior.peekNext() === undefined;
1423
- }
1424
- next(wrap) {
1425
- this.wrap.set(wrap);
1426
- this.listBehavior.next();
1427
- this.wrap.set(false);
1428
- }
1429
- prev(wrap) {
1430
- this.wrap.set(wrap);
1431
- this.listBehavior.prev();
1432
- this.wrap.set(false);
1433
- }
1434
- first() {
1435
- this.listBehavior.first();
1436
- }
1437
- last() {
1438
- this.listBehavior.last();
1439
- }
1440
- unfocus() {
1441
- this.inputs.activeItem.set(undefined);
1442
- }
1443
- trigger() {
1444
- if (this.readonly()) return;
1445
- this.listBehavior.selectOne();
1446
- }
1447
- goto(e) {
1448
- this.listBehavior.goto(this.inputs.getItem(e), {
1449
- selectOne: !this.readonly()
1450
- });
1451
- }
1452
- }
1453
-
1454
- function convertGetterSetterToWritableSignalLike(getter, setter) {
1455
- return Object.assign(getter, {
1456
- set: setter,
1457
- update: updateCallback => setter(updateCallback(getter()))
1458
- });
1459
- }
1460
-
1461
- class ExpansionControl {
1462
- inputs;
1463
- isExpanded = computed(() => this.inputs.expansionManager.isExpanded(this));
1464
- isExpandable = computed(() => this.inputs.expansionManager.isExpandable(this));
1465
- constructor(inputs) {
1466
- this.inputs = inputs;
1467
- this.expansionId = inputs.expansionId;
1468
- this.expandable = inputs.expandable;
1469
- this.disabled = inputs.disabled;
1470
- }
1471
- open() {
1472
- this.inputs.expansionManager.open(this);
1473
- }
1474
- close() {
1475
- this.inputs.expansionManager.close(this);
1476
- }
1477
- toggle() {
1478
- this.inputs.expansionManager.toggle(this);
1479
- }
1480
- }
1481
- class ListExpansion {
1482
- inputs;
1483
- expandedIds;
1484
- constructor(inputs) {
1485
- this.inputs = inputs;
1486
- this.expandedIds = inputs.expandedIds;
1487
1451
  }
1488
1452
  open(item) {
1489
- if (!this.isExpandable(item)) return;
1490
- if (this.isExpanded(item)) return;
1453
+ if (!this.isExpandable(item)) return false;
1454
+ if (item.expanded()) return false;
1491
1455
  if (!this.inputs.multiExpandable()) {
1492
1456
  this.closeAll();
1493
1457
  }
1494
- this.expandedIds.update(ids => ids.concat(item.expansionId()));
1458
+ item.expanded.set(true);
1459
+ return true;
1495
1460
  }
1496
1461
  close(item) {
1497
- if (this.isExpandable(item)) {
1498
- this.expandedIds.update(ids => ids.filter(id => id !== item.expansionId()));
1499
- }
1462
+ if (!this.isExpandable(item)) return false;
1463
+ item.expanded.set(false);
1464
+ return true;
1500
1465
  }
1501
1466
  toggle(item) {
1502
- this.expandedIds().includes(item.expansionId()) ? this.close(item) : this.open(item);
1467
+ return item.expanded() ? this.close(item) : this.open(item);
1503
1468
  }
1504
1469
  openAll() {
1505
1470
  if (this.inputs.multiExpandable()) {
@@ -1516,9 +1481,6 @@ class ListExpansion {
1516
1481
  isExpandable(item) {
1517
1482
  return !this.inputs.disabled() && !item.disabled() && item.expandable();
1518
1483
  }
1519
- isExpanded(item) {
1520
- return this.expandedIds().includes(item.expansionId());
1521
- }
1522
1484
  }
1523
1485
 
1524
1486
  class LabelControl {
@@ -1543,47 +1505,35 @@ class LabelControl {
1543
1505
 
1544
1506
  class TabPattern {
1545
1507
  inputs;
1546
- expansion;
1547
- id;
1508
+ id = () => this.inputs.id();
1548
1509
  index = computed(() => this.inputs.tablist().inputs.items().indexOf(this));
1549
- value;
1550
- disabled;
1551
- element;
1552
- selectable = () => true;
1553
- searchTerm = () => '';
1554
- expandable = computed(() => this.expansion.expandable());
1555
- expansionId = computed(() => this.expansion.expansionId());
1556
- 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;
1557
1515
  active = computed(() => this.inputs.tablist().inputs.activeItem() === this);
1558
- selected = computed(() => !!this.inputs.tablist().inputs.value().includes(this.value()));
1559
- 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));
1560
1518
  controls = computed(() => this.inputs.tabpanel()?.id());
1561
1519
  constructor(inputs) {
1562
1520
  this.inputs = inputs;
1563
- this.id = inputs.id;
1564
- this.value = inputs.value;
1565
- this.disabled = inputs.disabled;
1566
- this.element = inputs.element;
1567
- this.expansion = new ExpansionControl({
1568
- ...inputs,
1569
- expansionId: inputs.value,
1570
- expandable: () => true,
1571
- expansionManager: inputs.tablist().expansionManager
1572
- });
1521
+ this.expanded = inputs.expanded;
1522
+ }
1523
+ open() {
1524
+ return this.inputs.tablist().open(this);
1573
1525
  }
1574
1526
  }
1575
1527
  class TabPanelPattern {
1576
1528
  inputs;
1577
- id;
1578
- value;
1529
+ id = () => this.inputs.id();
1530
+ value = () => this.inputs.value();
1579
1531
  labelManager;
1580
1532
  hidden = computed(() => this.inputs.tab()?.expanded() === false);
1581
- tabindex = computed(() => this.hidden() ? -1 : 0);
1533
+ tabIndex = computed(() => this.hidden() ? -1 : 0);
1582
1534
  labelledBy = computed(() => this.labelManager.labelledBy().length > 0 ? this.labelManager.labelledBy().join(' ') : undefined);
1583
1535
  constructor(inputs) {
1584
1536
  this.inputs = inputs;
1585
- this.id = inputs.id;
1586
- this.value = inputs.value;
1587
1537
  this.labelManager = new LabelControl({
1588
1538
  ...inputs,
1589
1539
  defaultLabelledBy: computed(() => this.inputs.tab() ? [this.inputs.tab().id()] : [])
@@ -1592,12 +1542,15 @@ class TabPanelPattern {
1592
1542
  }
1593
1543
  class TabListPattern {
1594
1544
  inputs;
1595
- listBehavior;
1596
- expansionManager;
1597
- orientation;
1598
- disabled;
1599
- tabindex = computed(() => this.listBehavior.tabindex());
1600
- 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());
1601
1554
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1602
1555
  prevKey = computed(() => {
1603
1556
  if (this.inputs.orientation() === 'vertical') {
@@ -1612,40 +1565,27 @@ class TabListPattern {
1612
1565
  return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1613
1566
  });
1614
1567
  keydown = computed(() => {
1615
- return new KeyboardEventManager().on(this.prevKey, () => this.listBehavior.prev({
1616
- select: this.followFocus()
1617
- })).on(this.nextKey, () => this.listBehavior.next({
1618
- select: this.followFocus()
1619
- })).on('Home', () => this.listBehavior.first({
1620
- select: this.followFocus()
1621
- })).on('End', () => this.listBehavior.last({
1622
- select: this.followFocus()
1623
- })).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());
1624
1569
  });
1625
1570
  pointerdown = computed(() => {
1626
- return new PointerEventManager().on(e => this.listBehavior.goto(this._getItem(e), {
1627
- select: true
1628
- }));
1571
+ return new PointerEventManager().on(e => this._navigate(() => this.navigationBehavior.goto(this._getItem(e)), true));
1629
1572
  });
1630
1573
  constructor(inputs) {
1631
1574
  this.inputs = inputs;
1632
- this.disabled = inputs.disabled;
1633
- this.orientation = inputs.orientation;
1634
- this.listBehavior = new List({
1575
+ this.focusBehavior = new ListFocus(inputs);
1576
+ this.navigationBehavior = new ListNavigation({
1635
1577
  ...inputs,
1636
- multi: () => false,
1637
- typeaheadDelay: () => 0
1578
+ focusManager: this.focusBehavior
1638
1579
  });
1639
- this.expansionManager = new ListExpansion({
1580
+ this.expansionBehavior = new ListExpansion({
1640
1581
  ...inputs,
1641
- multiExpandable: () => false,
1642
- expandedIds: this.inputs.value
1582
+ multiExpandable: () => false
1643
1583
  });
1644
1584
  }
1645
1585
  setDefaultState() {
1646
1586
  let firstItem;
1647
1587
  for (const item of this.inputs.items()) {
1648
- if (!this.listBehavior.isFocusable(item)) continue;
1588
+ if (!this.focusBehavior.isFocusable(item)) continue;
1649
1589
  if (firstItem === undefined) {
1650
1590
  firstItem = item;
1651
1591
  }
@@ -1668,6 +1608,24 @@ class TabListPattern {
1668
1608
  this.pointerdown().handle(event);
1669
1609
  }
1670
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
+ }
1671
1629
  _getItem(e) {
1672
1630
  if (!(e.target instanceof HTMLElement)) {
1673
1631
  return;
@@ -1677,67 +1635,15 @@ class TabListPattern {
1677
1635
  }
1678
1636
  }
1679
1637
 
1680
- class ToolbarWidgetPattern {
1681
- inputs;
1682
- id;
1683
- element;
1684
- disabled;
1685
- toolbar;
1686
- tabindex = computed(() => this.toolbar().listBehavior.getItemTabindex(this));
1687
- searchTerm = () => '';
1688
- value = () => '';
1689
- selectable = () => true;
1690
- index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1);
1691
- active = computed(() => this.toolbar().inputs.activeItem() === this);
1692
- constructor(inputs) {
1693
- this.inputs = inputs;
1694
- this.id = inputs.id;
1695
- this.element = inputs.element;
1696
- this.disabled = inputs.disabled;
1697
- this.toolbar = inputs.toolbar;
1698
- }
1699
- }
1700
-
1701
- class ToolbarWidgetGroupPattern {
1702
- inputs;
1703
- id;
1704
- element;
1705
- disabled;
1706
- toolbar;
1707
- searchTerm = () => '';
1708
- value = () => '';
1709
- selectable = () => true;
1710
- index = computed(() => this.toolbar()?.inputs.items().indexOf(this) ?? -1);
1711
- controls = computed(() => this.inputs.controls() ?? this._defaultControls);
1712
- _defaultControls = {
1713
- isOnFirstItem: () => true,
1714
- isOnLastItem: () => true,
1715
- next: () => {},
1716
- prev: () => {},
1717
- first: () => {},
1718
- last: () => {},
1719
- unfocus: () => {},
1720
- trigger: () => {},
1721
- goto: () => {},
1722
- setDefaultState: () => {}
1723
- };
1724
- constructor(inputs) {
1725
- this.inputs = inputs;
1726
- this.id = inputs.id;
1727
- this.element = inputs.element;
1728
- this.disabled = inputs.disabled;
1729
- this.toolbar = inputs.toolbar;
1730
- }
1731
- }
1732
-
1733
1638
  class ToolbarPattern {
1734
1639
  inputs;
1735
1640
  listBehavior;
1736
1641
  orientation;
1737
- skipDisabled;
1642
+ softDisabled;
1738
1643
  disabled = computed(() => this.listBehavior.disabled());
1739
- tabindex = computed(() => this.listBehavior.tabindex());
1740
- activedescendant = computed(() => this.listBehavior.activedescendant());
1644
+ tabIndex = computed(() => this.listBehavior.tabIndex());
1645
+ activeDescendant = computed(() => this.listBehavior.activeDescendant());
1646
+ activeItem = () => this.listBehavior.inputs.activeItem();
1741
1647
  _prevKey = computed(() => {
1742
1648
  if (this.inputs.orientation() === 'vertical') {
1743
1649
  return 'ArrowUp';
@@ -1764,95 +1670,56 @@ class ToolbarPattern {
1764
1670
  });
1765
1671
  _keydown = computed(() => {
1766
1672
  const manager = new KeyboardEventManager();
1767
- return manager.on(this._nextKey, () => this._next()).on(this._prevKey, () => this._prev()).on(this._altNextKey, () => this._groupNext()).on(this._altPrevKey, () => this._groupPrev()).on(' ', () => this._trigger()).on('Enter', () => this._trigger()).on('Home', () => this._first()).on('End', () => this._last());
1673
+ return manager.on(this._nextKey, () => this.listBehavior.next()).on(this._prevKey, () => this.listBehavior.prev()).on(this._altNextKey, () => this._groupNext()).on(this._altPrevKey, () => this._groupPrev()).on(' ', () => this.select()).on('Enter', () => this.select()).on('Home', () => this.listBehavior.first()).on('End', () => this.listBehavior.last());
1768
1674
  });
1769
- _pointerdown = computed(() => new PointerEventManager().on(e => this._goto(e)));
1770
- _next() {
1771
- const item = this.inputs.activeItem();
1772
- if (item instanceof ToolbarWidgetGroupPattern) {
1773
- if (!item.disabled() && !item.controls().isOnLastItem()) {
1774
- item.controls().next(false);
1775
- return;
1776
- }
1777
- item.controls().unfocus();
1778
- }
1779
- this.listBehavior.next();
1780
- const newItem = this.inputs.activeItem();
1781
- if (newItem instanceof ToolbarWidgetGroupPattern) {
1782
- newItem.controls().first();
1783
- }
1784
- }
1785
- _prev() {
1786
- const item = this.inputs.activeItem();
1787
- if (item instanceof ToolbarWidgetGroupPattern) {
1788
- if (!item.disabled() && !item.controls().isOnFirstItem()) {
1789
- item.controls().prev(false);
1790
- return;
1791
- }
1792
- item.controls().unfocus();
1793
- }
1794
- this.listBehavior.prev();
1795
- const newItem = this.inputs.activeItem();
1796
- if (newItem instanceof ToolbarWidgetGroupPattern) {
1797
- newItem.controls().last();
1798
- }
1799
- }
1800
1675
  _groupNext() {
1801
- const item = this.inputs.activeItem();
1802
- if (item instanceof ToolbarWidgetPattern) return;
1803
- item?.controls().next(true);
1804
- }
1805
- _groupPrev() {
1806
- const item = this.inputs.activeItem();
1807
- if (item instanceof ToolbarWidgetPattern) return;
1808
- item?.controls().prev(true);
1809
- }
1810
- _trigger() {
1811
- const item = this.inputs.activeItem();
1812
- if (item instanceof ToolbarWidgetGroupPattern) {
1813
- item.controls().trigger();
1814
- }
1815
- }
1816
- _first() {
1817
- const item = this.inputs.activeItem();
1818
- if (item instanceof ToolbarWidgetGroupPattern) {
1819
- item.controls().unfocus();
1676
+ const currGroup = this.inputs.activeItem()?.group();
1677
+ const nextGroup = this.listBehavior.navigationBehavior.peekNext()?.group();
1678
+ if (!currGroup) {
1679
+ return;
1820
1680
  }
1821
- this.listBehavior.first();
1822
- const newItem = this.inputs.activeItem();
1823
- if (newItem instanceof ToolbarWidgetGroupPattern) {
1824
- newItem.controls().first();
1681
+ if (currGroup !== nextGroup) {
1682
+ this.listBehavior.goto(this.listBehavior.navigationBehavior.peekFirst(currGroup.inputs.items()));
1683
+ return;
1825
1684
  }
1685
+ this.listBehavior.next();
1826
1686
  }
1827
- _last() {
1828
- const item = this.inputs.activeItem();
1829
- if (item instanceof ToolbarWidgetGroupPattern) {
1830
- item.controls().unfocus();
1687
+ _groupPrev() {
1688
+ const currGroup = this.inputs.activeItem()?.group();
1689
+ const nextGroup = this.listBehavior.navigationBehavior.peekPrev()?.group();
1690
+ if (!currGroup) {
1691
+ return;
1831
1692
  }
1832
- this.listBehavior.last();
1833
- const newItem = this.inputs.activeItem();
1834
- if (newItem instanceof ToolbarWidgetGroupPattern) {
1835
- newItem.controls().last();
1693
+ if (currGroup !== nextGroup) {
1694
+ this.listBehavior.goto(this.listBehavior.navigationBehavior.peekLast(currGroup.inputs.items()));
1695
+ return;
1836
1696
  }
1697
+ this.listBehavior.prev();
1837
1698
  }
1838
1699
  _goto(e) {
1839
1700
  const item = this.inputs.getItem(e.target);
1840
- if (!item) return;
1841
- this.listBehavior.goto(item);
1842
- if (item instanceof ToolbarWidgetGroupPattern) {
1843
- item.controls().goto(e);
1701
+ if (item) {
1702
+ this.listBehavior.goto(item);
1703
+ this.select();
1844
1704
  }
1845
1705
  }
1706
+ select() {
1707
+ const group = this.inputs.activeItem()?.group();
1708
+ if (!group?.multi()) {
1709
+ group?.inputs.items().forEach(i => this.listBehavior.deselect(i));
1710
+ }
1711
+ this.listBehavior.toggle();
1712
+ }
1846
1713
  constructor(inputs) {
1847
1714
  this.inputs = inputs;
1848
1715
  this.orientation = inputs.orientation;
1849
- this.skipDisabled = inputs.skipDisabled;
1716
+ this.softDisabled = inputs.softDisabled;
1850
1717
  this.listBehavior = new List({
1851
1718
  ...inputs,
1852
- multi: () => false,
1719
+ multi: () => true,
1853
1720
  focusMode: () => 'roving',
1854
1721
  selectionMode: () => 'explicit',
1855
- value: signal([]),
1722
+ values: signal([]),
1856
1723
  typeaheadDelay: () => 0
1857
1724
  });
1858
1725
  }
@@ -1861,24 +1728,17 @@ class ToolbarPattern {
1861
1728
  this._keydown().handle(event);
1862
1729
  }
1863
1730
  onPointerdown(event) {
1731
+ event.preventDefault();
1732
+ }
1733
+ onClick(event) {
1864
1734
  if (this.disabled()) return;
1865
- this._pointerdown().handle(event);
1735
+ this._goto(event);
1866
1736
  }
1867
1737
  setDefaultState() {
1868
- let firstItem = null;
1869
- for (const item of this.inputs.items()) {
1870
- if (this.listBehavior.isFocusable(item)) {
1871
- if (!firstItem) {
1872
- firstItem = item;
1873
- }
1874
- }
1875
- }
1738
+ const firstItem = this.listBehavior.navigationBehavior.peekFirst(this.inputs.items());
1876
1739
  if (firstItem) {
1877
1740
  this.inputs.activeItem.set(firstItem);
1878
1741
  }
1879
- if (firstItem instanceof ToolbarWidgetGroupPattern) {
1880
- firstItem.controls().setDefaultState();
1881
- }
1882
1742
  }
1883
1743
  validate() {
1884
1744
  const violations = [];
@@ -1886,85 +1746,81 @@ class ToolbarPattern {
1886
1746
  }
1887
1747
  }
1888
1748
 
1749
+ class ToolbarWidgetPattern {
1750
+ inputs;
1751
+ id = () => this.inputs.id();
1752
+ element = () => this.inputs.element();
1753
+ disabled = () => this.inputs.disabled() || this.group()?.disabled() || false;
1754
+ group = () => this.inputs.group();
1755
+ toolbar = () => this.inputs.toolbar();
1756
+ tabIndex = computed(() => this.toolbar().listBehavior.getItemTabindex(this));
1757
+ searchTerm = () => '';
1758
+ value = () => this.inputs.value();
1759
+ selectable = () => true;
1760
+ index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1);
1761
+ selected = computed(() => this.toolbar().listBehavior.inputs.values().includes(this.value()));
1762
+ active = computed(() => this.toolbar().activeItem() === this);
1763
+ constructor(inputs) {
1764
+ this.inputs = inputs;
1765
+ }
1766
+ }
1767
+
1768
+ class ToolbarWidgetGroupPattern {
1769
+ inputs;
1770
+ disabled = () => this.inputs.disabled();
1771
+ toolbar = () => this.inputs.toolbar();
1772
+ multi = () => this.inputs.multi();
1773
+ searchTerm = () => '';
1774
+ value = () => '';
1775
+ selectable = () => true;
1776
+ element = () => undefined;
1777
+ constructor(inputs) {
1778
+ this.inputs = inputs;
1779
+ }
1780
+ }
1781
+
1889
1782
  const focusMode = () => 'roving';
1890
1783
  class AccordionGroupPattern {
1891
1784
  inputs;
1892
- navigation;
1893
- focusManager;
1894
- expansionManager;
1785
+ navigationBehavior;
1786
+ focusBehavior;
1787
+ expansionBehavior;
1895
1788
  constructor(inputs) {
1896
1789
  this.inputs = inputs;
1897
- this.wrap = inputs.wrap;
1898
- this.orientation = inputs.orientation;
1899
- this.textDirection = inputs.textDirection;
1900
- this.activeItem = inputs.activeItem;
1901
- this.disabled = inputs.disabled;
1902
- this.multiExpandable = inputs.multiExpandable;
1903
- this.items = inputs.items;
1904
- this.expandedIds = inputs.expandedIds;
1905
- this.skipDisabled = inputs.skipDisabled;
1906
- this.focusManager = new ListFocus({
1790
+ this.focusBehavior = new ListFocus({
1907
1791
  ...inputs,
1908
1792
  focusMode
1909
1793
  });
1910
- this.navigation = new ListNavigation({
1794
+ this.navigationBehavior = new ListNavigation({
1911
1795
  ...inputs,
1912
1796
  focusMode,
1913
- focusManager: this.focusManager
1797
+ focusManager: this.focusBehavior
1914
1798
  });
1915
- this.expansionManager = new ListExpansion({
1799
+ this.expansionBehavior = new ListExpansion({
1916
1800
  ...inputs
1917
1801
  });
1918
1802
  }
1919
- }
1920
- class AccordionTriggerPattern {
1921
- inputs;
1922
- expandable;
1923
- expansionId;
1924
- expanded;
1925
- expansionControl;
1926
- active = computed(() => this.inputs.accordionGroup().activeItem() === this);
1927
- controls = computed(() => this.inputs.accordionPanel()?.id());
1928
- tabindex = computed(() => this.inputs.accordionGroup().focusManager.isFocusable(this) ? 0 : -1);
1929
- disabled = computed(() => this.inputs.disabled() || this.inputs.accordionGroup().disabled());
1930
- index = computed(() => this.inputs.accordionGroup().items().indexOf(this));
1931
- constructor(inputs) {
1932
- this.inputs = inputs;
1933
- this.id = inputs.id;
1934
- this.element = inputs.element;
1935
- this.value = inputs.value;
1936
- this.expansionControl = new ExpansionControl({
1937
- ...inputs,
1938
- expansionId: inputs.value,
1939
- expandable: () => true,
1940
- expansionManager: inputs.accordionGroup().expansionManager
1941
- });
1942
- this.expandable = this.expansionControl.isExpandable;
1943
- this.expansionId = this.expansionControl.expansionId;
1944
- this.expanded = this.expansionControl.isExpanded;
1945
- }
1946
1803
  prevKey = computed(() => {
1947
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1804
+ if (this.inputs.orientation() === 'vertical') {
1948
1805
  return 'ArrowUp';
1949
1806
  }
1950
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1807
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1951
1808
  });
1952
1809
  nextKey = computed(() => {
1953
- if (this.inputs.accordionGroup().orientation() === 'vertical') {
1810
+ if (this.inputs.orientation() === 'vertical') {
1954
1811
  return 'ArrowDown';
1955
1812
  }
1956
- return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1813
+ return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1957
1814
  });
1958
1815
  keydown = computed(() => {
1959
- 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());
1960
1817
  });
1961
1818
  pointerdown = computed(() => {
1962
1819
  return new PointerEventManager().on(e => {
1963
- const item = this._getItem(e);
1964
- if (item) {
1965
- this.inputs.accordionGroup().navigation.goto(item);
1966
- this.expansionControl.toggle();
1967
- }
1820
+ const item = this.inputs.getItem(e.target);
1821
+ if (!item) return;
1822
+ this.navigationBehavior.goto(item);
1823
+ this.expansionBehavior.toggle(item);
1968
1824
  });
1969
1825
  });
1970
1826
  onKeydown(event) {
@@ -1974,26 +1830,51 @@ class AccordionTriggerPattern {
1974
1830
  this.pointerdown().handle(event);
1975
1831
  }
1976
1832
  onFocus(event) {
1977
- const item = this._getItem(event);
1978
- if (item && this.inputs.accordionGroup().focusManager.isFocusable(item)) {
1979
- this.inputs.accordionGroup().focusManager.focus(item);
1980
- }
1833
+ const item = this.inputs.getItem(event.target);
1834
+ if (!item) return;
1835
+ if (!this.focusBehavior.isFocusable(item)) return;
1836
+ this.focusBehavior.focus(item);
1981
1837
  }
1982
- _getItem(e) {
1983
- if (!(e.target instanceof HTMLElement)) {
1984
- return;
1985
- }
1986
- const element = e.target.closest('[role="button"]');
1987
- 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);
1988
1868
  }
1989
1869
  }
1990
1870
  class AccordionPanelPattern {
1991
1871
  inputs;
1872
+ id;
1873
+ accordionTrigger;
1992
1874
  hidden;
1993
1875
  constructor(inputs) {
1994
1876
  this.inputs = inputs;
1995
1877
  this.id = inputs.id;
1996
- this.value = inputs.value;
1997
1878
  this.accordionTrigger = inputs.accordionTrigger;
1998
1879
  this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false);
1999
1880
  }
@@ -2001,27 +1882,25 @@ class AccordionPanelPattern {
2001
1882
 
2002
1883
  class TreeItemPattern {
2003
1884
  inputs;
2004
- id;
2005
- value;
2006
- element;
2007
- disabled;
2008
- searchTerm;
2009
- tree;
2010
- parent;
2011
- 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();
2012
1893
  index = computed(() => this.tree().visibleItems().indexOf(this));
2013
- expansionId;
2014
- expansionManager;
2015
- expansion;
2016
- expandable;
2017
- selectable;
1894
+ expansionBehavior;
1895
+ expandable = () => this.inputs.hasChildren();
1896
+ selectable = () => this.inputs.selectable();
1897
+ expanded;
2018
1898
  level = computed(() => this.parent().level() + 1);
2019
- expanded = computed(() => this.expansion.isExpanded());
2020
- visible = computed(() => this.parent().expanded());
1899
+ visible = computed(() => this.parent().expanded() && this.parent().visible());
2021
1900
  setsize = computed(() => this.parent().children().length);
2022
1901
  posinset = computed(() => this.parent().children().indexOf(this) + 1);
2023
1902
  active = computed(() => this.tree().activeItem() === this);
2024
- tabindex = computed(() => this.tree().listBehavior.getItemTabindex(this));
1903
+ tabIndex = computed(() => this.tree().listBehavior.getItemTabindex(this));
2025
1904
  selected = computed(() => {
2026
1905
  if (this.tree().nav()) {
2027
1906
  return undefined;
@@ -2029,7 +1908,7 @@ class TreeItemPattern {
2029
1908
  if (!this.selectable()) {
2030
1909
  return undefined;
2031
1910
  }
2032
- return this.tree().value().includes(this.value());
1911
+ return this.tree().values().includes(this.value());
2033
1912
  });
2034
1913
  current = computed(() => {
2035
1914
  if (!this.tree().nav()) {
@@ -2038,31 +1917,14 @@ class TreeItemPattern {
2038
1917
  if (!this.selectable()) {
2039
1918
  return undefined;
2040
1919
  }
2041
- return this.tree().value().includes(this.value()) ? this.tree().currentType() : undefined;
1920
+ return this.tree().values().includes(this.value()) ? this.tree().currentType() : undefined;
2042
1921
  });
2043
1922
  constructor(inputs) {
2044
1923
  this.inputs = inputs;
2045
- this.id = inputs.id;
2046
- this.value = inputs.value;
2047
- this.element = inputs.element;
2048
- this.disabled = inputs.disabled;
2049
- this.searchTerm = inputs.searchTerm;
2050
- this.expansionId = inputs.id;
2051
- this.tree = inputs.tree;
2052
- this.parent = inputs.parent;
2053
- this.children = inputs.children;
2054
- this.expandable = inputs.hasChildren;
2055
- this.selectable = inputs.selectable;
2056
- this.expansion = new ExpansionControl({
2057
- ...inputs,
2058
- expandable: this.expandable,
2059
- expansionId: this.expansionId,
2060
- expansionManager: this.parent().expansionManager
2061
- });
2062
- this.expansionManager = new ListExpansion({
1924
+ this.expanded = inputs.expanded;
1925
+ this.expansionBehavior = new ListExpansion({
2063
1926
  ...inputs,
2064
1927
  multiExpandable: () => true,
2065
- expandedIds: signal([]),
2066
1928
  items: this.children,
2067
1929
  disabled: computed(() => this.tree()?.disabled() ?? false)
2068
1930
  });
@@ -2071,37 +1933,39 @@ class TreeItemPattern {
2071
1933
  class TreePattern {
2072
1934
  inputs;
2073
1935
  listBehavior;
2074
- expansionManager;
1936
+ expansionBehavior;
2075
1937
  level = () => 0;
2076
1938
  expanded = () => true;
2077
- tabindex = computed(() => this.listBehavior.tabindex());
2078
- activedescendant = computed(() => this.listBehavior.activedescendant());
1939
+ visible = () => true;
1940
+ tabIndex = computed(() => this.listBehavior.tabIndex());
1941
+ activeDescendant = computed(() => this.listBehavior.activeDescendant());
2079
1942
  children = computed(() => this.inputs.allItems().filter(item => item.level() === this.level() + 1));
2080
1943
  visibleItems = computed(() => this.inputs.allItems().filter(item => item.visible()));
2081
1944
  followFocus = computed(() => this.inputs.selectionMode() === 'follow');
1945
+ isRtl = computed(() => this.inputs.textDirection() === 'rtl');
2082
1946
  prevKey = computed(() => {
2083
1947
  if (this.inputs.orientation() === 'vertical') {
2084
1948
  return 'ArrowUp';
2085
1949
  }
2086
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1950
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
2087
1951
  });
2088
1952
  nextKey = computed(() => {
2089
1953
  if (this.inputs.orientation() === 'vertical') {
2090
1954
  return 'ArrowDown';
2091
1955
  }
2092
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1956
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
2093
1957
  });
2094
1958
  collapseKey = computed(() => {
2095
1959
  if (this.inputs.orientation() === 'horizontal') {
2096
1960
  return 'ArrowUp';
2097
1961
  }
2098
- return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
1962
+ return this.isRtl() ? 'ArrowRight' : 'ArrowLeft';
2099
1963
  });
2100
1964
  expandKey = computed(() => {
2101
1965
  if (this.inputs.orientation() === 'horizontal') {
2102
1966
  return 'ArrowDown';
2103
1967
  }
2104
- return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
1968
+ return this.isRtl() ? 'ArrowLeft' : 'ArrowRight';
2105
1969
  });
2106
1970
  dynamicSpaceKey = computed(() => this.listBehavior.isTyping() ? '' : ' ');
2107
1971
  typeaheadRegexp = /^.$/;
@@ -2143,11 +2007,15 @@ class TreePattern {
2143
2007
  }));
2144
2008
  }
2145
2009
  if (!this.followFocus() && this.inputs.multi()) {
2146
- manager.on(this.dynamicSpaceKey, () => list.toggle()).on('Enter', () => list.toggle()).on([Modifier.Ctrl, Modifier.Meta], 'A', () => list.toggleAll());
2010
+ manager.on(this.dynamicSpaceKey, () => list.toggle()).on('Enter', () => list.toggle(), {
2011
+ preventDefault: !this.nav()
2012
+ }).on([Modifier.Ctrl, Modifier.Meta], 'A', () => list.toggleAll());
2147
2013
  }
2148
2014
  if (!this.followFocus() && !this.inputs.multi()) {
2149
2015
  manager.on(this.dynamicSpaceKey, () => list.selectOne());
2150
- manager.on('Enter', () => list.selectOne());
2016
+ manager.on('Enter', () => list.selectOne(), {
2017
+ preventDefault: !this.nav()
2018
+ });
2151
2019
  }
2152
2020
  if (this.inputs.multi() && this.followFocus()) {
2153
2021
  manager.on([Modifier.Ctrl, Modifier.Meta], this.prevKey, () => list.prev()).on([Modifier.Ctrl, Modifier.Meta], this.nextKey, () => list.next()).on([Modifier.Ctrl, Modifier.Meta], this.expandKey, () => this.expand()).on([Modifier.Ctrl, Modifier.Meta], this.collapseKey, () => this.collapse()).on([Modifier.Ctrl, Modifier.Meta], ' ', () => list.toggle()).on([Modifier.Ctrl, Modifier.Meta], 'Enter', () => list.toggle()).on([Modifier.Ctrl, Modifier.Meta], 'Home', () => list.first()).on([Modifier.Ctrl, Modifier.Meta], 'End', () => list.last()).on([Modifier.Ctrl, Modifier.Meta], 'A', () => {
@@ -2183,45 +2051,33 @@ class TreePattern {
2183
2051
  }
2184
2052
  return manager;
2185
2053
  });
2186
- id;
2187
- nav;
2188
- currentType;
2189
- allItems;
2190
- disabled;
2191
- activeItem = signal(undefined);
2192
- skipDisabled;
2193
- wrap;
2194
- orientation;
2195
- textDirection;
2196
- multi;
2197
- selectionMode;
2198
- typeaheadDelay;
2199
- 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;
2200
2070
  constructor(inputs) {
2201
2071
  this.inputs = inputs;
2202
- this.id = inputs.id;
2203
- this.nav = inputs.nav;
2204
- this.currentType = inputs.currentType;
2205
- this.allItems = inputs.allItems;
2206
- this.focusMode = inputs.focusMode;
2207
- this.disabled = inputs.disabled;
2208
2072
  this.activeItem = inputs.activeItem;
2209
- this.skipDisabled = inputs.skipDisabled;
2210
- this.wrap = inputs.wrap;
2211
- this.orientation = inputs.orientation;
2212
- this.textDirection = inputs.textDirection;
2213
- this.multi = computed(() => this.nav() ? false : this.inputs.multi());
2214
- this.selectionMode = inputs.selectionMode;
2215
- this.typeaheadDelay = inputs.typeaheadDelay;
2216
- this.value = inputs.value;
2073
+ this.values = inputs.values;
2217
2074
  this.listBehavior = new List({
2218
2075
  ...inputs,
2219
2076
  items: this.visibleItems,
2220
2077
  multi: this.multi
2221
2078
  });
2222
- this.expansionManager = new ListExpansion({
2079
+ this.expansionBehavior = new ListExpansion({
2223
2080
  multiExpandable: () => true,
2224
- expandedIds: signal([]),
2225
2081
  items: this.children,
2226
2082
  disabled: this.disabled
2227
2083
  });
@@ -2266,14 +2122,14 @@ class TreePattern {
2266
2122
  if (item.expanded()) {
2267
2123
  this.collapse();
2268
2124
  } else {
2269
- item.expansion.open();
2125
+ this.expansionBehavior.open(item);
2270
2126
  }
2271
2127
  }
2272
2128
  expand(opts) {
2273
2129
  const item = this.activeItem();
2274
2130
  if (!item || !this.listBehavior.isFocusable(item)) return;
2275
2131
  if (item.expandable() && !item.expanded()) {
2276
- item.expansion.open();
2132
+ this.expansionBehavior.open(item);
2277
2133
  } else if (item.expanded() && item.children().some(item => this.listBehavior.isFocusable(item))) {
2278
2134
  this.listBehavior.next(opts);
2279
2135
  }
@@ -2281,13 +2137,13 @@ class TreePattern {
2281
2137
  expandSiblings(item) {
2282
2138
  item ??= this.activeItem();
2283
2139
  const siblings = item?.parent()?.children();
2284
- siblings?.forEach(item => item.expansion.open());
2140
+ siblings?.forEach(item => this.expansionBehavior.open(item));
2285
2141
  }
2286
2142
  collapse(opts) {
2287
2143
  const item = this.activeItem();
2288
2144
  if (!item || !this.listBehavior.isFocusable(item)) return;
2289
2145
  if (item.expandable() && item.expanded()) {
2290
- item.expansion.close();
2146
+ this.expansionBehavior.close(item);
2291
2147
  } else if (item.parent() && item.parent() !== this) {
2292
2148
  const parentItem = item.parent();
2293
2149
  if (parentItem instanceof TreeItemPattern && this.listBehavior.isFocusable(parentItem)) {
@@ -2308,9 +2164,10 @@ class ComboboxTreePattern extends TreePattern {
2308
2164
  inputs;
2309
2165
  isItemCollapsible = () => this.inputs.activeItem()?.parent() instanceof TreeItemPattern;
2310
2166
  role = () => 'tree';
2311
- activeId = computed(() => this.listBehavior.activedescendant());
2167
+ activeId = computed(() => this.listBehavior.activeDescendant());
2168
+ getActiveItem = () => this.inputs.activeItem();
2312
2169
  items = computed(() => this.inputs.allItems());
2313
- tabindex = () => -1;
2170
+ tabIndex = () => -1;
2314
2171
  constructor(inputs) {
2315
2172
  if (inputs.combobox()) {
2316
2173
  inputs.multi = () => false;
@@ -2330,18 +2187,128 @@ class ComboboxTreePattern extends TreePattern {
2330
2187
  first = () => this.listBehavior.first();
2331
2188
  unfocus = () => this.listBehavior.unfocus();
2332
2189
  select = item => this.listBehavior.select(item);
2190
+ toggle = item => this.listBehavior.toggle(item);
2333
2191
  clearSelection = () => this.listBehavior.deselectAll();
2334
2192
  getItem = e => this._getItem(e);
2335
- getSelectedItem = () => this.inputs.allItems().find(i => i.selected());
2336
- setValue = value => this.inputs.value.set(value ? [value] : []);
2193
+ getSelectedItems = () => this.inputs.allItems().filter(item => item.selected());
2194
+ setValue = value => this.inputs.values.set(value ? [value] : []);
2337
2195
  expandItem = () => this.expand();
2338
2196
  collapseItem = () => this.collapse();
2339
2197
  isItemExpandable(item = this.inputs.activeItem()) {
2340
2198
  return item ? item.expandable() : false;
2341
2199
  }
2342
- expandAll = () => this.items().forEach(item => item.expansion.open());
2343
- 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));
2202
+ isItemSelectable = (item = this.inputs.activeItem()) => {
2203
+ return item ? item.selectable() : false;
2204
+ };
2205
+ }
2206
+
2207
+ class DeferredContentAware {
2208
+ contentVisible = signal(false, ...(ngDevMode ? [{
2209
+ debugName: "contentVisible"
2210
+ }] : []));
2211
+ preserveContent = model(false, ...(ngDevMode ? [{
2212
+ debugName: "preserveContent"
2213
+ }] : []));
2214
+ static ɵfac = i0.ɵɵngDeclareFactory({
2215
+ minVersion: "12.0.0",
2216
+ version: "20.2.0-next.2",
2217
+ ngImport: i0,
2218
+ type: DeferredContentAware,
2219
+ deps: [],
2220
+ target: i0.ɵɵFactoryTarget.Directive
2221
+ });
2222
+ static ɵdir = i0.ɵɵngDeclareDirective({
2223
+ minVersion: "17.1.0",
2224
+ version: "20.2.0-next.2",
2225
+ type: DeferredContentAware,
2226
+ isStandalone: true,
2227
+ inputs: {
2228
+ preserveContent: {
2229
+ classPropertyName: "preserveContent",
2230
+ publicName: "preserveContent",
2231
+ isSignal: true,
2232
+ isRequired: false,
2233
+ transformFunction: null
2234
+ }
2235
+ },
2236
+ outputs: {
2237
+ preserveContent: "preserveContentChange"
2238
+ },
2239
+ ngImport: i0
2240
+ });
2241
+ }
2242
+ i0.ɵɵngDeclareClassMetadata({
2243
+ minVersion: "12.0.0",
2244
+ version: "20.2.0-next.2",
2245
+ ngImport: i0,
2246
+ type: DeferredContentAware,
2247
+ decorators: [{
2248
+ type: Directive
2249
+ }]
2250
+ });
2251
+ class DeferredContent {
2252
+ _deferredContentAware = inject(DeferredContentAware, {
2253
+ optional: true
2254
+ });
2255
+ _templateRef = inject(TemplateRef);
2256
+ _viewContainerRef = inject(ViewContainerRef);
2257
+ _currentViewRef = null;
2258
+ _isRendered = false;
2259
+ deferredContentAware = signal(this._deferredContentAware, ...(ngDevMode ? [{
2260
+ debugName: "deferredContentAware"
2261
+ }] : []));
2262
+ constructor() {
2263
+ afterRenderEffect(() => {
2264
+ if (this.deferredContentAware()?.contentVisible()) {
2265
+ if (!this._isRendered) {
2266
+ this._destroyContent();
2267
+ this._currentViewRef = this._viewContainerRef.createEmbeddedView(this._templateRef);
2268
+ this._isRendered = true;
2269
+ }
2270
+ } else if (!this.deferredContentAware()?.preserveContent()) {
2271
+ this._destroyContent();
2272
+ this._isRendered = false;
2273
+ }
2274
+ });
2275
+ }
2276
+ ngOnDestroy() {
2277
+ this._destroyContent();
2278
+ }
2279
+ _destroyContent() {
2280
+ const ref = this._currentViewRef;
2281
+ if (ref && !ref.destroyed) {
2282
+ ref.destroy();
2283
+ this._currentViewRef = null;
2284
+ }
2285
+ }
2286
+ static ɵfac = i0.ɵɵngDeclareFactory({
2287
+ minVersion: "12.0.0",
2288
+ version: "20.2.0-next.2",
2289
+ ngImport: i0,
2290
+ type: DeferredContent,
2291
+ deps: [],
2292
+ target: i0.ɵɵFactoryTarget.Directive
2293
+ });
2294
+ static ɵdir = i0.ɵɵngDeclareDirective({
2295
+ minVersion: "14.0.0",
2296
+ version: "20.2.0-next.2",
2297
+ type: DeferredContent,
2298
+ isStandalone: true,
2299
+ ngImport: i0
2300
+ });
2344
2301
  }
2302
+ i0.ɵɵngDeclareClassMetadata({
2303
+ minVersion: "12.0.0",
2304
+ version: "20.2.0-next.2",
2305
+ ngImport: i0,
2306
+ type: DeferredContent,
2307
+ decorators: [{
2308
+ type: Directive
2309
+ }],
2310
+ ctorParameters: () => []
2311
+ });
2345
2312
 
2346
- export { AccordionGroupPattern, AccordionPanelPattern, AccordionTriggerPattern, ComboboxListboxPattern, ComboboxPattern, ComboboxTreePattern, ListboxPattern, MenuBarPattern, MenuItemPattern, MenuPattern, MenuTriggerPattern, OptionPattern, RadioButtonPattern, RadioGroupPattern, TabListPattern, TabPanelPattern, TabPattern, ToolbarPattern, ToolbarRadioGroupPattern, 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 };
2347
2314
  //# sourceMappingURL=private.mjs.map