@akcelik/strct 4.0.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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive, model, output, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, contentChild, viewChild, InjectionToken, untracked, Injector, viewChildren, TemplateRef, Renderer2, Pipe, PLATFORM_ID, afterRenderEffect } from '@angular/core';
2
+ import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive, model, output, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, TemplateRef, contentChild, viewChild, InjectionToken, untracked, Injector, viewChildren, Renderer2, Pipe, PLATFORM_ID, afterRenderEffect } from '@angular/core';
3
3
  import { DomSanitizer } from '@angular/platform-browser';
4
4
  import { NgTemplateOutlet, DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
5
5
  import { RouterLink, RouterLinkActive } from '@angular/router';
@@ -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
@@ -3344,6 +3382,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
3344
3382
  }, styles: [".strct-tree{display:block}.strct-tree--comfortable .strct-tnode__row{gap:7px;padding:6px 10px;border-radius:6px;font-size:14px}.strct-tree--comfortable .strct-tnode__chevron{margin:-4px;margin-inline-end:-9px}.strct-tree--comfortable .strct-tnode__spacer{width:16px}.strct-tree--comfortable .strct-tnode__children{margin-inline-start:19px}\n"] }]
3345
3383
  }], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], nodeMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeMenu", required: false }] }], chevronAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevronAriaLabel", required: false }] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }], nodeMenuSelect: [{ type: i0.Output, args: ["nodeMenuSelect"] }], expandedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIds", required: false }] }, { type: i0.Output, args: ["expandedIdsChange"] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }], nodeToggled: [{ type: i0.Output, args: ["nodeToggled"] }], projectedNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { ...{ descendants: false }, isSignal: true }] }] } });
3346
3384
 
3385
+ /**
3386
+ * Dev-mode diagnostics for combinations a component accepts but cannot honour.
3387
+ *
3388
+ * Every message names what was ignored and what to do instead. Keyed: a
3389
+ * condition warns once per page load, not once per change detection.
3390
+ *
3391
+ * Production cost: none, PROVIDED each call site is guarded INLINE with
3392
+ * if (typeof ngDevMode !== 'undefined' && ngDevMode) { … }
3393
+ * Production builds define `ngDevMode` as false, and the minifier then drops
3394
+ * the whole block — messages included. A helper function returning that same
3395
+ * expression would NOT be folded across the call, so the check and its
3396
+ * strings would ship; that is why no such helper exists here.
3397
+ */
3398
+ const warned = new Set();
3399
+ function strctDevWarn(key, message) {
3400
+ if (typeof ngDevMode === 'undefined' || !ngDevMode || warned.has(key))
3401
+ return;
3402
+ warned.add(key);
3403
+ console.warn(message);
3404
+ }
3405
+ /** Test hook: forget which warnings have fired. Not part of the public API. */
3406
+ function resetStrctDevWarnings() {
3407
+ warned.clear();
3408
+ }
3409
+
3347
3410
  /**
3348
3411
  * Refcounted body scroll-lock shared across any number of simultaneously
3349
3412
  * open transient surfaces (modal, drawer …): the first lock hides body
@@ -3378,6 +3441,27 @@ function removeOpenModal(modal) {
3378
3441
  function isTopmostModal(modal) {
3379
3442
  return openModalStack[openModalStack.length - 1] === modal;
3380
3443
  }
3444
+ /**
3445
+ * Lazily rendered modal body. Plain projected content is created by the PARENT
3446
+ * with the parent's own view, so it is instantiated — and costs — even while
3447
+ * the modal is closed. Content inside this template is created only while the
3448
+ * modal is open, and destroyed when it closes (state does not survive a close):
3449
+ *
3450
+ * <strct-modal [(open)]="show" title="Import hosts">
3451
+ * <ng-template strctModalContent>
3452
+ * <strct-datagrid [rows]="hosts" … />
3453
+ * </ng-template>
3454
+ * </strct-modal>
3455
+ */
3456
+ class StrctModalContent {
3457
+ template = inject(TemplateRef);
3458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctModalContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3459
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctModalContent, isStandalone: true, selector: "[strctModalContent]", ngImport: i0 });
3460
+ }
3461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctModalContent, decorators: [{
3462
+ type: Directive,
3463
+ args: [{ selector: '[strctModalContent]' }]
3464
+ }] });
3381
3465
  /**
3382
3466
  * Overlay dialog with two-way `open`:
3383
3467
  * <strct-modal [(open)]="show" title="Confirm">
@@ -3396,7 +3480,8 @@ class StrctModal {
3396
3480
  /** Dialog title. */
