| 478 | |
| 479 | /** The menubar ui pattern class. */ |
| 480 | export class MenuBarPattern<V> { |
| 481 | /** Controls list behavior for the menu items. */ |
| 482 | readonly listBehavior: List<MenuItemPattern<V>, V>; |
| 483 | |
| 484 | /** The tab index of the menu. */ |
| 485 | readonly tabIndex = () => this.listBehavior.tabIndex(); |
| 486 | |
| 487 | /** The key used to navigate to the next item. */ |
| 488 | private readonly _nextKey = computed(() => { |
| 489 | return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; |
| 490 | }); |
| 491 | |
| 492 | /** The key used to navigate to the previous item. */ |
| 493 | private readonly _previousKey = computed(() => { |
| 494 | return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; |
| 495 | }); |
| 496 | |
| 497 | /** Represents the space key. Does nothing when the user is actively using typeahead. */ |
| 498 | readonly dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); |
| 499 | |
| 500 | /** The regexp used to decide if a key should trigger typeahead. */ |
| 501 | readonly typeaheadRegexp = /^.$/; |
| 502 | |
| 503 | /** Whether the menubar or any of its children are currently focused. */ |
| 504 | readonly isFocused = signal(false); |
| 505 | |
| 506 | /** Whether the menubar has been interacted with. */ |
| 507 | readonly hasBeenInteracted = signal(false); |
| 508 | |
| 509 | /** Whether the menubar is disabled. */ |
| 510 | readonly disabled = () => this.inputs.disabled(); |
| 511 | |
| 512 | /** Handles keyboard events for the menu. */ |
| 513 | readonly keydownManager = computed(() => { |
| 514 | return new KeyboardEventManager() |
| 515 | .on(this._nextKey, () => this.next(), {ignoreRepeat: false}) |
| 516 | .on(this._previousKey, () => this.prev(), {ignoreRepeat: false}) |
| 517 | .on('End', () => this.listBehavior.last()) |
| 518 | .on('Home', () => this.listBehavior.first()) |
| 519 | .on('Enter', () => this.inputs.activeItem()?.open({first: true})) |
| 520 | .on('ArrowUp', () => this.inputs.activeItem()?.open({last: true})) |
| 521 | .on('ArrowDown', () => this.inputs.activeItem()?.open({first: true})) |
| 522 | .on(this.dynamicSpaceKey, () => this.inputs.activeItem()?.open({first: true})) |
| 523 | .on(this.typeaheadRegexp, e => this.listBehavior.search(e.key)); |
| 524 | }); |
| 525 | |
| 526 | constructor(readonly inputs: MenuBarInputs<V>) { |
| 527 | this.listBehavior = new List<MenuItemPattern<V>, V>(inputs); |
| 528 | } |
| 529 | |
| 530 | /** Sets the default state for the menubar. */ |
| 531 | setDefaultState() { |
| 532 | const firstFocusable = this.listBehavior.navigationBehavior.peekFirst(); |
| 533 | if (firstFocusable) { |
| 534 | this.inputs.activeItem.set(firstFocusable); |
| 535 | } |
| 536 | } |
| 537 |
nothing calls this directly
no test coverage detected
searching dependent graphs…