@truenas/ui-components 0.1.67 → 0.1.68

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.
@@ -3252,52 +3252,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3252
3252
  }]
3253
3253
  }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuTriggerFor", required: true }] }], tnMenuPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tnMenuPosition", required: false }] }] } });
3254
3254
 
3255
- /**
3256
- * Renders a single `TnMenuItem` (separator, leaf button, or submenu trigger).
3257
- *
3258
- * Used recursively by `<tn-menu>` so the same authoring covers root items and
3259
- * arbitrarily-nested submenus — adding a new menu-item property (badge,
3260
- * tooltip, etc.) only needs to be wired up here, not duplicated across every
3261
- * nesting level.
3262
- *
3263
- * **Why a component, not an ng-template:** earlier prototypes used a recursive
3264
- * `<ng-template #menuItem>` outletted via `[ngTemplateOutlet]`. That fails
3265
- * because the embedded view's DI chain resolves through the template's
3266
- * declaration site, not the outlet anchor — `CdkMenuItem` then can't find
3267
- * `MENU_STACK`, which is provided per-`CdkMenu`. A real component's DI walks
3268
- * up through its host element instead, so each renderer instance sees the
3269
- * `CdkMenu` it's actually rendered under (root or any nested submenu).
3270
- *
3271
- * @internal Implementation detail of `<tn-menu>`. Not re-exported from the
3272
- * menu barrel or `public-api.ts` because it depends on injecting
3273
- * `TnMenuComponent` from the host DI scope and cannot be used standalone.
3274
- */
3275
- class TnMenuItemRendererComponent {
3276
- item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
3277
- // The renderer is rendered inside a CDK overlay portal, but the portal is
3278
- // created from a template defined inside `<tn-menu>` — so the view-DI chain
3279
- // still walks back to the host `<tn-menu>` component, which means inject()
3280
- // here resolves it correctly.
3281
- menu = inject(TnMenuComponent);
3282
- onClick() {
3283
- this.menu.onMenuItemClick(this.item());
3284
- }
3285
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3286
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuItemRendererComponent, isStandalone: true, selector: "tn-menu-item-renderer", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (item().separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n} @else if (!item().children || item().children!.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n (click)=\"onClick()\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n </button>\n} @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (child of item().children!; track child.id) {\n <tn-menu-item-renderer [item]=\"child\" />\n }\n </div>\n </ng-template>\n}\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => TnMenuItemRendererComponent), selector: "tn-menu-item-renderer", inputs: ["item"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => CdkMenu), selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuItem), selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuTrigger), selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: i0.forwardRef(() => TnIconComponent), selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: i0.forwardRef(() => TnTestIdDirective), selector: "[tnTestId]", inputs: ["tnTestId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3287
- }
3288
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuItemRendererComponent, decorators: [{
3289
- type: Component,
3290
- args: [{ selector: 'tn-menu-item-renderer', standalone: true, imports: [
3291
- CommonModule,
3292
- CdkMenu,
3293
- CdkMenuItem,
3294
- CdkMenuTrigger,
3295
- TnIconComponent,
3296
- TnTestIdDirective,
3297
- forwardRef(() => TnMenuItemRendererComponent),
3298
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (item().separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n} @else if (!item().children || item().children!.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n (click)=\"onClick()\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n </button>\n} @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item().testId ?? 'menu-item-' + item().id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item().disabled\"\n [cdkMenuItemDisabled]=\"!!item().disabled\"\n [class.disabled]=\"item().disabled\"\n [class.tn-menu-item--selected]=\"item().selected\"\n [attr.aria-current]=\"item().selected ? 'true' : null\"\n >\n @if (item().icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item().icon!\" [library]=\"item().iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item().label }}</span>\n @if (item().shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item().shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <div class=\"tn-menu tn-menu--nested\" cdkMenu>\n @for (child of item().children!; track child.id) {\n <tn-menu-item-renderer [item]=\"child\" />\n }\n </div>\n </ng-template>\n}\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
3299
- }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
3300
-
3301
3255
  /**
3302
3256
  * Projection-based menu item for use inside `<tn-menu>`.
3303
3257
  *
@@ -3365,27 +3319,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3365
3319
  }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconLibrary", required: false }] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
3366
3320
 
3367
3321
  /**
3368
- * Activates CDK menu hover-to-open behavior for menus opened via custom overlays.
3322
+ * Activates CDK menu hover-to-open behavior and keyboard focus entry — for a
3323
+ * root menu opened via a custom overlay instead of CDK's own `CdkMenuTrigger`.
3369
3324
  *
3370
3325
  * CDK's hover-to-open for submenu triggers is guarded by `!menuStack.isEmpty()`.
3371
- * When a CdkMenu is opened via TnMenuTriggerDirective (custom overlay) instead of
3372
- * CDK's own CdkMenuTrigger, the menu is considered "inline" and doesn't register
3373
- * with the stack, disabling hover for its submenu triggers.
3374
- *
3375
- * This directive pushes the CdkMenu to its own stack and focuses the element
3376
- * (to trigger the CdkMenu's focusin host listener, preventing the hasFocus
3377
- * auto-close subscription from immediately clearing the stack).
3326
+ * When a `CdkMenu` is opened via `TnMenuTriggerDirective` (custom overlay)
3327
+ * rather than `CdkMenuTrigger`, the menu is considered "inline" and doesn't
3328
+ * register with the stack, disabling hover for its submenu triggers and leaving
3329
+ * keyboard focus outside the panel.
3330
+ *
3331
+ * This directive pushes that root menu onto its own stack and moves focus to
3332
+ * the first (or selected) item so arrow-key navigation works immediately.
3333
+ * Submenus — opened by `CdkMenuTrigger` — already push themselves and are
3334
+ * focused by CDK, so for them this directive only installs the skip-disabled
3335
+ * predicate and otherwise stays out of the way.
3378
3336
  */
3379
3337
  class TnMenuActivateHoverDirective {
3380
3338
  cdkMenu = inject(CdkMenu);
3381
3339
  elementRef = inject((ElementRef));
3382
3340
  ngAfterContentInit() {
3341
+ // A root inline menu (opened via our custom overlay, not CdkMenuTrigger)
3342
+ // hasn't been pushed onto the stack yet. Submenus opened by CdkMenuTrigger
3343
+ // push themselves in CdkMenuBase.ngAfterContentInit, so the stack is already
3344
+ // non-empty by the time this runs for them — which is how we tell the two
3345
+ // apart without reaching for more CDK internals.
3383
3346
  const stack = this.cdkMenu.menuStack;
3384
- if (stack.isEmpty()) {
3347
+ const isRootInline = stack.isEmpty();
3348
+ if (isRootInline) {
3385
3349
  stack.push(this.cdkMenu);
3386
3350
  }
3387
3351
  const km = this.cdkMenu.keyManager;
3388
3352
  km?.skipPredicate?.((item) => item.disabled);
3353
+ // Only the root inline menu needs manual focus entry. CdkMenuTrigger already
3354
+ // focuses the first item of any submenu it opens, so we must not fight it.
3355
+ if (!isRootInline) {
3356
+ return;
3357
+ }
3389
3358
  // Prefer the marked "selected" item when one exists — matches user
3390
3359
  // expectation for option pickers (export format, sort key, etc.) where
3391
3360
  // reopening the menu should land focus on the current choice, not always
@@ -3415,6 +3384,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
3415
3384
  standalone: true,
3416
3385
  }]
3417
3386
  }] });
3387
+
3388
+ /**
3389
+ * Renders one complete menu panel: the `<div cdkMenu>` container plus every
3390
+ * item in it (separators, leaf buttons, and submenu triggers). Used for the
3391
+ * root menu and recursively for each nested submenu.
3392
+ *
3393
+ * **Why this owns the `<div cdkMenu>` (and the items aren't rendered by
3394
+ * `<tn-menu>` directly):** `CdkMenu` discovers its items via
3395
+ * `@ContentChildren(CdkMenuItem, {descendants: true})`, which only sees nodes
3396
+ * within the *same view* as the `cdkMenu` element — it does **not** cross into
3397
+ * a child component's view. An earlier design kept the `<div cdkMenu>` in
3398
+ * `<tn-menu>`'s template and rendered each item through a separate
3399
+ * `<tn-menu-item-renderer>` component; those item buttons lived in the
3400
+ * renderer's view, so `CdkMenu`'s query never found them. The result: an empty
3401
+ * `FocusKeyManager`, no focus on open, and dead arrow keys for the entire
3402
+ * `[items]` API. Keeping the container and its buttons together in this one
3403
+ * component's view is what makes keyboard navigation work.
3404
+ *
3405
+ * **Why a component, not a recursive `<ng-template>`:** an embedded view from
3406
+ * `*ngTemplateOutlet` resolves DI through the template's *declaration* site,
3407
+ * so a nested submenu item would inject the root menu's `MENU_STACK` instead of
3408
+ * its own. A real component resolves DI through its host's insertion point, so
3409
+ * each panel — root or any nesting depth — sees the `CdkMenu` it's actually
3410
+ * rendered under.
3411
+ *
3412
+ * **Projected `<tn-menu-item>` entries** (`projectedItems`) are only passed at
3413
+ * the root; submenus are always driven by `item.children`. They render as
3414
+ * sibling `cdkMenuItem` buttons so they share the same keyboard navigation.
3415
+ *
3416
+ * @internal Implementation detail of `<tn-menu>`. Not re-exported from the
3417
+ * menu barrel or `public-api.ts` because it depends on injecting
3418
+ * `TnMenuComponent` from the host DI scope and cannot be used standalone.
3419
+ */
3420
+ class TnMenuPanelComponent {
3421
+ /** Items for this menu level (the root array, or a submenu's `children`). */
3422
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
3423
+ /** Projected `<tn-menu-item>` children — only supplied for the root panel. */
3424
+ projectedItems = input([], ...(ngDevMode ? [{ debugName: "projectedItems" }] : []));
3425
+ /** Applies the nested-submenu styling/offset. */
3426
+ nested = input(false, ...(ngDevMode ? [{ debugName: "nested" }] : []));
3427
+ // The panel is rendered inside a CDK overlay portal, but the portal's
3428
+ // template is declared inside `<tn-menu>` — so the view-DI chain still walks
3429
+ // back to the host `<tn-menu>` component and inject() resolves it.
3430
+ menu = inject(TnMenuComponent);
3431
+ onItemClick(item) {
3432
+ this.menu.onMenuItemClick(item);
3433
+ }
3434
+ onProjectedClick(item, event) {
3435
+ this.menu.onProjectedItemClick(item, event);
3436
+ }
3437
+ trackByItemId(index, item) {
3438
+ return item.id;
3439
+ }
3440
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuPanelComponent, isStandalone: true, selector: "tn-menu-panel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, projectedItems: { classPropertyName: "projectedItems", publicName: "projectedItems", isSignal: true, isRequired: false, transformFunction: null }, nested: { classPropertyName: "nested", publicName: "nested", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"tn-menu\" cdkMenu tnMenuActivateHover [class.tn-menu--nested]=\"nested()\">\n <!--\n Projected <tn-menu-item> entries (root panel only) render as sibling\n cdkMenuItem buttons so they participate in CdkMenu's keyboard navigation\n (arrow keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of projectedItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n } @else if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [cdkMenuItemDisabled]=\"!!item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon!\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item.disabled\"\n [cdkMenuItemDisabled]=\"!!item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon!\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <tn-menu-panel [items]=\"item.children!\" [nested]=\"true\" />\n </ng-template>\n }\n }\n</div>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => TnMenuPanelComponent), selector: "tn-menu-panel", inputs: ["items", "projectedItems", "nested"] }, { kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1$2.NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenu), selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuItem), selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: i0.forwardRef(() => CdkMenuTrigger), selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: i0.forwardRef(() => TnIconComponent), selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: i0.forwardRef(() => TnTestIdDirective), selector: "[tnTestId]", inputs: ["tnTestId"] }, { kind: "directive", type: i0.forwardRef(() => TnMenuActivateHoverDirective), selector: "[tnMenuActivateHover]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3442
+ }
3443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuPanelComponent, decorators: [{
3444
+ type: Component,
3445
+ args: [{ selector: 'tn-menu-panel', standalone: true, imports: [
3446
+ CommonModule,
3447
+ CdkMenu,
3448
+ CdkMenuItem,
3449
+ CdkMenuTrigger,
3450
+ TnIconComponent,
3451
+ TnTestIdDirective,
3452
+ TnMenuActivateHoverDirective,
3453
+ forwardRef(() => TnMenuPanelComponent),
3454
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tn-menu\" cdkMenu tnMenuActivateHover [class.tn-menu--nested]=\"nested()\">\n <!--\n Projected <tn-menu-item> entries (root panel only) render as sibling\n cdkMenuItem buttons so they participate in CdkMenu's keyboard navigation\n (arrow keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of projectedItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n\n @for (item of items(); track trackByItemId($index, item)) {\n @if (item.separator) {\n <div class=\"tn-menu-separator\" role=\"separator\"></div>\n } @else if (!item.children || item.children.length === 0) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [disabled]=\"item.disabled\"\n [cdkMenuItemDisabled]=\"!!item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon!\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n </button>\n } @else {\n <button\n cdkMenuItem\n class=\"tn-menu-item tn-menu-item--nested\"\n type=\"button\"\n [tnTestId]=\"item.testId ?? 'menu-item-' + item.id\"\n [cdkMenuTriggerFor]=\"submenuTpl\"\n [disabled]=\"item.disabled\"\n [cdkMenuItemDisabled]=\"!!item.disabled\"\n [class.disabled]=\"item.disabled\"\n [class.tn-menu-item--selected]=\"item.selected\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n >\n @if (item.icon) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"item.icon!\" [library]=\"item.iconLibrary\" />\n }\n <span class=\"tn-menu-item-label\">{{ item.label }}</span>\n @if (item.shortcut) {\n <span class=\"tn-menu-item-shortcut\">{{ item.shortcut }}</span>\n }\n <span class=\"tn-menu-item-arrow\">\u25B6</span>\n </button>\n\n <ng-template #submenuTpl>\n <tn-menu-panel [items]=\"item.children!\" [nested]=\"true\" />\n </ng-template>\n }\n }\n</div>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
3455
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], projectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "projectedItems", required: false }] }], nested: [{ type: i0.Input, args: [{ isSignal: true, alias: "nested", required: false }] }] } });
3456
+
3418
3457
  class TnMenuComponent {
3419
3458
  items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
3420
3459
  contextMenu = input(false, ...(ngDevMode ? [{ debugName: "contextMenu" }] : [])); // Enable context menu mode (right-click)
@@ -3528,15 +3567,12 @@ class TnMenuComponent {
3528
3567
  this.openContextMenuAt(event.clientX, event.clientY);
3529
3568
  }
3530
3569
  }
