@verisoft/ui-core 21.0.14 → 21.0.16

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.
@@ -1,5 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { SimpleChange, InjectionToken, inject, Input, Component, Injectable, EventEmitter, Output, signal, ChangeDetectorRef, HostListener, Directive, Optional, Inject, TemplateRef, ContentChild, input, Pipe } from '@angular/core';
2
+ import { InjectionToken, signal, input, output, inject, ElementRef, Injector, DestroyRef, isDevMode, Directive, effect, SimpleChange, Input, Component, Injectable, EventEmitter, Output, ChangeDetectorRef, HostListener, Optional, Inject, TemplateRef, ContentChild, Pipe } from '@angular/core';
3
+ import { DOCUMENT } from '@angular/common';
3
4
  import { startWith, switchMap, merge, map, debounceTime, BehaviorSubject, Subject, takeUntil, filter, tap, of, catchError } from 'rxjs';
4
5
  import * as i1 from '@angular/forms';
5
6
  import { FormControlName, FormControlDirective, NgModel, FormControl, ReactiveFormsModule } from '@angular/forms';
@@ -12,6 +13,876 @@ import { createResetStateAction, createInitNewDetailAction, createInitDetailActi
12
13
  import { v4 } from 'uuid';
13
14
  import { HttpClient } from '@angular/common/http';
14
15
 
16
+ function assertNever(value) {
17
+ throw new Error(`Unexpected value: ${String(value)}`);
18
+ }
19
+
20
+ function matchesKeyboardKey(event, keys) {
21
+ if (!keys.length) {
22
+ return false;
23
+ }
24
+ return keys.includes(event.key);
25
+ }
26
+ function suppressKeyboardEvent(event, preventDefault) {
27
+ if (!preventDefault) {
28
+ return;
29
+ }
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ }
33
+
34
+ function filterEnabledItems(items) {
35
+ return items.filter((item) => !item.disabled);
36
+ }
37
+ function moveLinearIndex(currentIndex, delta, length, loop) {
38
+ if (length === 0) {
39
+ return -1;
40
+ }
41
+ if (currentIndex < 0) {
42
+ return delta > 0 ? 0 : length - 1;
43
+ }
44
+ const next = currentIndex + delta;
45
+ if (loop) {
46
+ if (next >= length) {
47
+ return 0;
48
+ }
49
+ if (next < 0) {
50
+ return length - 1;
51
+ }
52
+ return next;
53
+ }
54
+ return Math.max(0, Math.min(length - 1, next));
55
+ }
56
+ function resolveInitialActiveIndex(items, getItemId, initialActiveItemId) {
57
+ if (!items.length) {
58
+ return -1;
59
+ }
60
+ if (initialActiveItemId) {
61
+ const index = items.findIndex((item) => getItemId(item) === initialActiveItemId);
62
+ if (index >= 0) {
63
+ return index;
64
+ }
65
+ }
66
+ return 0;
67
+ }
68
+ function getRovingTabIndex(isActive) {
69
+ return isActive ? 0 : -1;
70
+ }
71
+
72
+ const DEFAULT_MENU_KEYBOARD_NAVIGATION = {
73
+ enabled: true,
74
+ openKeys: ['ArrowDown', 'ArrowUp', 'Enter', ' '],
75
+ openToLastKeys: ['ArrowUp'],
76
+ nextKeys: ['ArrowDown'],
77
+ previousKeys: ['ArrowUp'],
78
+ firstKeys: ['Home'],
79
+ lastKeys: ['End'],
80
+ selectKeys: ['Enter', ' '],
81
+ closeKeys: ['Escape'],
82
+ dismissKeys: ['Tab'],
83
+ loop: true,
84
+ preventDefault: true,
85
+ activeItemClass: 'menu-keyboard__item--active',
86
+ };
87
+
88
+ const MENU_OPEN_ACTION_CHECKS = [
89
+ { keys: 'dismissKeys', action: 'dismiss' },
90
+ { keys: 'nextKeys', action: 'next' },
91
+ { keys: 'previousKeys', action: 'previous' },
92
+ { keys: 'firstKeys', action: 'first' },
93
+ { keys: 'lastKeys', action: 'last' },
94
+ { keys: 'selectKeys', action: 'activate' },
95
+ { keys: 'closeKeys', action: 'close' },
96
+ ];
97
+ const MENU_TRIGGER_ACTION_CHECKS = [
98
+ { keys: 'openToLastKeys', action: 'openToLast' },
99
+ { keys: 'openKeys', action: 'open' },
100
+ ];
101
+ const MENU_TRIGGER_ACTION_ORDER = [
102
+ 'openToLast',
103
+ 'open',
104
+ ];
105
+ function resolveMenuTriggerAction(event, navigation) {
106
+ const matched = new Set();
107
+ for (const { keys, action } of MENU_TRIGGER_ACTION_CHECKS) {
108
+ if (matchesKeyboardKey(event, navigation[keys])) {
109
+ matched.add(action);
110
+ }
111
+ }
112
+ for (const action of MENU_TRIGGER_ACTION_ORDER) {
113
+ if (matched.has(action)) {
114
+ return action;
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ function resolveMenuKeyboardAction(event, navigation) {
120
+ for (const { keys, action } of MENU_OPEN_ACTION_CHECKS) {
121
+ if (matchesKeyboardKey(event, navigation[keys])) {
122
+ return action;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ function resolveMenuKeyboardNavigation(input) {
129
+ if (input === undefined || input === false) {
130
+ return null;
131
+ }
132
+ const resolved = {
133
+ ...DEFAULT_MENU_KEYBOARD_NAVIGATION,
134
+ enabled: true,
135
+ ...(input === true ? {} : input),
136
+ };
137
+ if (!resolved.enabled) {
138
+ return null;
139
+ }
140
+ return resolved;
141
+ }
142
+
143
+ class MenuKeyboardController {
144
+ config;
145
+ focusedIndex = -1;
146
+ isOpen = false;
147
+ keyboardPresetFocus = false;
148
+ constructor(config) {
149
+ this.config = config;
150
+ }
151
+ get actionableItems() {
152
+ return filterEnabledItems(this.config.getItems());
153
+ }
154
+ isNavigationEnabled() {
155
+ return this.resolveNavigation() !== null;
156
+ }
157
+ getActiveItemClass() {
158
+ return (this.resolveNavigation()?.activeItemClass ?? DEFAULT_ACTIVE_ITEM_CLASS$1);
159
+ }
160
+ onTriggerKeydown(event) {
161
+ const navigation = this.resolveNavigation();
162
+ if (!navigation || this.isOpen) {
163
+ return;
164
+ }
165
+ const action = resolveMenuTriggerAction(event, navigation);
166
+ if (!action) {
167
+ return;
168
+ }
169
+ const items = this.actionableItems;
170
+ if (!items.length) {
171
+ return;
172
+ }
173
+ suppressKeyboardEvent(event, navigation.preventDefault);
174
+ this.applyTriggerAction(action, items, navigation);
175
+ this.keyboardPresetFocus = true;
176
+ this.config.onOpen();
177
+ }
178
+ onMenuKeydown(event) {
179
+ const navigation = this.resolveNavigation();
180
+ if (!navigation || !this.isOpen) {
181
+ return;
182
+ }
183
+ const items = this.actionableItems;
184
+ if (!items.length) {
185
+ return;
186
+ }
187
+ const action = resolveMenuKeyboardAction(event, navigation);
188
+ if (!action) {
189
+ return;
190
+ }
191
+ this.applyOpenMenuAction(action, event, navigation, items);
192
+ }
193
+ handleOpenChange(open) {
194
+ if (!open) {
195
+ this.isOpen = false;
196
+ this.focusedIndex = -1;
197
+ this.keyboardPresetFocus = false;
198
+ return;
199
+ }
200
+ const wasOpen = this.isOpen;
201
+ this.isOpen = true;
202
+ const navigation = this.resolveNavigation();
203
+ const items = this.actionableItems;
204
+ if (!items.length) {
205
+ this.isOpen = false;
206
+ this.focusedIndex = -1;
207
+ this.keyboardPresetFocus = false;
208
+ return;
209
+ }
210
+ if (!wasOpen) {
211
+ if (!this.keyboardPresetFocus) {
212
+ this.focusedIndex = navigation
213
+ ? this.resolveInitialActiveIndex(navigation)
214
+ : 0;
215
+ }
216
+ if (this.focusedIndex < 0 || this.focusedIndex >= items.length) {
217
+ this.focusedIndex = 0;
218
+ }
219
+ }
220
+ this.keyboardPresetFocus = false;
221
+ }
222
+ cancelPendingOpen() {
223
+ this.keyboardPresetFocus = false;
224
+ this.focusedIndex = -1;
225
+ }
226
+ close(restoreFocus) {
227
+ if (!this.isOpen) {
228
+ return;
229
+ }
230
+ this.config.onClose(restoreFocus);
231
+ }
232
+ setActiveItem(item) {
233
+ if (!this.isOpen) {
234
+ return;
235
+ }
236
+ const index = this.actionableItems.indexOf(item);
237
+ if (index >= 0) {
238
+ this.focusedIndex = index;
239
+ }
240
+ }
241
+ isItemFocused(item) {
242
+ if (!this.isOpen || this.focusedIndex < 0) {
243
+ return false;
244
+ }
245
+ const focusedItem = this.actionableItems[this.focusedIndex];
246
+ return focusedItem === item;
247
+ }
248
+ getItemTabIndex(item) {
249
+ return this.isItemFocused(item) ? 0 : -1;
250
+ }
251
+ applyTriggerAction(action, items, navigation) {
252
+ switch (action) {
253
+ case 'openToLast':
254
+ this.focusedIndex = items.length - 1;
255
+ return;
256
+ case 'open':
257
+ this.focusedIndex = this.resolveInitialActiveIndex(navigation);
258
+ return;
259
+ default:
260
+ assertNever(action);
261
+ }
262
+ }
263
+ applyOpenMenuAction(action, event, navigation, items) {
264
+ switch (action) {
265
+ case 'dismiss':
266
+ this.close(false);
267
+ return;
268
+ case 'next':
269
+ suppressKeyboardEvent(event, navigation.preventDefault);
270
+ this.focusedIndex = moveLinearIndex(this.focusedIndex, 1, items.length, navigation.loop);
271
+ return;
272
+ case 'previous':
273
+ suppressKeyboardEvent(event, navigation.preventDefault);
274
+ this.focusedIndex = moveLinearIndex(this.focusedIndex, -1, items.length, navigation.loop);
275
+ return;
276
+ case 'first':
277
+ suppressKeyboardEvent(event, navigation.preventDefault);
278
+ this.focusedIndex = 0;
279
+ return;
280
+ case 'last':
281
+ suppressKeyboardEvent(event, navigation.preventDefault);
282
+ this.focusedIndex = items.length - 1;
283
+ return;
284
+ case 'activate':
285
+ suppressKeyboardEvent(event, navigation.preventDefault);
286
+ this.activateFocused(items);
287
+ return;
288
+ case 'close':
289
+ suppressKeyboardEvent(event, navigation.preventDefault);
290
+ this.close(true);
291
+ return;
292
+ default:
293
+ assertNever(action);
294
+ }
295
+ }
296
+ resolveNavigation() {
297
+ const input = typeof this.config.keyboardNavigation === 'function'
298
+ ? this.config.keyboardNavigation()
299
+ : this.config.keyboardNavigation;
300
+ return resolveMenuKeyboardNavigation(input);
301
+ }
302
+ resolveInitialActiveIndex(navigation) {
303
+ return resolveInitialActiveIndex(this.actionableItems, (item) => this.getItemId(item), navigation.initialActiveItemId);
304
+ }
305
+ getItemId(item) {
306
+ if (this.config.getItemId) {
307
+ return this.config.getItemId(item);
308
+ }
309
+ return item.id;
310
+ }
311
+ activateFocused(items) {
312
+ const item = items[this.focusedIndex];
313
+ if (!item) {
314
+ return;
315
+ }
316
+ this.config.onSelect(item);
317
+ this.close(true);
318
+ }
319
+ }
320
+ const DEFAULT_ACTIVE_ITEM_CLASS$1 = 'menu-keyboard__item--active';
321
+
322
+ const MENU_KEYBOARD_PORT = new InjectionToken('MENU_KEYBOARD_PORT');
323
+
324
+ const DEFAULT_ACTIVE_ITEM_CLASS = 'menu-keyboard__item--active';
325
+ class MenuKeyboardFacade {
326
+ static nextListId = 0;
327
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
328
+ focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
329
+ focusRequestVersion = signal(0, ...(ngDevMode ? [{ debugName: "focusRequestVersion" }] : []));
330
+ activeItemClass = signal(DEFAULT_ACTIVE_ITEM_CLASS, ...(ngDevMode ? [{ debugName: "activeItemClass" }] : []));
331
+ listId = signal(`v-menu-kb-list-${MenuKeyboardFacade.nextListId++}`, ...(ngDevMode ? [{ debugName: "listId" }] : []));
332
+ actionableItems = signal([], ...(ngDevMode ? [{ debugName: "actionableItems" }] : []));
333
+ triggerElement = null;
334
+ listElement = null;
335
+ itemElements = new Map();
336
+ activeItemHandler = null;
337
+ triggerKeydownHandler = null;
338
+ focusItemHandler = null;
339
+ syncTriggerHandler = null;
340
+ syncItemHandler = null;
341
+ portProvider = null;
342
+ setPortProvider(provider) {
343
+ this.portProvider = provider;
344
+ }
345
+ setActiveItemHandler(handler) {
346
+ this.activeItemHandler = handler;
347
+ }
348
+ setTriggerKeydownHandler(handler) {
349
+ this.triggerKeydownHandler = handler;
350
+ }
351
+ setFocusItemHandler(handler) {
352
+ this.focusItemHandler = handler;
353
+ }
354
+ setSyncTriggerHandler(handler) {
355
+ this.syncTriggerHandler = handler;
356
+ }
357
+ setSyncItemHandler(handler) {
358
+ this.syncItemHandler = handler;
359
+ }
360
+ registerTrigger(element) {
361
+ this.triggerElement = element;
362
+ }
363
+ unregisterTrigger(element) {
364
+ if (this.triggerElement === element) {
365
+ this.triggerElement = null;
366
+ }
367
+ }
368
+ registerList(element) {
369
+ this.listElement = element;
370
+ }
371
+ unregisterList(element) {
372
+ if (this.listElement === element) {
373
+ this.listElement = null;
374
+ }
375
+ }
376
+ registerItem(item, element) {
377
+ this.itemElements.set(item, element);
378
+ }
379
+ unregisterItem(item, element) {
380
+ const registered = this.itemElements.get(item);
381
+ if (registered === element) {
382
+ this.itemElements.delete(item);
383
+ }
384
+ }
385
+ unregisterItemElement(element) {
386
+ for (const [item, registered] of this.itemElements.entries()) {
387
+ if (registered === element) {
388
+ this.itemElements.delete(item);
389
+ }
390
+ }
391
+ }
392
+ getTriggerElement() {
393
+ return this.triggerElement;
394
+ }
395
+ getListElement() {
396
+ return this.listElement;
397
+ }
398
+ publish(controller, items) {
399
+ const wasOpen = this.isOpen();
400
+ const previousFocusedIndex = this.focusedIndex();
401
+ const nextFocusedIndex = controller.focusedIndex;
402
+ this.isOpen.set(controller.isOpen);
403
+ this.focusedIndex.set(nextFocusedIndex);
404
+ this.activeItemClass.set(controller.getActiveItemClass());
405
+ this.actionableItems.set(filterEnabledItems(items));
406
+ if (controller.isOpen &&
407
+ wasOpen &&
408
+ nextFocusedIndex >= 0 &&
409
+ previousFocusedIndex !== nextFocusedIndex) {
410
+ this.focusRequestVersion.update((value) => value + 1);
411
+ }
412
+ }
413
+ isItemActive(item) {
414
+ if (!this.isOpen() || this.focusedIndex() < 0) {
415
+ return false;
416
+ }
417
+ const items = this.actionableItems();
418
+ return items[this.focusedIndex()] === item;
419
+ }
420
+ getItemTabIndex(item) {
421
+ return this.isItemActive(item) ? 0 : -1;
422
+ }
423
+ requestActiveItem(item) {
424
+ this.activeItemHandler?.(item);
425
+ }
426
+ requestTriggerKeydown(event) {
427
+ this.triggerKeydownHandler?.(event);
428
+ }
429
+ getActiveItemElement() {
430
+ const items = this.actionableItems();
431
+ const index = this.focusedIndex();
432
+ if (!this.isOpen() || index < 0 || index >= items.length) {
433
+ return null;
434
+ }
435
+ return this.itemElements.get(items[index]) ?? null;
436
+ }
437
+ requestItemFocus(host, a11y) {
438
+ if (this.focusItemHandler) {
439
+ this.focusItemHandler(host, a11y);
440
+ return;
441
+ }
442
+ host.focus({ preventScroll: true });
443
+ }
444
+ requestTriggerAccessibilitySync(host, a11y) {
445
+ this.syncTriggerHandler?.(host, a11y);
446
+ }
447
+ requestItemAccessibilitySync(host, a11y) {
448
+ this.syncItemHandler?.(host, a11y);
449
+ }
450
+ resolveItemFromTarget(target) {
451
+ for (const [item, element] of this.itemElements.entries()) {
452
+ if (element.contains(target)) {
453
+ return item;
454
+ }
455
+ }
456
+ return null;
457
+ }
458
+ getPort() {
459
+ return this.portProvider?.() ?? null;
460
+ }
461
+ }
462
+
463
+ const MENU_KEYBOARD = new InjectionToken('MENU_KEYBOARD');
464
+
465
+ class MenuKeyboardDirective {
466
+ menuKeyboardItems = input([], ...(ngDevMode ? [{ debugName: "menuKeyboardItems" }] : []));
467
+ menuKeyboardNavigation = input(undefined, ...(ngDevMode ? [{ debugName: "menuKeyboardNavigation" }] : []));
468
+ menuKeyboardEnableWhenUnset = input(false, ...(ngDevMode ? [{ debugName: "menuKeyboardEnableWhenUnset" }] : []));
469
+ menuKeyboardDefaults = input({}, ...(ngDevMode ? [{ debugName: "menuKeyboardDefaults" }] : []));
470
+ menuKeyboardItemId = input((item) => item.id, ...(ngDevMode ? [{ debugName: "menuKeyboardItemId" }] : []));
471
+ menuKeyboardSelect = output();
472
+ facade = inject((MenuKeyboardFacade));
473
+ isOpen = this.facade.isOpen;
474
+ focusedIndex = this.facade.focusedIndex;
475
+ activeItemClass = this.facade.activeItemClass;
476
+ listId = this.facade.listId;
477
+ elementRef = inject((ElementRef));
478
+ document = inject(DOCUMENT);
479
+ injector = inject(Injector);
480
+ destroyRef = inject(DestroyRef);
481
+ onDocumentKeydown = (event) => this.handleDocumentKeydown(event);
482
+ portOperationState = 'idle';
483
+ pendingPortIntent = null;
484
+ controller = new MenuKeyboardController({
485
+ getItems: () => this.menuKeyboardItems(),
486
+ getItemId: (item) => this.menuKeyboardItemId()(item),
487
+ keyboardNavigation: () => this.resolveNavigation(),
488
+ onSelect: (item) => this.menuKeyboardSelect.emit(item),
489
+ onOpen: () => {
490
+ this.schedulePortIntent({ type: 'open' });
491
+ },
492
+ onClose: (restoreFocus) => {
493
+ this.schedulePortIntent({ type: 'close', restoreFocus });
494
+ },
495
+ });
496
+ constructor() {
497
+ this.facade.setPortProvider(() => this.getPort());
498
+ this.facade.setActiveItemHandler((item) => {
499
+ this.controller.setActiveItem(item);
500
+ this.publishState();
501
+ });
502
+ this.facade.setTriggerKeydownHandler((event) => {
503
+ this.handleTriggerKeydown(event);
504
+ });
505
+ this.facade.setFocusItemHandler((host, a11y) => {
506
+ this.focusItemHost(host, a11y);
507
+ });
508
+ this.facade.setSyncTriggerHandler((host, a11y) => {
509
+ this.applyTriggerAccessibility(host, a11y);
510
+ });
511
+ this.facade.setSyncItemHandler((host, a11y) => {
512
+ this.applyItemAccessibility(host, a11y);
513
+ });
514
+ this.destroyRef.onDestroy(() => {
515
+ this.facade.setTriggerKeydownHandler(() => undefined);
516
+ this.facade.setFocusItemHandler(() => undefined);
517
+ this.facade.setSyncTriggerHandler(() => undefined);
518
+ this.facade.setSyncItemHandler(() => undefined);
519
+ this.facade.setPortProvider(() => null);
520
+ this.unbindDocumentKeydown();
521
+ });
522
+ }
523
+ handleOpenChange(open) {
524
+ this.controller.handleOpenChange(open);
525
+ if (open && !this.controller.isOpen) {
526
+ void this.schedulePortIntent({ type: 'close', restoreFocus: false });
527
+ }
528
+ if (this.controller.isOpen) {
529
+ this.bindDocumentKeydown();
530
+ }
531
+ else {
532
+ this.unbindDocumentKeydown();
533
+ }
534
+ this.publishState();
535
+ }
536
+ close(restoreFocus = false) {
537
+ this.schedulePortIntent({ type: 'close', restoreFocus });
538
+ }
539
+ focusTrigger() {
540
+ const trigger = this.facade.getTriggerElement();
541
+ if (!trigger) {
542
+ return;
543
+ }
544
+ const a11y = {
545
+ ariaHaspopup: 'menu',
546
+ ariaExpanded: String(this.facade.isOpen()),
547
+ ariaControls: this.facade.listId(),
548
+ };
549
+ const port = this.getPort();
550
+ if (port?.focusTrigger) {
551
+ port.focusTrigger(trigger, a11y);
552
+ return;
553
+ }
554
+ trigger.focus({ preventScroll: true });
555
+ }
556
+ focusActiveItem() {
557
+ if (!this.controller.isOpen || this.controller.focusedIndex < 0) {
558
+ return;
559
+ }
560
+ const items = this.controller.actionableItems;
561
+ const item = items[this.controller.focusedIndex];
562
+ const host = this.facade.getActiveItemElement();
563
+ if (!item || !host) {
564
+ return;
565
+ }
566
+ this.focusItemHost(host, this.buildItemFocusA11y(item));
567
+ }
568
+ getTriggerElement() {
569
+ return this.facade.getTriggerElement() ?? undefined;
570
+ }
571
+ getListElement() {
572
+ return this.facade.getListElement() ?? undefined;
573
+ }
574
+ isItemActive(item) {
575
+ return this.facade.isItemActive(item);
576
+ }
577
+ getItemTabIndex(item) {
578
+ return this.facade.getItemTabIndex(item);
579
+ }
580
+ hasActionableItems() {
581
+ return this.controller.actionableItems.length > 0;
582
+ }
583
+ handleTriggerKeydown(event) {
584
+ if (this.controller.isOpen || !this.controller.isNavigationEnabled()) {
585
+ return;
586
+ }
587
+ this.controller.onTriggerKeydown(event);
588
+ this.publishState();
589
+ }
590
+ handleDocumentKeydown(event) {
591
+ if (!this.controller.isOpen || !this.shouldHandleDocumentKeydown(event)) {
592
+ return;
593
+ }
594
+ this.controller.onMenuKeydown(event);
595
+ this.publishState();
596
+ }
597
+ shouldHandleDocumentKeydown(event) {
598
+ const host = this.elementRef.nativeElement;
599
+ const listElement = this.facade.getListElement();
600
+ const path = event.composedPath();
601
+ if (path.includes(host)) {
602
+ return true;
603
+ }
604
+ return !!(listElement && path.includes(listElement));
605
+ }
606
+ bindDocumentKeydown() {
607
+ this.document.removeEventListener('keydown', this.onDocumentKeydown, true);
608
+ this.document.addEventListener('keydown', this.onDocumentKeydown, true);
609
+ }
610
+ unbindDocumentKeydown() {
611
+ this.document.removeEventListener('keydown', this.onDocumentKeydown, true);
612
+ }
613
+ focusItemHost(host, a11y) {
614
+ const resolvedA11y = a11y ??
615
+ (() => {
616
+ const item = this.facade.resolveItemFromTarget(host);
617
+ return item ? this.buildItemFocusA11y(item) : undefined;
618
+ })();
619
+ const port = this.getPort();
620
+ if (port?.focusItem) {
621
+ port.focusItem(host, resolvedA11y);
622
+ return;
623
+ }
624
+ host.focus({ preventScroll: true });
625
+ }
626
+ applyTriggerAccessibility(host, a11y) {
627
+ this.getPort()?.applyTriggerAccessibility?.(host, a11y);
628
+ }
629
+ applyItemAccessibility(host, a11y) {
630
+ this.getPort()?.applyItemAccessibility?.(host, a11y);
631
+ }
632
+ buildItemFocusA11y(item) {
633
+ return {
634
+ role: 'menuitem',
635
+ tabIndex: this.getItemTabIndex(item),
636
+ ariaDisabled: String(item.disabled ?? false),
637
+ };
638
+ }
639
+ publishState() {
640
+ this.facade.publish(this.controller, this.menuKeyboardItems());
641
+ }
642
+ schedulePortIntent(intent) {
643
+ if (this.portOperationState === 'idle') {
644
+ void this.executePortIntent(intent);
645
+ return;
646
+ }
647
+ this.pendingPortIntent = intent;
648
+ }
649
+ async executePortIntent(intent) {
650
+ switch (intent.type) {
651
+ case 'open':
652
+ await this.openViaPort();
653
+ return;
654
+ case 'close':
655
+ await this.closeViaPort(intent.restoreFocus);
656
+ return;
657
+ default:
658
+ assertNever(intent);
659
+ }
660
+ }
661
+ async drainPendingPortIntent() {
662
+ const intent = this.pendingPortIntent;
663
+ this.pendingPortIntent = null;
664
+ if (!intent) {
665
+ return;
666
+ }
667
+ await this.executePortIntent(intent);
668
+ }
669
+ async openViaPort() {
670
+ const port = this.getPort();
671
+ if (!port) {
672
+ this.controller.cancelPendingOpen();
673
+ this.publishState();
674
+ return;
675
+ }
676
+ this.portOperationState = 'opening';
677
+ try {
678
+ const opened = await port.open();
679
+ if (opened === false) {
680
+ this.controller.cancelPendingOpen();
681
+ }
682
+ }
683
+ catch (error) {
684
+ this.controller.cancelPendingOpen();
685
+ this.reportPortError('open', error);
686
+ }
687
+ finally {
688
+ this.portOperationState = 'idle';
689
+ this.publishState();
690
+ await this.drainPendingPortIntent();
691
+ }
692
+ }
693
+ async closeViaPort(restoreFocus) {
694
+ const port = this.getPort();
695
+ if (!port) {
696
+ return;
697
+ }
698
+ this.portOperationState = 'closing';
699
+ try {
700
+ await port.close(restoreFocus);
701
+ }
702
+ catch (error) {
703
+ this.reportPortError('close', error);
704
+ }
705
+ finally {
706
+ this.portOperationState = 'idle';
707
+ this.publishState();
708
+ await this.drainPendingPortIntent();
709
+ }
710
+ }
711
+ reportPortError(operation, error) {
712
+ if (isDevMode()) {
713
+ console.warn(`[vMenuKeyboard] Port ${operation} failed.`, error);
714
+ }
715
+ }
716
+ getPort() {
717
+ return this.injector.get(MENU_KEYBOARD_PORT, null, { optional: true });
718
+ }
719
+ resolveNavigation() {
720
+ const input = this.menuKeyboardNavigation();
721
+ const defaults = this.menuKeyboardDefaults();
722
+ if (input === false) {
723
+ return false;
724
+ }
725
+ if (input === undefined) {
726
+ if (!this.menuKeyboardEnableWhenUnset()) {
727
+ return undefined;
728
+ }
729
+ return Object.keys(defaults).length ? defaults : true;
730
+ }
731
+ if (input === true) {
732
+ return Object.keys(defaults).length ? defaults : true;
733
+ }
734
+ return {
735
+ ...defaults,
736
+ ...input,
737
+ };
738
+ }
739
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
740
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: MenuKeyboardDirective, isStandalone: true, selector: "[vMenuKeyboard]", inputs: { menuKeyboardItems: { classPropertyName: "menuKeyboardItems", publicName: "menuKeyboardItems", isSignal: true, isRequired: false, transformFunction: null }, menuKeyboardNavigation: { classPropertyName: "menuKeyboardNavigation", publicName: "menuKeyboardNavigation", isSignal: true, isRequired: false, transformFunction: null }, menuKeyboardEnableWhenUnset: { classPropertyName: "menuKeyboardEnableWhenUnset", publicName: "menuKeyboardEnableWhenUnset", isSignal: true, isRequired: false, transformFunction: null }, menuKeyboardDefaults: { classPropertyName: "menuKeyboardDefaults", publicName: "menuKeyboardDefaults", isSignal: true, isRequired: false, transformFunction: null }, menuKeyboardItemId: { classPropertyName: "menuKeyboardItemId", publicName: "menuKeyboardItemId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuKeyboardSelect: "menuKeyboardSelect" }, providers: [
741
+ MenuKeyboardFacade,
742
+ { provide: MENU_KEYBOARD, useExisting: MenuKeyboardFacade },
743
+ ], exportAs: ["menuKeyboard"], ngImport: i0 });
744
+ }
745
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardDirective, decorators: [{
746
+ type: Directive,
747
+ args: [{
748
+ selector: '[vMenuKeyboard]',
749
+ exportAs: 'menuKeyboard',
750
+ providers: [
751
+ MenuKeyboardFacade,
752
+ { provide: MENU_KEYBOARD, useExisting: MenuKeyboardFacade },
753
+ ],
754
+ }]
755
+ }], ctorParameters: () => [], propDecorators: { menuKeyboardItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardItems", required: false }] }], menuKeyboardNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardNavigation", required: false }] }], menuKeyboardEnableWhenUnset: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardEnableWhenUnset", required: false }] }], menuKeyboardDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardDefaults", required: false }] }], menuKeyboardItemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardItemId", required: false }] }], menuKeyboardSelect: [{ type: i0.Output, args: ["menuKeyboardSelect"] }] } });
756
+
757
+ class MenuKeyboardTriggerDirective {
758
+ facade = inject(MENU_KEYBOARD);
759
+ elementRef = inject((ElementRef));
760
+ destroyRef = inject(DestroyRef);
761
+ constructor() {
762
+ const element = this.elementRef.nativeElement;
763
+ this.facade.registerTrigger(element);
764
+ effect(() => {
765
+ this.facade.requestTriggerAccessibilitySync(element, {
766
+ ariaHaspopup: 'menu',
767
+ ariaExpanded: String(this.facade.isOpen()),
768
+ ariaControls: this.facade.listId(),
769
+ });
770
+ });
771
+ this.destroyRef.onDestroy(() => {
772
+ this.facade.unregisterTrigger(element);
773
+ });
774
+ }
775
+ onKeydown(event) {
776
+ this.facade.requestTriggerKeydown(event);
777
+ }
778
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
779
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: MenuKeyboardTriggerDirective, isStandalone: true, selector: "[vMenuKeyboardTrigger]", host: { attributes: { "aria-haspopup": "menu" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.aria-expanded": "facade.isOpen()", "attr.aria-controls": "facade.listId()" } }, ngImport: i0 });
780
+ }
781
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardTriggerDirective, decorators: [{
782
+ type: Directive,
783
+ args: [{
784
+ selector: '[vMenuKeyboardTrigger]',
785
+ host: {
786
+ 'aria-haspopup': 'menu',
787
+ '[attr.aria-expanded]': 'facade.isOpen()',
788
+ '[attr.aria-controls]': 'facade.listId()',
789
+ '(keydown)': 'onKeydown($event)',
790
+ },
791
+ }]
792
+ }], ctorParameters: () => [] });
793
+
794
+ class MenuKeyboardListDirective {
795
+ facade = inject(MENU_KEYBOARD);
796
+ elementRef = inject((ElementRef));
797
+ destroyRef = inject(DestroyRef);
798
+ constructor() {
799
+ const element = this.elementRef.nativeElement;
800
+ this.facade.registerList(element);
801
+ this.destroyRef.onDestroy(() => {
802
+ this.facade.unregisterList(element);
803
+ });
804
+ }
805
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
806
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: MenuKeyboardListDirective, isStandalone: true, selector: "[vMenuKeyboardList]", host: { attributes: { "role": "menu", "tabindex": "-1" }, properties: { "id": "facade.listId()" } }, ngImport: i0 });
807
+ }
808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardListDirective, decorators: [{
809
+ type: Directive,
810
+ args: [{
811
+ selector: '[vMenuKeyboardList]',
812
+ host: {
813
+ role: 'menu',
814
+ tabindex: '-1',
815
+ '[id]': 'facade.listId()',
816
+ },
817
+ }]
818
+ }], ctorParameters: () => [] });
819
+
820
+ class MenuKeyboardItemDirective {
821
+ item = input.required({ ...(ngDevMode ? { debugName: "item" } : {}), alias: 'menuKeyboardItem' });
822
+ facade = inject(MENU_KEYBOARD);
823
+ elementRef = inject((ElementRef));
824
+ destroyRef = inject(DestroyRef);
825
+ constructor() {
826
+ const element = this.elementRef.nativeElement;
827
+ effect((onCleanup) => {
828
+ const menuItem = this.item();
829
+ this.facade.registerItem(menuItem, element);
830
+ onCleanup(() => {
831
+ this.facade.unregisterItem(menuItem, element);
832
+ });
833
+ });
834
+ effect(() => {
835
+ const activeClass = this.facade.activeItemClass();
836
+ const isActive = this.facade.isItemActive(this.item());
837
+ element.classList.toggle(activeClass, isActive);
838
+ });
839
+ effect(() => {
840
+ this.facade.isOpen();
841
+ this.facade.focusedIndex();
842
+ this.facade.requestItemAccessibilitySync(element, {
843
+ role: 'menuitem',
844
+ tabIndex: this.facade.getItemTabIndex(this.item()),
845
+ ariaDisabled: String(this.item().disabled ?? false),
846
+ });
847
+ });
848
+ effect(() => {
849
+ this.facade.focusRequestVersion();
850
+ if (!this.facade.isOpen() || !this.facade.isItemActive(this.item())) {
851
+ return;
852
+ }
853
+ this.facade.requestItemFocus(element, {
854
+ role: 'menuitem',
855
+ tabIndex: this.facade.getItemTabIndex(this.item()),
856
+ ariaDisabled: String(this.item().disabled ?? false),
857
+ });
858
+ });
859
+ this.destroyRef.onDestroy(() => {
860
+ this.facade.unregisterItemElement(element);
861
+ });
862
+ }
863
+ onMouseEnter() {
864
+ const menuItem = this.item();
865
+ if (menuItem.disabled) {
866
+ return;
867
+ }
868
+ this.facade.requestActiveItem(menuItem);
869
+ }
870
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
871
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: MenuKeyboardItemDirective, isStandalone: true, selector: "[vMenuKeyboardItem]", inputs: { item: { classPropertyName: "item", publicName: "menuKeyboardItem", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "mouseenter": "onMouseEnter()" }, properties: { "attr.tabindex": "facade.getItemTabIndex(item())", "attr.aria-disabled": "item().disabled ?? false" } }, ngImport: i0 });
872
+ }
873
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: MenuKeyboardItemDirective, decorators: [{
874
+ type: Directive,
875
+ args: [{
876
+ selector: '[vMenuKeyboardItem]',
877
+ host: {
878
+ role: 'menuitem',
879
+ '[attr.tabindex]': 'facade.getItemTabIndex(item())',
880
+ '[attr.aria-disabled]': 'item().disabled ?? false',
881
+ '(mouseenter)': 'onMouseEnter()',
882
+ },
883
+ }]
884
+ }], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuKeyboardItem", required: true }] }] } });
885
+
15
886
  function setComponentProperties(component, value, firstChange = false) {
16
887
  if (!value || !component) {
17
888
  return;
@@ -2185,5 +3056,5 @@ class Format {
2185
3056
  * Generated bundle index. Do not edit.
2186
3057
  */
2187
3058
 
2188
- export { ACTION_BUTTON_GROUP_COMPONENT_TOKEN, BREADCRUMB_COMPONENT_TOKEN, BUTTON_COMPONENT_TOKEN, BaseFormDirective, BaseFormInputComponent, BaseInputControls, BreadcrumbCoreComponent, BreadcrumbService, ButtonShortCutDirective, CALENDAR_COMPONENT_TOKEN, CHECKBOX_COMPONENT_TOKEN, CONFIRM_DIALOG_COMPONENT_TOKEN, ColumnConfiguration, ColumnModel, ColumnVisibility, ControlSeverity, DEFAULT_DEBOUNCE_TIME, DEFAULT_PAGINATION, DROPDOWN_BUTTON_COMPONENT_TOKEN, DROPDOWN_COMPONENT_TOKEN, DatasourceDirective, DetailStoreDirective, DialogService, EnumToListPipe, ErrorCodesFns, ErrorPipe, FEATURE_LIST_COLUMN_PROVIDER, FEATURE_LIST_COMPONENT_TOKEN, FEATURE_LIST_PAGE_CONFIG_PROPERTY, FILTER_COMPONENT_TOKEN, FORM_FIELD_COMPONENT_TOKEN, FeatureListColumnDirective, FeatureListFilterFieldDirective, FeatureListFilterPipe, FieldAlign, FieldSize, FieldType, FilterFieldDirective, Format, GENERIC_FIELD_COMPONENT_TOKEN, GenericFieldType, GovButtonType, GovControlSeverity, HEADER_COMPONENT_TOKEN, ICONS_COMPONENT_TOKEN, INPUT_GROUP_COMPONENT_TOKEN, IconPosition, IconsComponent, KeyOrFunctionPipe, LOADER_COMPONENT_TOKEN, LOGO_ROUTER_ROUTE, LayoutType, LinkRenderer, MAX_COLUMN_CHAR_COUNT, MENU_TOKEN, MULTISELECT_COMPONENT_TOKEN, MenuServiceDirective, NUMBER_INPUT_COMPONENT_TOKEN, PAGE_HEADER_COMPONENT_TOKEN, PASSWORD_COMPONENT_TOKEN, PageHeaderCoreComponent, PageHeaderService, PasswordStrength, Position, PreventUnsavedChangesDirective, RADIOBUTTON_COMPONENT_TOKEN, Renderer, RowModel, SECTION_COMPONENT_TOKEN, SETTINGS_MENU, SIDE_MENU_COMPONENT_TOKEN, SIDE_MENU_STATE_TOKEN, SLIDER_COMPONENT_TOKEN, SNACKBAR_COMPONENT_TOKEN, STEPPER_COMPONENT_TOKEN, SWITCH_COMPONENT_TOKEN, ScreenSizeService, SideMenuProviderService, SideMenuService, SlotPosition, TABLE_COLUMN_PROVIDER, TABLE_COMPONENT_TOKEN, TABLE_FILTER_COMPONENT_TOKEN, TAB_VIEW_COMPONENT_TOKEN, TAG_COMPONENT_TOKEN, TEXTAREA_COMPONENT_TOKEN, TEXTFIELD_COMPONENT_TOKEN, TOOLTIP_COMPONENT_TOKEN, TableBuilder, TableButtonSeverity, TableColumnDirective, TableDatasourceDirective, TableFilterDirective, TableSelectionMode, TableService, UnsubscribeComponent, WarningCodesFns, WarningPipe, downloadFile, downloadText, getFilledControlCount, getFirstError, getFirstErrorFromControl, isFilterEmpty, isFormStateEqual, queryListChanged, routerRenderer, setComponentProperties, setDataToArray };
3059
+ export { ACTION_BUTTON_GROUP_COMPONENT_TOKEN, BREADCRUMB_COMPONENT_TOKEN, BUTTON_COMPONENT_TOKEN, BaseFormDirective, BaseFormInputComponent, BaseInputControls, BreadcrumbCoreComponent, BreadcrumbService, ButtonShortCutDirective, CALENDAR_COMPONENT_TOKEN, CHECKBOX_COMPONENT_TOKEN, CONFIRM_DIALOG_COMPONENT_TOKEN, ColumnConfiguration, ColumnModel, ColumnVisibility, ControlSeverity, DEFAULT_DEBOUNCE_TIME, DEFAULT_MENU_KEYBOARD_NAVIGATION, DEFAULT_PAGINATION, DROPDOWN_BUTTON_COMPONENT_TOKEN, DROPDOWN_COMPONENT_TOKEN, DatasourceDirective, DetailStoreDirective, DialogService, EnumToListPipe, ErrorCodesFns, ErrorPipe, FEATURE_LIST_COLUMN_PROVIDER, FEATURE_LIST_COMPONENT_TOKEN, FEATURE_LIST_PAGE_CONFIG_PROPERTY, FILTER_COMPONENT_TOKEN, FORM_FIELD_COMPONENT_TOKEN, FeatureListColumnDirective, FeatureListFilterFieldDirective, FeatureListFilterPipe, FieldAlign, FieldSize, FieldType, FilterFieldDirective, Format, GENERIC_FIELD_COMPONENT_TOKEN, GenericFieldType, GovButtonType, GovControlSeverity, HEADER_COMPONENT_TOKEN, ICONS_COMPONENT_TOKEN, INPUT_GROUP_COMPONENT_TOKEN, IconPosition, IconsComponent, KeyOrFunctionPipe, LOADER_COMPONENT_TOKEN, LOGO_ROUTER_ROUTE, LayoutType, LinkRenderer, MAX_COLUMN_CHAR_COUNT, MENU_KEYBOARD, MENU_KEYBOARD_PORT, MENU_TOKEN, MULTISELECT_COMPONENT_TOKEN, MenuKeyboardController, MenuKeyboardDirective, MenuKeyboardFacade, MenuKeyboardItemDirective, MenuKeyboardListDirective, MenuKeyboardTriggerDirective, MenuServiceDirective, NUMBER_INPUT_COMPONENT_TOKEN, PAGE_HEADER_COMPONENT_TOKEN, PASSWORD_COMPONENT_TOKEN, PageHeaderCoreComponent, PageHeaderService, PasswordStrength, Position, PreventUnsavedChangesDirective, RADIOBUTTON_COMPONENT_TOKEN, Renderer, RowModel, SECTION_COMPONENT_TOKEN, SETTINGS_MENU, SIDE_MENU_COMPONENT_TOKEN, SIDE_MENU_STATE_TOKEN, SLIDER_COMPONENT_TOKEN, SNACKBAR_COMPONENT_TOKEN, STEPPER_COMPONENT_TOKEN, SWITCH_COMPONENT_TOKEN, ScreenSizeService, SideMenuProviderService, SideMenuService, SlotPosition, TABLE_COLUMN_PROVIDER, TABLE_COMPONENT_TOKEN, TABLE_FILTER_COMPONENT_TOKEN, TAB_VIEW_COMPONENT_TOKEN, TAG_COMPONENT_TOKEN, TEXTAREA_COMPONENT_TOKEN, TEXTFIELD_COMPONENT_TOKEN, TOOLTIP_COMPONENT_TOKEN, TableBuilder, TableButtonSeverity, TableColumnDirective, TableDatasourceDirective, TableFilterDirective, TableSelectionMode, TableService, UnsubscribeComponent, WarningCodesFns, WarningPipe, assertNever, downloadFile, downloadText, filterEnabledItems, getFilledControlCount, getFirstError, getFirstErrorFromControl, getRovingTabIndex, isFilterEmpty, isFormStateEqual, matchesKeyboardKey, moveLinearIndex, queryListChanged, resolveInitialActiveIndex, resolveMenuKeyboardAction, resolveMenuKeyboardNavigation, resolveMenuTriggerAction, routerRenderer, setComponentProperties, setDataToArray, suppressKeyboardEvent };
2189
3060
  //# sourceMappingURL=verisoft-ui-core.mjs.map