3397
3481
  title = input('', /* @ts-ignore */
3398
3482
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
3399
- /** Size variant (fixed scale; defaults to `sm` = 480px). */
3483
+ /** Size variant (fixed scale; defaults to `sm` = 480px). Ignored by
3484
+ * `chromeless`, which sizes from the wizard it hosts. */
3400
3485
  size = input('sm', /* @ts-ignore */
3401
3486
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3402
3487
  /** Hide the footer slot. */
@@ -3406,6 +3491,11 @@ class StrctModal {
3406
3491
  * content (a `flush` vertical wizard) is the dialog surface. `title` still
3407
3492
  * names the dialog for assistive tech; backdrop/Escape dismissal follow
3408
3493
  * `dismissible` as usual.
3494
+ *
3495
+ * The dialog's width comes from the wizard's geometry, not from `size`:
3496
+ * rail + `--strct-wiz-content-min` (default 864px) [+ aside]. To change it,
3497
+ * set that variable on this `strct-modal` or an ancestor — the dialog reads
3498
+ * it on itself, so setting it on the `strct-wizard` inside does not reach it.
3409
3499
  */
3410
3500
  chromeless = input(false, { ...(ngDevMode ? { debugName: "chromeless" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3411
3501
  /** Accessible label of the X close button (localizable). */
@@ -3431,6 +3521,22 @@ class StrctModal {
3431
3521
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3432
3522
  /** Emitted when the alert is dismissed. */
3433
3523
  closed = output();
3524
+ /** Lazily rendered body, if one is provided (see {@link StrctModalContent}). */
3525
+ lazyContent = contentChild(StrctModalContent, /* @ts-ignore */
3526
+ ...(ngDevMode ? [{ debugName: "lazyContent" }] : /* istanbul ignore next */ []));
3527
+ // Dev mode only: `size` is accepted alongside `chromeless` but cannot apply.
3528
+ sizeDiagnostic = typeof ngDevMode !== 'undefined' && ngDevMode
3529
+ ? effect(() => {
3530
+ const size = this.size();
3531
+ if (this.chromeless() && size !== 'sm') {
3532
+ strctDevWarn(`modal:chromeless-size:${size}`, `[strct-modal] size="${size}" has no effect with chromeless: a chromeless ` +
3533
+ `dialog sizes itself from the wizard it hosts (rail + --strct-wiz-content-min, ` +
3534
+ `default 864px). To change its width, set --strct-wiz-content-min on the ` +
3535
+ `strct-modal or an ancestor — on the strct-wizard alone it does not reach ` +
3536
+ `the dialog.`);
3537
+ }
3538
+ })
3539
+ : null;
3434
3540
  titleId = `strct-modal-${++modalCounter}`;
3435
3541
  /** Element that had focus before the dialog opened, restored on close. */
3436
3542
  previousActive = null;
@@ -3573,7 +3679,7 @@ class StrctModal {
3573
3679
  (items[0] ?? this.dialog())?.focus();
3574
3680
  }
3575
3681
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3576
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctModal, isStandalone: true, selector: "strct-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, chromeless: { classPropertyName: "chromeless", publicName: "chromeless", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, backdropClass: { classPropertyName: "backdropClass", publicName: "backdropClass", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: `
3682
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctModal, isStandalone: true, selector: "strct-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, chromeless: { classPropertyName: "chromeless", publicName: "chromeless", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, backdropClass: { classPropertyName: "backdropClass", publicName: "backdropClass", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, queries: [{ propertyName: "lazyContent", first: true, predicate: StrctModalContent, descendants: true, isSignal: true }], ngImport: i0, template: `
3577
3683
  @if (open()) {
3578
3684
  <!-- Backdrop: pointer-only dismiss target. No role/tabindex — keyboard users
3579
3685
  dismiss via Escape, and the focus trap makes the backdrop unreachable
@@ -3619,18 +3725,23 @@ class StrctModal {
3619
3725
  </button>
3620
3726
  </div>
3621
3727
  }
3622
- <div class="strct-modal__body"><ng-content /></div>
3728
+ <div class="strct-modal__body">
3729
+ <ng-content />
3730
+ @if (lazyContent(); as lazy) {
3731
+ <ng-container [ngTemplateOutlet]="lazy.template" />
3732
+ }
3733
+ </div>
3623
3734
  @if (!hideFooter() && !chromeless()) {
3624
3735
  <div class="strct-modal__foot"><ng-content select="[strctModalFooter]" /></div>
3625
3736
  }
3626
3737
  </div>
3627
3738
  </div>
3628
3739
  }
3629
- `, isInline: true, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3740
+ `, isInline: true, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3630
3741
  }
3631
3742
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctModal, decorators: [{
3632
3743
  type: Component,
3633
- args: [{ selector: 'strct-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StrctIcon], template: `
3744
+ args: [{ selector: 'strct-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StrctIcon, NgTemplateOutlet], template: `
3634
3745
  @if (open()) {
3635
3746
  <!-- Backdrop: pointer-only dismiss target. No role/tabindex — keyboard users
3636
3747
  dismiss via Escape, and the focus trap makes the backdrop unreachable
@@ -3676,7 +3787,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
3676
3787
  </button>
3677
3788
  </div>
3678
3789
  }
3679
- <div class="strct-modal__body"><ng-content /></div>
3790
+ <div class="strct-modal__body">
3791
+ <ng-content />
3792
+ @if (lazyContent(); as lazy) {
3793
+ <ng-container [ngTemplateOutlet]="lazy.template" />
3794
+ }
3795
+ </div>
3680
3796
  @if (!hideFooter() && !chromeless()) {
3681
3797
  <div class="strct-modal__foot"><ng-content select="[strctModalFooter]" /></div>
3682
3798
  }
@@ -3686,7 +3802,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
3686
3802
  `, host: {
3687
3803
  '(document:keydown.escape)': 'onEscape()',
3688
3804
  }, styles: [".strct-modal__overlay{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--backdrop);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-modal-fade .12s ease}@media(max-width:768px){.strct-modal__overlay{padding:var(--space-3)}}.strct-modal__dialog{width:100%;max-height:calc(100vh - 48px);display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-elevated);overflow:hidden;animation:strct-modal-rise .14s ease}.strct-modal__dialog--sm{max-width:min(480px,calc(100vw - 32px))}.strct-modal__dialog--md{max-width:min(640px,calc(100vw - 32px))}.strct-modal__dialog--lg{max-width:min(860px,calc(100vw - 32px))}.strct-modal__dialog--xl{max-width:min(1080px,calc(100vw - 32px))}.strct-modal__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--b1)}.strct-modal__head--drag{cursor:move;-webkit-user-select:none;user-select:none;touch-action:none}.strct-modal__overlay--glass{background:#00000061;-webkit-backdrop-filter:blur(10px) saturate(1.25);backdrop-filter:blur(10px) saturate(1.25)}.strct-modal__dialog--glass{background:color-mix(in srgb,var(--bg-1) 72%,transparent);-webkit-backdrop-filter:blur(24px) saturate(1.4);backdrop-filter:blur(24px) saturate(1.4)}.strct-modal__dialog--glass .strct-modal__foot{background:color-mix(in srgb,var(--bg-2) 55%,transparent)}.strct-modal__title{font-size:14px;font-weight:600;color:var(--t1)}.strct-modal__close{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-modal__close:hover{color:var(--t1);background:var(--bg-3)}.strct-modal__body{padding:var(--space-4);overflow-y:auto;color:var(--t2);font-size:13px}.strct-modal__dialog--chromeless{width:calc(232px + var(--strct-wiz-content-min, 864px) + 2px);max-width:calc(100vw - 32px)}.strct-modal__dialog--chromeless:has(.strct-wiz__layout--aside){width:calc(232px + var(--strct-wiz-content-min, 864px) + 280px + 2px)}.strct-modal__dialog--chromeless .strct-modal__body{padding:0;overflow:hidden;display:flex;flex-direction:column}.strct-modal__dialog--chromeless .strct-modal__body>*{flex:1;min-height:0}.strct-modal__foot{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-4);border-top:1px solid var(--b1);background:var(--bg-2)}@keyframes strct-modal-fade{0%{opacity:0}}@keyframes strct-modal-rise{0%{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.strct-modal__overlay,.strct-modal__dialog{animation:none}}\n"] }]
3689
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
3805
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], chromeless: [{ type: i0.Input, args: [{ isSignal: true, alias: "chromeless", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], draggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "draggable", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], backdropClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdropClass", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctModalContent), { isSignal: true }] }] } });
3690
3806
 
3691
3807
  /** Marks the drawer's footer action area: `<ng-container strctDrawerFooter>…`. */
3692
3808
  class StrctDrawerFooter {
@@ -3967,7 +4083,7 @@ class StrctDropdown {
3967
4083
  onMenuKeydown(event) {
3968
4084
  if (this.popover())
3969
4085
  return;
3970
- const items = this.enabledItems();
4086
+ const items = this.navItems();
3971
4087
  if (!items.length)
3972
4088
  return;
3973
4089
  const idx = items.indexOf(event.target);
@@ -3987,7 +4103,9 @@ class StrctDropdown {
3987
4103
  }
3988
4104
  else if (key === 'Enter' || key === ' ') {
3989
4105
  event.preventDefault();
3990
- event.target.click();
4106
+ const target = event.target;
4107
+ if (target.getAttribute('aria-disabled') !== 'true')
4108
+ target.click();
3991
4109
  }
3992
4110
  else if (key === 'Tab') {
3993
4111
  this.close();
@@ -3998,6 +4116,20 @@ class StrctDropdown {
3998
4116
  ...this.host.nativeElement.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"]), strct-submenu .strct-submenu__trigger'),
3999
4117
  ];
4000
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
+ }
4001
4133
  /** Focus the selected item if there is one, else the first — after render. */
4002
4134
  focusInitialItem() {
4003
4135
  setTimeout(() => {
@@ -4112,6 +4244,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
4112
4244
  },
4113
4245
  }]
4114
4246
  }] });
4247
+ let dropdownItemCounter = 0;
4115
4248
  /** A selectable row inside a `<strct-dropdown>`. */
4116
4249
  class StrctDropdownItem {
4117
4250
  /**
@@ -4126,15 +4259,42 @@ class StrctDropdownItem {
4126
4259
  critical = input(false, { ...(ngDevMode ? { debugName: "critical" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4127
4260
  /** Static disable flag. */
4128
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
+ }
4129
4284
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
4130
- 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) {
4131
4286
  <span class="strct-dd__check" aria-hidden="true">
4132
4287
  @if (selected()) {
4133
4288
  <strct-icon strictName="check" [size]="12" [strokeWidth]="1.8" />
4134
4289
  }
4135
4290
  </span>
4136
4291
  }
4137
- <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 });
4138
4298
  }
4139
4299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, decorators: [{
4140
4300
  type: Component,
@@ -4145,16 +4305,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
4145
4305
  }
4146
4306
  </span>
4147
4307
  }
4148
- <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: {
4149
4314
  class: 'strct-dd__item',
4150
4315
  '[attr.role]': "selected() === null ? 'menuitem' : 'menuitemradio'",
4151
4316
  '[attr.aria-checked]': 'selected()',
4152
- '[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()',
4153
4322
  '[class.strct-dd__item--critical]': 'critical()',
4154
4323
  '[class.strct-dd__item--selected]': 'selected() === true',
4155
4324
  '[attr.aria-disabled]': 'disabled() || null',
4156
- }, 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"] }]
4157
- }], 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 }] }] } });
4158
4327
  /** Thin separator between groups of menu items. */
4159
4328
  class StrctDropdownDivider {
4160
4329
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownDivider, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -4209,7 +4378,9 @@ class StrctContextMenu {
4209
4378
  // guessed size) and focus can land on the first item.
4210
4379
  setTimeout(() => {
4211
4380
  this.clampToViewport();
4212
- 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));
4213
4384
  });
4214
4385
  }
4215
4386
  close() {
@@ -4220,12 +4391,14 @@ class StrctContextMenu {
4220
4391
  restoreFocus(this.restoreTo);
4221
4392
  this.restoreTo = null;
4222
4393
  }
4223
- /** 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). */
4224
4396
  navItems() {
4225
4397
  const el = this.menuEl()?.nativeElement;
4226
4398
  if (!el)
4227
4399
  return [];
4228
- 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'));
4229
4402
  }
4230
4403
  /**
4231
4404
  * Focus-based roving: items keep the `tabindex="-1"` their host binding
@@ -4438,8 +4611,10 @@ class StrctSubmenu {
4438
4611
  const panel = this.panel()?.nativeElement;
4439
4612
  // Dropdown-item rows carry tabindex=-1, outside FOCUSABLE_SELECTOR —
4440
4613
  // fall back to the first rovable row when nothing else is tabbable.
4441
- if (panel && !focusFirstIn(panel))
4442
- 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
+ }
4443
4618
  });
4444
4619
  }
4445
4620
  /**
@@ -4475,11 +4650,13 @@ class StrctSubmenu {
4475
4650
  items[key === 'Home' ? 0 : items.length - 1].focus();
4476
4651
  }
4477
4652
  }
4478
- /** 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). */
4479
4655
  panelItems() {
4480
4656
  const panel = this.panel()?.nativeElement;
4481
4657
  return panel
4482
- ? [...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'))
4483
4660
  : [];
4484
4661
  }
4485
4662
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSubmenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -4668,7 +4845,10 @@ class StrctWizard {
4668
4845
  * ~720px of component width — it never flips horizontal.
4669
4846
  *
4670
4847
  * The default can be flipped app-wide via `provideStrctWizardDefaults`;
4671
- * this input always wins when bound.
4848
+ * this input always wins when bound. **Tests:** a spec that renders a
4849
+ * component using `strct-wizard` must provide the same defaults the app
4850
+ * does, or it renders the horizontal layout — a different DOM, no rail,
4851
+ * no title.
4672
4852
  */
4673
4853
  vertical = input(this.defaults?.vertical ?? false, { ...(ngDevMode ? { debugName: "vertical" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4674
4854
  /**
@@ -4678,7 +4858,8 @@ class StrctWizard {
4678
4858
  * Size the host with width/height only; do not override its `display`.
4679
4859
  */
4680
4860
  flush = input(false, { ...(ngDevMode ? { debugName: "flush" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4681
- /** Rail heading shown above the progress bar (vertical mode). */
4861
+ /** Rail heading shown above the progress bar. Vertical mode only — the
4862
+ * horizontal layout has no title band (a dev-mode warning says so). */
4682
4863
  title = input('', /* @ts-ignore */
4683
4864
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4684
4865
  /**
@@ -4730,6 +4911,8 @@ class StrctWizard {
4730
4911
  canAdvance = computed(() => this.steps()[this.current()]?.canAdvance() ?? true, /* @ts-ignore */
4731
4912
  ...(ngDevMode ? [{ debugName: "canAdvance" }] : /* istanbul ignore next */ []));
4732
4913
  constructor() {
4914
+ if (typeof ngDevMode !== 'undefined' && ngDevMode)
4915
+ this.diagnose();
4733
4916
  effect(() => {
4734
4917
  const idx = this.current();
4735
4918
  this.steps().forEach((step, i) => step.setActive(i === idx));
@@ -4739,6 +4922,37 @@ class StrctWizard {
4739
4922
  });
4740
4923
  });
4741
4924
  }
4925
+ /** Dev-mode warnings for inputs the current layout silently ignores. */
4926
+ diagnose() {
4927
+ if (!(typeof ngDevMode !== 'undefined' && ngDevMode))
4928
+ return;
4929
+ effect(() => {
4930
+ const title = this.title();
4931
+ if (title && !this.vertical()) {
4932
+ strctDevWarn('wizard:horizontal-title', `[strct-wizard] title="${title}" is not rendered: only the vertical layout ` +
4933
+ `has a title band. Set \`vertical\` — or, if the app provides ` +
4934
+ `provideStrctWizardDefaults({ vertical: true }), provide it here too ` +
4935
+ `(a test that omits it renders the horizontal wizard).`);
4936
+ }
4937
+ });
4938
+ // A chromeless dialog sizes itself from --strct-wiz-content-min read on the
4939
+ // DIALOG; set on the wizard, it widens the grid but not the dialog.
4940
+ const host = inject((ElementRef)).nativeElement;
4941
+ afterNextRender(() => {
4942
+ const dialog = host.closest('.strct-modal__dialog--chromeless');
4943
+ if (!dialog)
4944
+ return;
4945
+ const prop = '--strct-wiz-content-min';
4946
+ const own = getComputedStyle(host).getPropertyValue(prop).trim();
4947
+ const outer = getComputedStyle(dialog).getPropertyValue(prop).trim();
4948
+ if (own !== outer) {
4949
+ strctDevWarn('wizard:content-min-scope', `[strct-wizard] ${prop} is ${own || '(unset)'} on the wizard but ` +
4950
+ `${outer || '(unset — default 864px)'} on its chromeless dialog. The dialog ` +
4951
+ `reads the variable on itself, so its width ignores the wizard's value. Set ` +
4952
+ `it on the strct-modal or an ancestor instead.`);
4953
+ }
4954
+ });
4955
+ }
4742
4956
  next() {
4743
4957
  if (!this.isLast() && this.canAdvance()) {
4744
4958
  this.current.update((i) => i + 1);
@@ -10492,7 +10706,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
10492
10706
  * Per-column cell template for `strct-table` / `strct-datagrid`. The column key
10493
10707
  * is the directive value; the row, value and column are the template context:
10494
10708
  *
10495
- * <ng-template strctCell="status" let-row let-value="value">
10709
+ * <ng-template strctCell="status" let-row let-value="value"> (let-row="row" also works)
10496
10710
  * <strct-badge [status]="row['success'] ? 'success' : 'critical'">{{ value }}</strct-badge>
10497
10711
  * </ng-template>
10498
10712
  */
@@ -10576,6 +10790,7 @@ class StrctTable {
10576
10790
  [ngTemplateOutlet]="tpl"
10577
10791
  [ngTemplateOutletContext]="{
10578
10792
  $implicit: row,
10793
+ row,
10579
10794
  value: row[col.key],
10580
10795
  column: col,
10581
10796
  }"
@@ -10632,6 +10847,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
10632
10847
  [ngTemplateOutlet]="tpl"
10633
10848
  [ngTemplateOutletContext]="{
10634
10849
  $implicit: row,
10850
+ row,
10635
10851
  value: row[col.key],
10636
10852
  column: col,
10637
10853
  }"
@@ -10809,7 +11025,7 @@ const UTIL_W = { detail: 36, expand: 36, sel: 40 };
10809
11025
  const STICKY_FALLBACK_W = 120;
10810
11026
  /**
10811
11027
  * Marks the expandable-row detail template. The row is the template's implicit
10812
- * context: `<ng-template strctRowDetail let-row> … {{ row['name'] }} … </ng-template>`.
11028
+ * context (`let-row`, or `let-row="row"`): `<ng-template strctRowDetail let-row> … {{ row['name'] }} … </ng-template>`.
10813
11029
  */
10814
11030
  class StrctRowDetailDef {
10815
11031
  template = inject(TemplateRef);
@@ -11603,6 +11819,55 @@ class StrctDatagrid {
11603
11819
  somePageSelected = computed(() => !this.allPageSelected() &&
11604
11820
  this.selectionRows().some((r) => this.selected().has(this.idOf(r))), /* @ts-ignore */
11605
11821
  ...(ngDevMode ? [{ debugName: "somePageSelected" }] : /* istanbul ignore next */ []));
11822
+ /** Dev-mode check that row identities can do their job (see util/dev-warn). */
11823
+ diagnoseIdentity(rowId, rows, init, lazy) {
11824
+ if (!(typeof ngDevMode !== 'undefined' && ngDevMode))
11825
+ return;
11826
+ if (rows.length === 0)
11827
+ return;
11828
+ const label = rowId == null ? '' : typeof rowId === 'function' ? 'rowId (function)' : `rowId="${rowId}"`;
11829
+ if (rowId != null) {
11830
+ const seen = new Set();
11831
+ const dupes = new Set();
11832
+ let unresolved = 0;
11833
+ for (const r of rows) {
11834
+ const raw = typeof rowId === 'function' ? rowId(r) : r[rowId];
11835
+ if (raw == null)
11836
+ unresolved++;
11837
+ else if (seen.has(raw))
11838
+ dupes.add(raw);
11839
+ else
11840
+ seen.add(raw);
11841
+ }
11842
+ if (dupes.size > 0) {
11843
+ const sample = [...dupes]
11844
+ .slice(0, 3)
11845
+ .map((v) => JSON.stringify(v))
11846
+ .join(', ');
11847
+ strctDevWarn(`datagrid:dupe:${label}`, `[strct-datagrid] ${label} resolves to the same value for more than one row ` +
11848
+ `(${sample}). Selection and expansion key on this value, so those rows ` +
11849
+ `will behave as one. Give each row a distinct value, or drop rowId to key ` +
11850
+ `on the row object.`);
11851
+ }
11852
+ if (unresolved > 0) {
11853
+ strctDevWarn(`datagrid:unresolved:${label}`, `[strct-datagrid] ${label} does not resolve for ${unresolved} of ${rows.length} ` +
11854
+ `rows. Those rows fall back to object identity, so their selection and ` +
11855
+ `expansion will not survive a data refresh. Check the field name, or give ` +
11856
+ `every row a value.`);
11857
+ }
11858
+ }
11859
+ // Lazy mode seeds ids for rows on other pages, so a miss there is normal.
11860
+ if (!lazy && init && init.length > 0) {
11861
+ const ids = new Set(rows.map((r) => this.idOf(r)));
11862
+ if (!init.some((v) => ids.has(v))) {
11863
+ strctDevWarn(`datagrid:init:${label}`, `[strct-datagrid] initialSelection has ${init.length} value(s), none of which ` +
11864
+ (rowId == null
11865
+ ? `is one of the rows — with no rowId, the values must be the row objects themselves. `
11866
+ : `matches any row's ${label}. `) +
11867
+ `The pre-selection will be empty.`);
11868
+ }
11869
+ }
11870
+ }
11606
11871
  /** Resolve a row's stable identity: its `rowId` value, or the row object
11607
11872
  * itself when there is no `rowId` or it resolves to null/undefined. */
11608
11873
  idOf(row) {
@@ -11654,6 +11919,17 @@ class StrctDatagrid {
11654
11919
  return;
11655
11920
  untracked(() => this.selected.set(new Set(init)));
11656
11921
  });
11922
+ // Dev mode only: say so when rowId or initialSelection cannot do what they
11923
+ // were given to do. In production the effect is never registered.
11924
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
11925
+ effect(() => {
11926
+ const rowId = this.rowId();
11927
+ const rows = this.rows();
11928
+ const init = this.initialSelection();
11929
+ const lazy = this.lazy();
11930
+ untracked(() => this.diagnoseIdentity(rowId, rows, init, lazy));
11931
+ });
11932
+ }
11657
11933
  // Server-side mode: announce what to load whenever page / sort / pageSize
11658
11934
  // change (and once on init, so the consumer fetches the first page).
11659
11935
  effect(() => {
@@ -12340,6 +12616,7 @@ class StrctDatagrid {
12340
12616
  [ngTemplateOutlet]="tpl"
12341
12617
  [ngTemplateOutletContext]="{
12342
12618
  $implicit: row,
12619
+ row,
12343
12620
  value: row[col.key],
12344
12621
  column: col,
12345
12622
  }"
@@ -12368,7 +12645,7 @@ class StrctDatagrid {
12368
12645
  <div class="strct-dg__detail">
12369
12646
  <ng-container
12370
12647
  [ngTemplateOutlet]="detailDef()!.template"
12371
- [ngTemplateOutletContext]="{ $implicit: row }"
12648
+ [ngTemplateOutletContext]="{ $implicit: row, row }"
12372
12649
  />
12373
12650
  </div>
12374
12651
  </td>
@@ -12406,7 +12683,7 @@ class StrctDatagrid {
12406
12683
  <div class="strct-dg__pane-body">
12407
12684
  <ng-container
12408
12685
  [ngTemplateOutlet]="detailDef()!.template"
12409
- [ngTemplateOutletContext]="{ $implicit: activeRow() }"
12686
+ [ngTemplateOutletContext]="{ $implicit: activeRow(), row: activeRow() }"
12410
12687
  />
12411
12688
  </div>
12412
12689
  </aside>
@@ -12829,6 +13106,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
12829
13106
  [ngTemplateOutlet]="tpl"
12830
13107
  [ngTemplateOutletContext]="{
12831
13108
  $implicit: row,
13109
+ row,
12832
13110
  value: row[col.key],
12833
13111
  column: col,
12834
13112
  }"
@@ -12857,7 +13135,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
12857
13135
  <div class="strct-dg__detail">
12858
13136
  <ng-container
12859
13137
  [ngTemplateOutlet]="detailDef()!.template"
12860
- [ngTemplateOutletContext]="{ $implicit: row }"
13138
+ [ngTemplateOutletContext]="{ $implicit: row, row }"
12861
13139
  />
12862
13140
  </div>
12863
13141
  </td>
@@ -12895,7 +13173,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
12895
13173
  <div class="strct-dg__pane-body">
12896
13174
  <ng-container
12897
13175
  [ngTemplateOutlet]="detailDef()!.template"
12898
- [ngTemplateOutletContext]="{ $implicit: activeRow() }"
13176
+ [ngTemplateOutletContext]="{ $implicit: activeRow(), row: activeRow() }"
12899
13177
  />
12900
13178
  </div>
12901
13179
  </aside>
@@ -18117,7 +18395,7 @@ class StrctSplitButton {
18117
18395
  /** Main action label. */
18118
18396
  label = input.required(/* @ts-ignore */
18119
18397
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
18120
- /** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?). */
18398
+ /** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?, hint?). */
18121
18399
  items = input([], /* @ts-ignore */
18122
18400
  ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
18123
18401
  /** Optional leading icon of the main segment. */
@@ -18165,6 +18443,7 @@ class StrctSplitButton {
18165
18443
  <strct-dropdown-item
18166
18444
  [critical]="item.critical ?? false"
18167
18445
  [disabled]="item.disabled ?? false"
18446
+ [hint]="item.hint"
18168
18447
  (click)="!item.disabled && picked.emit(item)"
18169
18448
  >
18170
18449
  @if (item.icon) {
@@ -18176,7 +18455,7 @@ class StrctSplitButton {
18176
18455
  }
18177
18456
  </strct-dropdown>
18178
18457
  </div>
18179
- `, 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 });
18180
18459
  }
18181
18460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSplitButton, decorators: [{
18182
18461
  type: Component,
@@ -18216,6 +18495,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18216
18495
  <strct-dropdown-item
18217
18496
  [critical]="item.critical ?? false"
18218
18497
  [disabled]="item.disabled ?? false"
18498
+ [hint]="item.hint"
18219
18499
  (click)="!item.disabled && picked.emit(item)"
18220
18500
  >
18221
18501
  @if (item.icon) {
@@ -18570,6 +18850,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18570
18850
  args: ['window:scroll']
18571
18851
  }] } });
18572
18852
 
18853
+ let menubarCounter = 0;
18573
18854
  /**
18574
18855
  * Horizontal menubar — the application-menu strip ("File · Edit · View") for
18575
18856
  * dense tool-style consoles:
@@ -18696,7 +18977,7 @@ class StrctMenubar {
18696
18977
  event.preventDefault();
18697
18978
  event.stopPropagation();
18698
18979
  const idx = Number(active?.getAttribute('data-idx'));
18699
- if (menu.items[idx]?.children?.length) {
18980
+ if (menu.items[idx]?.children?.length && !menu.items[idx].disabled) {
18700
18981
  this.subIdx.set(idx);
18701
18982
  setTimeout(() => this.navButtons(true)[0]?.focus());
18702
18983
  }
@@ -18747,18 +19028,24 @@ class StrctMenubar {
18747
19028
  restoreFocus(this.trigger);
18748
19029
  this.trigger = null;
18749
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
+ }
18750
19037
  topButtons() {
18751
19038
  return Array.from(this.host.nativeElement.querySelectorAll('.strct-mb__top'));
18752
19039
  }
18753
- /** 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. */
18754
19043
  navButtons(sub) {
18755
19044
  const menuEl = this.host.nativeElement.querySelector('.strct-mb__menu');
18756
19045
  if (!menuEl)
18757
19046
  return [];
18758
- const sel = sub
18759
- ? '.strct-mb__subitem:not([disabled])'
18760
- : '.strct-mb__item:not([disabled]):not(.strct-mb__subitem)';
18761
- 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'));
18762
19049
  }
18763
19050
  onDocClick(event) {
18764
19051
  if (this.openId() && !this.host.nativeElement.contains(event.target)) {
@@ -18810,7 +19097,9 @@ class StrctMenubar {
18810
19097
  [class.strct-mb__item--critical]="item.critical"
18811
19098
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
18812
19099
  [attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
18813
- [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"
18814
19103
  (click)="onItemClick(menu, item, i)"
18815
19104
  (mouseenter)="onItemHover(item, i)"
18816
19105
  >
@@ -18822,9 +19111,12 @@ class StrctMenubar {
18822
19111
  <strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
18823
19112
  }
18824
19113
  </button>
19114
+ @if (item.hint) {
19115
+ <span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
19116
+ }
18825
19117
  @if (subIdx() === i && item.children?.length) {
18826
19118
  <div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
18827
- @for (sub of item.children; track $index) {
19119
+ @for (sub of item.children; track $index; let j = $index) {
18828
19120
  @if (sub.divider) {
18829
19121
  <div class="strct-mb__divider" role="separator"></div>
18830
19122
  } @else {
@@ -18833,7 +19125,9 @@ class StrctMenubar {
18833
19125
  class="strct-mb__item strct-mb__subitem"
18834
19126
  role="menuitem"
18835
19127
  [class.strct-mb__item--critical]="sub.critical"
18836
- [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"
18837
19131
  (click)="pick(menu, sub)"
18838
19132
  >
18839
19133
  @if (sub.icon) {
@@ -18841,6 +19135,9 @@ class StrctMenubar {
18841
19135
  }
18842
19136
  {{ sub.label }}
18843
19137
  </button>
19138
+ @if (sub.hint) {
19139
+ <span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
19140
+ }
18844
19141
  }
18845
19142
  }
18846
19143
  </div>
@@ -18853,7 +19150,7 @@ class StrctMenubar {
18853
19150
  </div>
18854
19151
  }
18855
19152
  </div>
18856
- `, 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 });
18857
19154
  }
18858
19155
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenubar, decorators: [{
18859
19156
  type: Component,
@@ -18895,7 +19192,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18895
19192
  [class.strct-mb__item--critical]="item.critical"
18896
19193
  [attr.aria-haspopup]="item.children?.length ? 'menu' : null"
18897
19194
  [attr.aria-expanded]="item.children?.length ? subIdx() === i : null"
18898
- [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"
18899
19198
  (click)="onItemClick(menu, item, i)"
18900
19199
  (mouseenter)="onItemHover(item, i)"
18901
19200
  >
@@ -18907,9 +19206,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18907
19206
  <strct-icon class="strct-mb__caret" name="chevronRight" [size]="12" />
18908
19207
  }
18909
19208
  </button>
19209
+ @if (item.hint) {
19210
+ <span [id]="hintId(menu.id, i)" hidden>{{ item.hint }}</span>
19211
+ }
18910
19212
  @if (subIdx() === i && item.children?.length) {
18911
19213
  <div class="strct-mb__submenu" role="menu" [attr.aria-label]="item.label">
18912
- @for (sub of item.children; track $index) {
19214
+ @for (sub of item.children; track $index; let j = $index) {
18913
19215
  @if (sub.divider) {
18914
19216
  <div class="strct-mb__divider" role="separator"></div>
18915
19217
  } @else {
@@ -18918,7 +19220,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18918
19220
  class="strct-mb__item strct-mb__subitem"
18919
19221
  role="menuitem"
18920
19222
  [class.strct-mb__item--critical]="sub.critical"
18921
- [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"
18922
19226
  (click)="pick(menu, sub)"
18923
19227
  >
18924
19228
  @if (sub.icon) {
@@ -18926,6 +19230,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18926
19230
  }
18927
19231
  {{ sub.label }}
18928
19232
  </button>
19233
+ @if (sub.hint) {
19234
+ <span [id]="hintId(menu.id, i, j)" hidden>{{ sub.hint }}</span>
19235
+ }
18929
19236
  }
18930
19237
  }
18931
19238
  </div>
@@ -18938,7 +19245,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18938
19245
  </div>
18939
19246
  }
18940
19247
  </div>
18941
- `, 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"] }]
18942
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: [{
18943
19250
  type: HostListener,
18944
19251
  args: ['document:click', ['$event']]
@@ -21573,5 +21880,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
21573
21880
  * Generated bundle index. Do not edit.
21574
21881
  */
21575
21882
 
21576
- export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLogViewer, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderItem, StrctRowDetailDef, StrctSearchbox, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWizard, StrctWizardAside, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctValidationIcon, strctValidationTone, unlockBodyScroll };
21883
+ export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLogViewer, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderItem, StrctRowDetailDef, StrctSearchbox, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWizard, StrctWizardAside, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctValidationIcon, strctValidationTone, unlockBodyScroll };
21577
21884
  //# sourceMappingURL=akcelik-strct.mjs.map