@akcelik/strct 4.1.0 → 4.2.0

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.
@@ -2340,6 +2340,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2340
2340
  `, host: { class: 'strct-tabs' }, styles: [".strct-tabs{display:block}.strct-tabs__bar{display:flex;gap:2px;border-bottom:1px solid var(--b2);overflow-x:auto;white-space:nowrap;scrollbar-width:none}.strct-tabs__btn{appearance:none;background:transparent;border:0;cursor:pointer;font-family:var(--font);font-size:13px;font-weight:500;color:var(--t2);padding:9px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .14s ease,border-color .14s ease}.strct-tabs__btn:hover{color:var(--t1)}.strct-tabs__btn--active{color:var(--acc);border-bottom-color:var(--acc)}.strct-tabs__btn:disabled{color:var(--t4);cursor:not-allowed}.strct-tabs__panels{padding-top:16px}.strct-tab[hidden]{display:none}\n"] }]
2341
2341
  }], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTab), { isSignal: true }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], keepAlive: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepAlive", required: false }] }] } });
2342
2342
 
2343
+ let menuPanelCounter = 0;
2343
2344
  /**
2344
2345
  * Floating menu panel — portaled into `<body>` (so it escapes overflow /
2345
2346
  * transform clipping), positioned by its real measured size, with full keyboard
@@ -2381,15 +2382,26 @@ class StrctMenuPanel {
2381
2382
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2382
2383
  openSubIndex = signal(null, /* @ts-ignore */
2383
2384
  ...(ngDevMode ? [{ debugName: "openSubIndex" }] : /* istanbul ignore next */ []));
2385
+ /** Entries the keyboard visits: not dividers, and not a disabled entry that
2386
+ * has nothing to say — a disabled entry WITH a hint is reachable so its
2387
+ * reason can be read. (Before, disabled entries were "visited" but a
2388
+ * natively disabled button refused focus, stranding the keyboard.) */
2384
2389
  navIndices = computed(() => this.items()
2385
- .map((it, i) => (it.divider ? -1 : i))
2390
+ .map((it, i) => (it.divider || (it.disabled && !it.hint) ? -1 : i))
2386
2391
  .filter((i) => i >= 0), /* @ts-ignore */
2387
2392
  ...(ngDevMode ? [{ debugName: "navIndices" }] : /* istanbul ignore next */ []));
2393
+ uid = ++menuPanelCounter;
2394
+ hintId(i) {
2395
+ return `strct-menu-${this.uid}-hint-${i}`;
2396
+ }
2388
2397
  constructor() {
2389
2398
  this.posX.set(this.x());
2390
2399
  this.posY.set(this.y());
2391
2400
  afterNextRender(() => {
2392
- this.activeIndex.set(this.navIndices()[0] ?? 0);
2401
+ // Open on the first entry that can act; a disabled-but-hinted one is
2402
+ // reachable by arrow keys, not the landing spot.
2403
+ const nav = this.navIndices();
2404
+ this.activeIndex.set(nav.find((i) => !this.items()[i].disabled) ?? nav[0] ?? 0);
2393
2405
  this.clampToViewport();
2394
2406
  this.focusItem(this.activeIndex());
2395
2407
  });
@@ -2441,7 +2453,7 @@ class StrctMenuPanel {
2441
2453
  onHover(i) {
2442
2454
  this.activeIndex.set(i);
2443
2455
  const it = this.items()[i];
2444
- this.openSubIndex.set(it?.children?.length ? i : null);
2456
+ this.openSubIndex.set(it?.children?.length && !it.disabled ? i : null);
2445
2457
  }
2446
2458
  onLeave(i) {
2447
2459
  if (this.openSubIndex() === i)
@@ -2487,7 +2499,7 @@ class StrctMenuPanel {
2487
2499
  this.focusItem(this.navIndices().at(-1) ?? 0);
2488
2500
  break;
2489
2501
  case 'ArrowRight':
2490
- if (item?.children?.length) {
2502
+ if (item?.children?.length && !item.disabled) {
2491
2503
  event.preventDefault();
2492
2504
  event.stopPropagation();
2493
2505
  this.openSubIndex.set(this.activeIndex());
@@ -2532,13 +2544,21 @@ class StrctMenuPanel {
2532
2544
  <div class="strct-menu__sep" role="separator"></div>
2533
2545
  } @else {
2534
2546
  <div class="strct-menu__wrap" (mouseenter)="onHover(i)" (mouseleave)="onLeave(i)">
2547
+ <!-- aria-disabled, not [disabled]: a natively disabled button takes no
2548
+ focus and, in some browsers, no pointer events — so neither the
2549
+ keyboard nor the hint's tooltip could reach it. Activation is
2550
+ blocked in code. The tooltip sits on the button, not the wrapper:
2551
+ the wrapper also holds the submenu, whose entries would otherwise
2552
+ inherit this entry's title. -->
2535
2553
  <button
2536
2554
  type="button"
2537
2555
  class="strct-menu__item"
2538
2556
  [attr.data-idx]="i"
2539
2557
  [class.strct-menu__item--critical]="item.critical"
2540
2558
  [class.strct-menu__item--active]="i === activeIndex()"
2541
- [disabled]="item.disabled"
2559
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
2560
+ [attr.aria-describedby]="item.hint ? hintId(i) : null"
2561
+ [attr.title]="item.hint || null"
2542
2562
  role="menuitem"
2543
2563
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
2544
2564
  [attr.aria-expanded]="item.children?.length ? openSubIndex() === i : null"
@@ -2565,6 +2585,11 @@ class StrctMenuPanel {
2565
2585
  />
2566
2586
  }
2567
2587
  </button>
2588
+ @if (item.hint) {
2589
+ <!-- hidden: kept out of the entry's accessible NAME, yet still read
2590
+ as its description through aria-describedby. -->
2591
+ <span [id]="hintId(i)" hidden>{{ item.hint }}</span>
2592
+ }
2568
2593
  @if (openSubIndex() === i && item.children?.length) {
2569
2594
  <strct-menu-panel
2570
2595
  submenu
@@ -2581,7 +2606,7 @@ class StrctMenuPanel {
2581
2606
  }
2582
2607
  }
2583
2608
  </div>
2584
- `, isInline: true, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not(:disabled),.strct-menu__item--active:not(:disabled){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not(:disabled),.strct-menu__item--critical.strct-menu__item--active:not(:disabled){background:var(--critical-bg)}.strct-menu__item:disabled{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"], dependencies: [{ kind: "component", type: StrctMenuPanel, selector: "strct-menu-panel", inputs: ["items", "data", "x", "y", "submenu"], outputs: ["select", "close", "back"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2609
+ `, isInline: true, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not([aria-disabled=true]),.strct-menu__item--active:not([aria-disabled=true]){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not([aria-disabled=true]),.strct-menu__item--critical.strct-menu__item--active:not([aria-disabled=true]){background:var(--critical-bg)}.strct-menu__item[aria-disabled=true]{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"], dependencies: [{ kind: "component", type: StrctMenuPanel, selector: "strct-menu-panel", inputs: ["items", "data", "x", "y", "submenu"], outputs: ["select", "close", "back"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2585
2610
  }
2586
2611
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenuPanel, decorators: [{
2587
2612
  type: Component,
@@ -2592,13 +2617,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2592
2617
  <div class="strct-menu__sep" role="separator"></div>
2593
2618
  } @else {
2594
2619
  <div class="strct-menu__wrap" (mouseenter)="onHover(i)" (mouseleave)="onLeave(i)">
2620
+ <!-- aria-disabled, not [disabled]: a natively disabled button takes no
2621
+ focus and, in some browsers, no pointer events — so neither the
2622
+ keyboard nor the hint's tooltip could reach it. Activation is
2623
+ blocked in code. The tooltip sits on the button, not the wrapper:
2624
+ the wrapper also holds the submenu, whose entries would otherwise
2625
+ inherit this entry's title. -->
2595
2626
  <button
2596
2627
  type="button"
2597
2628
  class="strct-menu__item"
2598
2629
  [attr.data-idx]="i"
2599
2630
  [class.strct-menu__item--critical]="item.critical"
2600
2631
  [class.strct-menu__item--active]="i === activeIndex()"
2601
- [disabled]="item.disabled"
2632
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
2633
+ [attr.aria-describedby]="item.hint ? hintId(i) : null"
2634
+ [attr.title]="item.hint || null"
2602
2635
  role="menuitem"
2603
2636
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
2604
2637
  [attr.aria-expanded]="item.children?.length ? openSubIndex() === i : null"
@@ -2625,6 +2658,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2625
2658
  />
2626
2659
  }
2627
2660
  </button>
2661
+ @if (item.hint) {
2662
+ <!-- hidden: kept out of the entry's accessible NAME, yet still read
2663
+ as its description through aria-describedby. -->
2664
+ <span [id]="hintId(i)" hidden>{{ item.hint }}</span>
2665
+ }
2628
2666
  @if (openSubIndex() === i && item.children?.length) {
2629
2667
  <strct-menu-panel
2630
2668
  submenu
@@ -2647,7 +2685,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2647
2685
  '[style.left.px]': 'submenu() ? null : posX()',
2648
2686
  '[style.top.px]': 'submenu() ? subTop() : posY()',
2649
2687
  '[style.zIndex]': 'submenu() ? null : 1100',
2650
- }, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not(:disabled),.strct-menu__item--active:not(:disabled){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not(:disabled),.strct-menu__item--critical.strct-menu__item--active:not(:disabled){background:var(--critical-bg)}.strct-menu__item:disabled{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"] }]
2688
+ }, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not([aria-disabled=true]),.strct-menu__item--active:not([aria-disabled=true]){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not([aria-disabled=true]),.strct-menu__item--critical.strct-menu__item--active:not([aria-disabled=true]){background:var(--critical-bg)}.strct-menu__item[aria-disabled=true]{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"] }]
2651
2689
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], submenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "submenu", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], close: [{ type: i0.Output, args: ["close"] }], back: [{ type: i0.Output, args: ["back"] }] } });
2652
2690
  /**
2653
2691
  * Imperatively opens the data-driven menu panel, portaled into `<body>`. Shared
@@ -4045,7 +4083,7 @@ class StrctDropdown {
4045
4083
  onMenuKeydown(event) {
4046
4084
  if (this.popover())
4047
4085
  return;
4048
- const items = this.enabledItems();
4086
+ const items = this.navItems();
4049
4087
  if (!items.length)
4050
4088
  return;
4051
4089
  const idx = items.indexOf(event.target);
@@ -4065,7 +4103,9 @@ class StrctDropdown {
4065
4103
  }
4066
4104
  else if (key === 'Enter' || key === ' ') {
4067
4105
  event.preventDefault();
4068
- event.target.click();
4106
+ const target = event.target;
4107
+ if (target.getAttribute('aria-disabled') !== 'true')
4108
+ target.click();
4069
4109
  }
4070
4110
  else if (key === 'Tab') {
4071
4111
  this.close();
@@ -4076,6 +4116,20 @@ class StrctDropdown {
4076
4116
  ...this.host.nativeElement.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"]), strct-submenu .strct-submenu__trigger'),
4077
4117
  ];
4078
4118
  }
4119
+ /** What the arrow keys visit: enabled items, plus disabled ones that carry a
4120
+ * hint (so the reason can be read). The menu still opens on an enabled one. */
4121
+ navItems() {
4122
+ const host = this.host.nativeElement;
4123
+ const rows = [
4124
+ ...host.querySelectorAll('strct-dropdown-item'),
4125
+ ...host.querySelectorAll('strct-submenu .strct-submenu__trigger'),
4126
+ ].filter((el) => el.getAttribute('aria-disabled') !== 'true' ||
4127
+ el.classList.contains('strct-dd__item--hinted'));
4128
+ // Two queries, so restore DOM order explicitly. (A single comma-separated
4129
+ // selector is DOM-ordered in browsers, but not in every DOM implementation
4130
+ // — jsdom returns it grouped, which scrambles arrow-key order in tests.)
4131
+ return rows.sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
4132
+ }
4079
4133
  /** Focus the selected item if there is one, else the first — after render. */
4080
4134
  focusInitialItem() {
4081
4135
  setTimeout(() => {
@@ -4190,6 +4244,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
4190
4244
  },
4191
4245
  }]
4192
4246
  }] });
4247
+ let dropdownItemCounter = 0;
4193
4248
  /** A selectable row inside a `<strct-dropdown>`. */
4194
4249
  class StrctDropdownItem {
4195
4250
  /**
@@ -4204,15 +4259,42 @@ class StrctDropdownItem {
4204
4259
  critical = input(false, { ...(ngDevMode ? { debugName: "critical" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4205
4260
  /** Static disable flag. */
4206
4261
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4262
+ /**
4263
+ * A short explanation — typically why a disabled item is unavailable. Shown
4264
+ * as the tooltip and given to assistive technology as the description; never
4265
+ * rendered inline. A disabled item with a hint stays keyboard-reachable so
4266
+ * its reason can be read; activation stays blocked.
4267
+ */
4268
+ hint = input(null, /* @ts-ignore */
4269
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
4270
+ hintId = `strct-dd-item-${++dropdownItemCounter}-hint`;
4271
+ constructor() {
4272
+ // Without pointer-events:none (see the hinted style), a disabled item
4273
+ // would pass clicks to the consumer's (click). Capture-phase at the target
4274
+ // runs before those listeners, and stopping here also keeps the menu's own
4275
+ // activate-and-close handler from seeing it.
4276
+ const host = inject((ElementRef)).nativeElement;
4277
+ host.addEventListener('click', (event) => {
4278
+ if (!this.disabled())
4279
+ return;
4280
+ event.preventDefault();
4281
+ event.stopImmediatePropagation();
4282
+ }, { capture: true });
4283
+ }
4207
4284
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
4208
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDropdownItem, isStandalone: true, selector: "strct-dropdown-item", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, critical: { classPropertyName: "critical", publicName: "critical", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "selected() === null ? 'menuitem' : 'menuitemradio'", "attr.aria-checked": "selected()", "attr.tabindex": "disabled() ? null : -1", "class.strct-dd__item--critical": "critical()", "class.strct-dd__item--selected": "selected() === true", "attr.aria-disabled": "disabled() || null" }, classAttribute: "strct-dd__item" }, ngImport: i0, template: `@if (selected() !== null) {
4285
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDropdownItem, isStandalone: true, selector: "strct-dropdown-item", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, critical: { classPropertyName: "critical", publicName: "critical", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "selected() === null ? 'menuitem' : 'menuitemradio'", "attr.aria-checked": "selected()", "attr.tabindex": "disabled() && !hint() ? null : -1", "attr.title": "hint() || null", "attr.aria-describedby": "hint() ? hintId : null", "class.strct-dd__item--hinted": "!!hint()", "class.strct-dd__item--critical": "critical()", "class.strct-dd__item--selected": "selected() === true", "attr.aria-disabled": "disabled() || null" }, classAttribute: "strct-dd__item" }, ngImport: i0, template: `@if (selected() !== null) {
4209
4286
  <span class="strct-dd__check" aria-hidden="true">
4210
4287
  @if (selected()) {
4211
4288
  <strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
4212
4289
  }
4213
4290
  </span>
4214
4291
  }
4215
- <ng-content />`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4292
+ <ng-content />
4293
+ @if (hint()) {
4294
+ <!-- hidden: kept out of the item's accessible NAME, still read as its
4295
+ description through aria-describedby. -->
4296
+ <span [id]="hintId" hidden>{{ hint() }}</span>
4297
+ }`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4216
4298
  }
4217
4299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, decorators: [{
4218
4300
  type: Component,
@@ -4223,16 +4305,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
4223
4305
  }
4224
4306
  </span>
4225
4307
  }
4226
- <ng-content />`, host: {
4308
+ <ng-content />
4309
+ @if (hint()) {
4310
+ <!-- hidden: kept out of the item's accessible NAME, still read as its
4311
+ description through aria-describedby. -->
4312
+ <span [id]="hintId" hidden>{{ hint() }}</span>
4313
+ }`, host: {
4227
4314
  class: 'strct-dd__item',
4228
4315
  '[attr.role]': "selected() === null ? 'menuitem' : 'menuitemradio'",
4229
4316
  '[attr.aria-checked]': 'selected()',
4230
- '[attr.tabindex]': 'disabled() ? null : -1',
4317
+ // A disabled item is skipped — unless it has a hint to be read.
4318
+ '[attr.tabindex]': 'disabled() && !hint() ? null : -1',
4319
+ '[attr.title]': 'hint() || null',
4320
+ '[attr.aria-describedby]': 'hint() ? hintId : null',
4321
+ '[class.strct-dd__item--hinted]': '!!hint()',
4231
4322
  '[class.strct-dd__item--critical]': 'critical()',
4232
4323
  '[class.strct-dd__item--selected]': 'selected() === true',
4233
4324
  '[attr.aria-disabled]': 'disabled() || null',
4234
- }, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}\n"] }]
4235
- }], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], critical: [{ type: i0.Input, args: [{ isSignal: true, alias: "critical", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
4325
+ }, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"] }]
4326
+ }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], critical: [{ type: i0.Input, args: [{ isSignal: true, alias: "critical", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
4236
4327
  /** Thin separator between groups of menu items. */
4237
4328
  class StrctDropdownDivider {
4238
4329
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -4287,7 +4378,9 @@ class StrctContextMenu {
4287
4378
  // guessed size) and focus can land on the first item.
4288
4379
  setTimeout(() => {
4289
4380
  this.clampToViewport();
4290
- this.focusItem(0);
4381
+ // Land on the first entry that can act, not a disabled-but-hinted one.
4382
+ const first = this.navItems().findIndex((i) => i.getAttribute('aria-disabled') !== 'true');
4383
+ this.focusItem(Math.max(first, 0));
4291
4384
  });
4292
4385
  }
4293
4386
  close() {
@@ -4298,12 +4391,14 @@ class StrctContextMenu {
4298
4391
  restoreFocus(this.restoreTo);
4299
4392
  this.restoreTo = null;
4300
4393
  }
4301
- /** Enabled `strct-dropdown-item` elements in DOM order. */
4394
+ /** Keyboard-reachable `strct-dropdown-item` elements in DOM order: enabled
4395
+ * ones, plus disabled ones that carry a hint (so the reason can be read). */
4302
4396
  navItems() {
4303
4397
  const el = this.menuEl()?.nativeElement;
4304
4398
  if (!el)
4305
4399
  return [];
4306
- return Array.from(el.querySelectorAll('.strct-dd__item:not([aria-disabled="true"])'));
4400
+ return Array.from(el.querySelectorAll('.strct-dd__item')).filter((i) => i.getAttribute('aria-disabled') !== 'true' ||
4401
+ i.classList.contains('strct-dd__item--hinted'));
4307
4402
  }
4308
4403
  /**
4309
4404
  * Focus-based roving: items keep the `tabindex="-1"` their host binding
@@ -4516,8 +4611,10 @@ class StrctSubmenu {
4516
4611
  const panel = this.panel()?.nativeElement;
4517
4612
  // Dropdown-item rows carry tabindex=-1, outside FOCUSABLE_SELECTOR —
4518
4613
  // fall back to the first rovable row when nothing else is tabbable.
4519
- if (panel && !focusFirstIn(panel))
4520
- this.panelItems()[0]?.focus();
4614
+ if (panel && !focusFirstIn(panel)) {
4615
+ const items = this.panelItems();
4616
+ (items.find((el) => el.getAttribute('aria-disabled') !== 'true') ?? items[0])?.focus();
4617
+ }
4521
4618
  });
4522
4619
  }
4523
4620
  /**
@@ -4553,11 +4650,13 @@ class StrctSubmenu {
4553
4650
  items[key === 'Home' ? 0 : items.length - 1].focus();
4554
4651
  }
4555
4652
  }
4556
- /** Rovable rows of the open fly-out (dropdown items, skipping disabled). */
4653
+ /** Rovable rows of the open fly-out: enabled dropdown items, plus disabled
4654
+ * ones that carry a hint (so the reason can be read). */
4557
4655
  panelItems() {
4558
4656
  const panel = this.panel()?.nativeElement;
4559
4657
  return panel
4560
- ? [...panel.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"])')]
4658
+ ? [...panel.querySelectorAll('strct-dropdown-item')].filter((i) => i.getAttribute('aria-disabled') !== 'true' ||
4659
+ i.classList.contains('strct-dd__item--hinted'))
4561
4660
  : [];
4562
4661
  }
4563
4662
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSubmenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -18296,7 +18395,7 @@ class StrctSplitButton {
18296
18395
  /** Main action label. */
18297
18396
  label = input.required(/* @ts-ignore */
18298
18397
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
18299
- /** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?). */
18398
+ /** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?, hint?). */
18300
18399
  items = input([], /* @ts-ignore */
18301
18400
  ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
18302
18401
  /** Optional leading icon of the main segment. */
@@ -18344,6 +18443,7 @@ class StrctSplitButton {
18344
18443
  <strct-dropdown-item
18345
18444
  [critical]="item.critical ?? false"
18346
18445
  [disabled]="item.disabled ?? false"
18446
+ [hint]="item.hint"
18347
18447
  (click)="!item.disabled && picked.emit(item)"
18348
18448
  >
18349
18449
  @if (item.icon) {
@@ -18355,7 +18455,7 @@ class StrctSplitButton {
18355
18455
  }
18356
18456
  </strct-dropdown>
18357
18457
  </div>
18358
- `, isInline: true, styles: [".strct-sbt{display:inline-flex;align-items:stretch}.strct-sbt .strct-dd{display:inline-flex;align-self:stretch}.strct-sbt .strct-dd__trigger{display:inline-flex;align-items:stretch}.strct-sbt__chev{align-self:stretch;height:100%}.strct-sbt__main,.strct-sbt__chev{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--acc);background:transparent;color:var(--acc);font-family:var(--font);font-size:13px;font-weight:600;line-height:1;cursor:pointer;padding:var(--space-2) var(--space-4)}.strct-sbt__main{border-start-start-radius:var(--radius-md);border-end-start-radius:var(--radius-md);border-inline-end:0}.strct-sbt__chev{padding:var(--space-2);border-start-end-radius:var(--radius-md);border-end-end-radius:var(--radius-md);border-inline-start:1px solid var(--acc50)}.strct-sbt--solid .strct-sbt__main,.strct-sbt--solid .strct-sbt__chev{background:var(--acc);color:var(--inv)}.strct-sbt--solid .strct-sbt__chev{border-inline-start-color:color-mix(in srgb,var(--inv) 30%,var(--acc))}.strct-sbt__main:hover:not(:disabled),.strct-sbt__chev:hover:not(:disabled){background:var(--acc18)}.strct-sbt--solid .strct-sbt__main:hover:not(:disabled),.strct-sbt--solid .strct-sbt__chev:hover:not(:disabled){filter:brightness(1.08);background:var(--acc)}.strct-sbt__main:disabled,.strct-sbt__chev:disabled{opacity:.5;cursor:default}.strct-sbt__main:focus-visible,.strct-sbt__chev:focus-visible{outline:2px solid var(--acc50);outline-offset:1px;position:relative;z-index:var(--z-base)}\n"], dependencies: [{ kind: "component", type: StrctDropdown, selector: "strct-dropdown", inputs: ["align", "popover", "popoverLabel"] }, { kind: "component", type: StrctDropdownDivider, selector: "strct-dropdown-divider" }, { kind: "component", type: StrctDropdownItem, selector: "strct-dropdown-item", inputs: ["selected", "critical", "disabled"] }, { kind: "directive", type: StrctDropdownTrigger, selector: "[strctDropdownTrigger]" }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18458
+ `, isInline: true, styles: [".strct-sbt{display:inline-flex;align-items:stretch}.strct-sbt .strct-dd{display:inline-flex;align-self:stretch}.strct-sbt .strct-dd__trigger{display:inline-flex;align-items:stretch}.strct-sbt__chev{align-self:stretch;height:100%}.strct-sbt__main,.strct-sbt__chev{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--acc);background:transparent;color:var(--acc);font-family:var(--font);font-size:13px;font-weight:600;line-height:1;cursor:pointer;padding:var(--space-2) var(--space-4)}.strct-sbt__main{border-start-start-radius:var(--radius-md);border-end-start-radius:var(--radius-md);border-inline-end:0}.strct-sbt__chev{padding:var(--space-2);border-start-end-radius:var(--radius-md);border-end-end-radius:var(--radius-md);border-inline-start:1px solid var(--acc50)}.strct-sbt--solid .strct-sbt__main,.strct-sbt--solid .strct-sbt__chev{background:var(--acc);color:var(--inv)}.strct-sbt--solid .strct-sbt__chev{border-inline-start-color:color-mix(in srgb,var(--inv) 30%,var(--acc))}.strct-sbt__main:hover:not(:disabled),.strct-sbt__chev:hover:not(:disabled){background:var(--acc18)}.strct-sbt--solid .strct-sbt__main:hover:not(:disabled),.strct-sbt--solid .strct-sbt__chev:hover:not(:disabled){filter:brightness(1.08);background:var(--acc)}.strct-sbt__main:disabled,.strct-sbt__chev:disabled{opacity:.5;cursor:default}.strct-sbt__main:focus-visible,.strct-sbt__chev:focus-visible{outline:2px solid var(--acc50);outline-offset:1px;position:relative;z-index:var(--z-base)}\n"], dependencies: [{ kind: "component", type: StrctDropdown, selector: "strct-dropdown", inputs: ["align", "popover", "popoverLabel"] }, { kind: "component", type: StrctDropdownDivider, selector: "strct-dropdown-divider" }, { kind: "component", type: StrctDropdownItem, selector: "strct-dropdown-item", inputs: ["selected", "critical", "disabled", "hint"] }, { kind: "directive", type: StrctDropdownTrigger, selector: "[strctDropdownTrigger]" }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18359
18459
  }
18360
18460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSplitButton, decorators: [{
18361
18461
  type: Component,
@@ -18395,6 +18495,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18395
18495
  <strct-dropdown-item
18396
18496
  [critical]="item.critical ?? false"
18397
18497
  [disabled]="item.disabled ?? false"
18498
+ [hint]="item.hint"
18398
18499
  (click)="!item.disabled && picked.emit(item)"
18399
18500
  >
18400
18501
  @if (item.icon) {
@@ -18749,6 +18850,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18749
18850
  args: ['window:scroll']
18750
18851
  }] } });
18751
18852
 
18853
+ let menubarCounter = 0;
18752
18854
  /**
18753
18855
  * Horizontal menubar — the application-menu strip ("File · Edit · View") for
18754
18856
  * dense tool-style consoles:
@@ -18875,7 +18977,7 @@ class StrctMenubar {
18875
18977
  event.preventDefault();
18876
18978
  event.stopPropagation();
18877
18979
  const idx = Number(active?.getAttribute('data-idx'));
18878
- if (menu.items[idx]?.children?.length) {
18980
+ if (menu.items[idx]?.children?.length && !menu.items[idx].disabled) {
18879
18981
  this.subIdx.set(idx);
18880
18982
  setTimeout(() => this.navButtons(true)[0]?.focus());
18881
18983
  }
@@ -18926,18 +19028,24 @@ class StrctMenubar {
18926
19028
  restoreFocus(this.trigger);
18927
19029
  this.trigger = null;
18928
19030
  }
19031
+ uid = ++menubarCounter;
19032
+ hintId(menuId, i, j) {
19033
+ // menu ids are consumer data; aria-describedby splits on whitespace.
19034
+ const menu = menuId.replace(/\s+/g, '_');
19035
+ return `strct-mb-${this.uid}-${menu}-${i}${j == null ? '' : '-' + j}-hint`;
19036
+ }
18929
19037
  topButtons() {
18930
19038
  return Array.from(this.host.nativeElement.querySelectorAll('.strct-mb__top'));
18931
19039
  }
18932
- /** Enabled item buttons of the open menu — either its items or the open submenu's. */
19040
+ /** Keyboard-reachable item buttons of the open menu — either its items or the
19041
+ * open submenu's: enabled ones, plus disabled ones that carry a hint (so the
19042
+ * reason can be read). A disabled entry with nothing to say is skipped. */
18933
19043
  navButtons(sub) {
18934
19044
  const menuEl = this.host.nativeElement.querySelector('.strct-mb__menu');
18935
19045
  if (!menuEl)
18936
19046
  return [];
18937
- const sel = sub
18938
- ? '.strct-mb__subitem:not([disabled])'
18939
- : '.strct-mb__item:not([disabled]):not(.strct-mb__subitem)';
18940
- return Array.from(menuEl.querySelectorAll(sel));
19047
+ const sel = sub ? '.strct-mb__subitem' : '.strct-mb__item:not(.strct-mb__subitem)';
19048
+ return Array.from(menuEl.querySelectorAll(sel)).filter((b) => b.getAttribute('aria-disabled') !== 'true' || b.hasAttribute('aria-describedby'));
18941
19049
  }
18942
19050
  onDocClick(event) {
18943
19051
  if (this.openId() && !this.host.nativeElement.contains(event.target)) {
@@ -18989,7 +19097,9 @@ class StrctMenubar {
18989
19097
  [class.strct-mb__item--critical]="item.critical"
18990
19098
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
18991
19099
  [attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
18992
- [disabled]="item.disabled || null"
19100
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
19101
+ [attr.aria-describedby]="item.hint ? hintId(menu.id, i) : null"
19102
+ [attr.title]="item.hint || null"
18993
19103
  (click)="onItemClick(menu, item, i)"
18994
19104
  (mouseenter)="onItemHover(item, i)"
18995
19105
  >
@@ -19001,9 +19111,12 @@ class StrctMenubar {
19001
19111
  <strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
19002
19112
  }
19003
19113
  </button>
19114
+ @if (item.hint) {
19115
+ <span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
19116
+ }
19004
19117
  @if (subIdx() === i && item.children?.length) {
19005
19118
  <div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
19006
- @for (sub of item.children; track $index) {
19119
+ @for (sub of item.children; track $index; let j = $index) {
19007
19120
  @if (sub.divider) {
19008
19121
  <div class="strct-mb__divider" role="separator"></div>
19009
19122
  } @else {
@@ -19012,7 +19125,9 @@ class StrctMenubar {
19012
19125
  class="strct-mb__item strct-mb__subitem"
19013
19126
  role="menuitem"
19014
19127
  [class.strct-mb__item--critical]="sub.critical"
19015
- [disabled]="sub.disabled || null"
19128
+ [attr.aria-disabled]="sub.disabled ? 'true' : null"
19129
+ [attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
19130
+ [attr.title]="sub.hint || null"
19016
19131
  (click)="pick(menu, sub)"
19017
19132
  >
19018
19133
  @if (sub.icon) {
@@ -19020,6 +19135,9 @@ class StrctMenubar {
19020
19135
  }
19021
19136
  {{ sub.label }}
19022
19137
  </button>
19138
+ @if (sub.hint) {
19139
+ <span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
19140
+ }
19023
19141
  }
19024
19142
  }
19025
19143
  </div>
@@ -19032,7 +19150,7 @@ class StrctMenubar {
19032
19150
  </div>
19033
19151
  }
19034
19152
  </div>
19035
- `, isInline: true, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not(:disabled){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not(:disabled){background:var(--critical-bg)}.strct-mb__item:disabled{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19153
+ `, isInline: true, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not([aria-disabled=true]){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not([aria-disabled=true]){background:var(--critical-bg)}.strct-mb__item[aria-disabled=true]{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19036
19154
  }
19037
19155
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenubar, decorators: [{
19038
19156
  type: Component,
@@ -19074,7 +19192,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19074
19192
  [class.strct-mb__item--critical]="item.critical"
19075
19193
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
19076
19194
  [attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
19077
- [disabled]="item.disabled || null"
19195
+ [attr.aria-disabled]="item.disabled ? 'true' : null"
19196
+ [attr.aria-describedby]="item.hint ? hintId(menu.id, i) : null"
19197
+ [attr.title]="item.hint || null"
19078
19198
  (click)="onItemClick(menu, item, i)"
19079
19199
  (mouseenter)="onItemHover(item, i)"
19080
19200
  >
@@ -19086,9 +19206,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19086
19206
  <strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
19087
19207
  }
19088
19208
  </button>
19209
+ @if (item.hint) {
19210
+ <span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
19211
+ }
19089
19212
  @if (subIdx() === i && item.children?.length) {
19090
19213
  <div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
19091
- @for (sub of item.children; track $index) {
19214
+ @for (sub of item.children; track $index; let j = $index) {
19092
19215
  @if (sub.divider) {
19093
19216
  <div class="strct-mb__divider" role="separator"></div>
19094
19217
  } @else {
@@ -19097,7 +19220,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19097
19220
  class="strct-mb__item strct-mb__subitem"
19098
19221
  role="menuitem"
19099
19222
  [class.strct-mb__item--critical]="sub.critical"
19100
- [disabled]="sub.disabled || null"
19223
+ [attr.aria-disabled]="sub.disabled ? 'true' : null"
19224
+ [attr.aria-describedby]="sub.hint ? hintId(menu.id, i, j) : null"
19225
+ [attr.title]="sub.hint || null"
19101
19226
  (click)="pick(menu, sub)"
19102
19227
  >
19103
19228
  @if (sub.icon) {
@@ -19105,6 +19230,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19105
19230
  }
19106
19231
  {{ sub.label }}
19107
19232
  </button>
19233
+ @if (sub.hint) {
19234
+ <span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
19235
+ }
19108
19236
  }
19109
19237
  }
19110
19238
  </div>
@@ -19117,7 +19245,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19117
19245
  </div>
19118
19246
  }
19119
19247
  </div>
19120
- `, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not(:disabled){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not(:disabled){background:var(--critical-bg)}.strct-mb__item:disabled{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"] }]
19248
+ `, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:var(--radius-md)}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:var(--z-dropdown);min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__menu:focus{outline:none}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not([aria-disabled=true]){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not([aria-disabled=true]){background:var(--critical-bg)}.strct-mb__item[aria-disabled=true]{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}.strct-mb__row{position:relative}.strct-mb__caret{margin-inline-start:auto;color:var(--t3)}[dir=rtl] .strct-mb__caret{transform:rotate(180deg)}.strct-mb__submenu{position:absolute;top:-5px;inset-inline-start:calc(100% + 2px);z-index:var(--z-base);min-width:160px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);display:flex;flex-direction:column}\n"] }]
19121
19249
  }], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], onDocClick: [{
19122
19250
  type: HostListener,
19123
19251
  args: ['document:click', ['$event']]