3531
- trackByItemId(index, item) {
3532
- return item.id;
3533
- }
3534
3570
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3535
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuComponent, isStandalone: true, selector: "tn-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemClick: "menuItemClick", menuOpen: "menuOpen", menuClose: "menuClose" }, queries: [{ propertyName: "contentItems", predicate: TnMenuItemComponent, isSignal: true }], viewQueries: [{ propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true, isSignal: true }, { propertyName: "contextMenuTemplate", first: true, predicate: ["contextMenuTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!--\n Projected <tn-menu-item> entries are re-rendered here inside the\n overlay so they participate in CdkMenu's keyboard navigation (arrow\n keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of contentItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedItemClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "fullSize", "customSize"] }, { kind: "directive", type: TnMenuActivateHoverDirective, selector: "[tnMenuActivateHover]" }, { kind: "component", type: TnMenuItemRendererComponent, selector: "tn-menu-item-renderer", inputs: ["item"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId"] }] });
3571
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnMenuComponent, isStandalone: true, selector: "tn-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuItemClick: "menuItemClick", menuOpen: "menuOpen", menuClose: "menuClose" }, queries: [{ propertyName: "contentItems", predicate: TnMenuItemComponent, isSignal: true }], viewQueries: [{ propertyName: "menuTemplate", first: true, predicate: ["menuTemplate"], descendants: true, isSignal: true }, { propertyName: "contextMenuTemplate", first: true, predicate: ["contextMenuTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <tn-menu-panel [items]=\"items()\" />\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <tn-menu-panel [items]=\"items()\" [projectedItems]=\"contentItems()\" />\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"], dependencies: [{ kind: "component", type: TnMenuPanelComponent, selector: "tn-menu-panel", inputs: ["items", "projectedItems", "nested"] }] });
3536
3572
  }
3537
3573
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnMenuComponent, decorators: [{
3538
3574
  type: Component,
3539
- args: [{ selector: 'tn-menu', standalone: true, imports: [CommonModule, CdkMenu, CdkMenuItem, CdkMenuTrigger, TnIconComponent, TnMenuActivateHoverDirective, TnMenuItemRendererComponent, TnTestIdDirective], template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <div class=\"tn-menu\" cdkMenu tnMenuActivateHover>\n <!--\n Projected <tn-menu-item> entries are re-rendered here inside the\n overlay so they participate in CdkMenu's keyboard navigation (arrow\n keys, Home/End, type-ahead) alongside the items() array below.\n -->\n @for (projected of contentItems(); track projected) {\n <button\n cdkMenuItem\n class=\"tn-menu-item\"\n type=\"button\"\n [tnTestId]=\"projected.resolvedTestId()\"\n [disabled]=\"projected.disabled()\"\n [cdkMenuItemDisabled]=\"projected.disabled()\"\n [class.disabled]=\"projected.disabled()\"\n [class.tn-menu-item--selected]=\"projected.selected()\"\n [attr.aria-current]=\"projected.selected() ? 'true' : null\"\n (click)=\"onProjectedItemClick(projected, $event)\"\n >\n @if (projected.label() !== undefined) {\n @if (projected.icon()) {\n <tn-icon size=\"sm\" class=\"tn-menu-item-icon\" [name]=\"projected.icon()!\" [library]=\"projected.iconLibrary()\" />\n }\n <span class=\"tn-menu-item-label\">{{ projected.label() }}</span>\n @if (projected.shortcut()) {\n <span class=\"tn-menu-item-shortcut\">{{ projected.shortcut() }}</span>\n }\n } @else {\n <ng-container [ngTemplateOutlet]=\"projected.content()\" />\n }\n </button>\n }\n @for (item of items(); track trackByItemId($index, item)) {\n <tn-menu-item-renderer [item]=\"item\" />\n }\n </div>\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
3575
+ args: [{ selector: 'tn-menu', standalone: true, imports: [TnMenuPanelComponent], template: "<!-- Context menu content slot: anything that isn't a projected <tn-menu-item> -->\n@if (contextMenu()) {\n <div class=\"tn-menu-context-content\" (contextmenu)=\"onContextMenu($event)\">\n <ng-content />\n </div>\n}\n\n<!-- Context menu template for overlay -->\n<ng-template #contextMenuTemplate>\n <tn-menu-panel [items]=\"items()\" />\n</ng-template>\n\n<!-- Regular menu template -->\n<ng-template #menuTemplate>\n <tn-menu-panel [items]=\"items()\" [projectedItems]=\"contentItems()\" />\n</ng-template>\n", styles: [".tn-menu-container{display:inline-block;position:relative}.tn-menu-container.tn-menu-container--context{display:block;width:100%;height:100%;cursor:context-menu}.tn-menu-trigger{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;transition:all .2s ease}.tn-menu-trigger:hover:not(.disabled){background:var(--tn-bg2, #f5f5f5);border-color:var(--tn-lines, #cccccc)}.tn-menu-trigger:focus{outline:2px solid var(--tn-primary, #007bff);outline-offset:2px}.tn-menu-trigger.disabled{opacity:.5;cursor:not-allowed}.tn-menu-arrow{font-size:12px;transition:transform .2s ease}.tn-menu-arrow.tn-menu-arrow--up{transform:rotate(180deg)}.tn-menu{display:flex;flex-direction:column;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #e0e0e0);border-radius:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;min-width:160px;max-width:300px;padding:4px 0;z-index:1000}.tn-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 16px;border:none;background:transparent;color:var(--tn-fg1, #333333);cursor:pointer;font-size:14px;text-align:left;transition:background-color .2s ease}.tn-menu-item:hover:not(.disabled){background:var(--tn-alt-bg2, #e8f4fd)!important}.tn-menu-item:focus{outline:none;background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item[aria-selected=true]{background:var(--tn-alt-bg2, #e8f4fd)}.tn-menu-item.tn-menu-item--selected{background:var(--tn-alt-bg2, #e8f4fd);font-weight:600;color:var(--tn-primary, #007bff)}.tn-menu-item.disabled{opacity:.5;cursor:not-allowed}.tn-menu-item.disabled:hover{background:transparent!important}.tn-menu-item-icon{font-size:1rem;width:16px;text-align:center}.tn-menu-item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tn-menu-item-shortcut{font-size:12px;color:var(--tn-fg2, #666666);margin-left:auto;padding-left:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-weight:400;opacity:.7}.tn-menu-item-arrow{font-size:10px;margin-left:8px;color:var(--tn-fg2, #666666);transition:transform .2s ease;flex-shrink:0}.tn-menu-item--nested{position:relative}.tn-menu-item--nested:hover .tn-menu-item-arrow{color:var(--tn-fg1, #333333)}.tn-menu-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:4px 0}.tn-menu--nested{margin-left:4px;box-shadow:0 8px 24px #00000026,0 4px 8px #0000001a;border-radius:4px}.tn-menu-context-content{width:100%;height:100%}.tn-menu-context-content:hover:before{content:\"\";position:absolute;inset:0;background:rgba(var(--tn-primary-rgb, 0, 123, 255),.05);pointer-events:none;border:1px dashed rgba(var(--tn-primary-rgb, 0, 123, 255),.3);border-radius:4px}\n"] }]
3540
3576
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], menuItemClick: [{ type: i0.Output, args: ["menuItemClick"] }], menuOpen: [{ type: i0.Output, args: ["menuOpen"] }], menuClose: [{ type: i0.Output, args: ["menuClose"] }], menuTemplate: [{ type: i0.ViewChild, args: ['menuTemplate', { isSignal: true }] }], contextMenuTemplate: [{ type: i0.ViewChild, args: ['contextMenuTemplate', { isSignal: true }] }], contentItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnMenuItemComponent), { isSignal: true }] }] } });
3541
3577
 
3542
3578
  class TnSlideToggleComponent {