@frame-kit/ui-ng-patterns 0.1.0 → 0.1.2

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,10 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, ChangeDetectionStrategy, ViewEncapsulation, Component, computed, HostBinding, inject, ViewContainerRef, DestroyRef, viewChild, signal, effect } from '@angular/core';
2
+ import { input, ChangeDetectionStrategy, ViewEncapsulation, Component, computed, HostBinding, inject, TemplateRef, Directive, ViewContainerRef, DestroyRef, Injector, viewChild, contentChild, signal, effect, afterNextRender } from '@angular/core';
3
3
  import { HeadlineComponent } from '@frame-kit/ui-ng/core/headline';
4
4
  import { TextComponent } from '@frame-kit/ui-ng/core/text';
5
5
  import { IconComponent } from '@frame-kit/ui-ng/core/icon';
6
6
  import { Overlay } from '@angular/cdk/overlay';
7
7
  import { TemplatePortal } from '@angular/cdk/portal';
8
+ import { NgTemplateOutlet } from '@angular/common';
8
9
  import { toSignal } from '@angular/core/rxjs-interop';
9
10
  import { Router, ActivatedRoute, NavigationEnd, RouterLink } from '@angular/router';
10
11
  import { filter, map, Subscription } from 'rxjs';
@@ -296,6 +297,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
296
297
  args: ['attr.inert']
297
298
  }] } });
298
299
 
300
+ /**
301
+ * Marks an `<ng-template>` whose content `fk-sidebar-nav` renders as an item's
302
+ * leading content — e.g. a method badge or any custom adornment — receiving the
303
+ * item as `$implicit`. The component still owns the row, label, disclosure,
304
+ * active/branch state, collapse, and flyout; this only customizes what sits
305
+ * before the label.
306
+ *
307
+ * The template is applied to any item whose `data` is set; items without `data`
308
+ * fall back to their `icon`.
309
+ *
310
+ * <fk-sidebar-nav [model]="nav">
311
+ * <ng-template fkSidebarNavItem let-item>
312
+ * <fk-method-badge [method]="item.data" />
313
+ * </ng-template>
314
+ * </fk-sidebar-nav>
315
+ */
316
+ class SidebarNavItemDirective {
317
+ template = inject(TemplateRef);
318
+ /** Types `let-item` as a `SidebarNavLink` in the consumer's template. */
319
+ static ngTemplateContextGuard(_directive, _context) {
320
+ return true;
321
+ }
322
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarNavItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
323
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: SidebarNavItemDirective, isStandalone: true, selector: "ng-template[fkSidebarNavItem]", ngImport: i0 });
324
+ }
325
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarNavItemDirective, decorators: [{
326
+ type: Directive,
327
+ args: [{
328
+ selector: 'ng-template[fkSidebarNavItem]',
329
+ standalone: true,
330
+ }]
331
+ }] });
332
+
299
333
  /**
300
334
  * Model-driven dashboard sidebar navigation.
301
335
  *
@@ -317,7 +351,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
317
351
  class SidebarNavComponent {
318
352
  // ===== INPUTS =====
319
353
  /** The navigation model — ordered groups of items. */
320
- model = input.required(...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
354
+ model = input([], ...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
321
355
  /**
322
356
  * Collapsed (icon-rail) state. Left `null`, the component reads the collapse
323
357
  * state of an ancestor `fk-app-shell`. Set explicitly to drive the rail
@@ -333,8 +367,21 @@ class SidebarNavComponent {
333
367
  overlay = inject(Overlay);
334
368
  vcr = inject(ViewContainerRef);
335
369
  destroyRef = inject(DestroyRef);
370
+ injector = inject(Injector);
336
371
  // ===== REFS =====
337
372
  flyoutTpl = viewChild.required('flyoutTpl');
373
+ /** Optional consumer template for an item's leading content (e.g. a badge). */
374
+ itemTpl = contentChild(SidebarNavItemDirective, ...(ngDevMode ? [{ debugName: "itemTpl" }] : /* istanbul ignore next */ []));
375
+ itemTemplate = computed(() => this.itemTpl()?.template ?? null, ...(ngDevMode ? [{ debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
376
+ /**
377
+ * The leading-content template for an item, or null. Only items carrying
378
+ * `data` use it; others fall back to their `icon`.
379
+ */
380
+ leadingTemplate(item) {
381
+ return item.data !== undefined && item.data !== null
382
+ ? this.itemTemplate()
383
+ : null;
384
+ }
338
385
  // ===== STATE =====
339
386
  /** Active URL, recomputed on every navigation — drives section-active styling. */
340
387
  currentUrl = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), map(() => stripUrl(this.router.url))), { initialValue: stripUrl(this.router.url) });
@@ -364,6 +411,25 @@ class SidebarNavComponent {
364
411
  this.currentUrl();
365
412
  this.closeFlyout();
366
413
  });
414
+ // Auto-open the section that holds the active route — on first load and
415
+ // whenever navigation lands inside a (possibly different, or previously
416
+ // collapsed) section. This only ever *adds* to openIds, so a section the
417
+ // user collapses by hand stays closed until they navigate back into it.
418
+ effect(() => {
419
+ this.currentUrl();
420
+ const activeIds = this.model()
421
+ .flatMap((group) => group.items)
422
+ .filter((item) => this.isParent(item) && this.sectionActive(item))
423
+ .map((item) => item.id);
424
+ if (activeIds.length === 0) {
425
+ return;
426
+ }
427
+ this.openIds.update((set) => {
428
+ const next = new Set(set);
429
+ activeIds.forEach((id) => next.add(id));
430
+ return next;
431
+ });
432
+ });
367
433
  this.destroyRef.onDestroy(() => this.closeFlyout());
368
434
  }
369
435
  // ===== COMPUTED =====
@@ -407,9 +473,14 @@ class SidebarNavComponent {
407
473
  }
408
474
  return (item.children ?? []).some((child) => this.linkActive(child));
409
475
  }
410
- /** Whether a parent discloses its children inline (expanded rail only). */
476
+ /**
477
+ * Whether a parent discloses its children inline (expanded rail only).
478
+ * Disclosure is owned entirely by `openIds` — the active section is seeded
479
+ * into it by the auto-open effect, so it starts open yet can still be
480
+ * collapsed by clicking the parent (the effect only re-opens on navigation).
481
+ */
411
482
  isOpen(item) {
412
- return this.openIds().has(item.id) || this.sectionActive(item);
483
+ return this.openIds().has(item.id);
413
484
  }
414
485
  // ===== INTERACTIONS =====
415
486
  /** Parent row click: flyout in the rail, inline disclosure when expanded. */
@@ -447,6 +518,9 @@ class SidebarNavComponent {
447
518
  const positionStrategy = this.overlay
448
519
  .position()
449
520
  .flexibleConnectedTo(rowEl)
521
+ // Nudge the panel back into the viewport when a low trigger row would push
522
+ // its (now height-capped) box off the bottom edge.
523
+ .withPush(true)
450
524
  .withPositions([
451
525
  {
452
526
  originX: 'end',
@@ -476,10 +550,21 @@ class SidebarNavComponent {
476
550
  this.flyoutOrigin = rowEl;
477
551
  this.flyoutItem.set(item);
478
552
  overlayRef.attach(new TemplatePortal(this.flyoutTpl(), this.vcr, { $implicit: item }));
553
+ // After the panel renders: bring the active item into view (so a selection
554
+ // deep in a long, scrolled list is visible on reopen, matching a native
555
+ // select), and point the caret at the trigger's center now that CDK has
556
+ // applied (and possibly pushed) the panel's final position.
557
+ afterNextRender(() => {
558
+ this.scrollActiveItemIntoView(overlayRef.overlayElement);
559
+ this.positionFlyoutCaret(overlayRef.overlayElement);
560
+ }, { injector: this.injector });
479
561
  // Scope the event subscriptions to this overlay's lifetime via a
480
562
  // Subscription torn down in closeFlyout() — not to the component lifetime,
481
563
  // and not relying on CDK completing the streams on dispose.
482
564
  const subscriptions = new Subscription();
565
+ // Re-point the caret whenever CDK repositions the panel (push on open, or a
566
+ // reposition while scrolling) so it keeps tracking the trigger's center.
567
+ subscriptions.add(positionStrategy.positionChanges.subscribe(() => this.positionFlyoutCaret(overlayRef.overlayElement)));
483
568
  subscriptions.add(overlayRef.outsidePointerEvents().subscribe((event) => {
484
569
  // Ignore clicks on the trigger row itself — the row's own click handler
485
570
  // toggles the flyout, so closing here would just let it reopen.
@@ -504,13 +589,54 @@ class SidebarNavComponent {
504
589
  this.flyoutOrigin = null;
505
590
  this.flyoutItem.set(null);
506
591
  }
592
+ /**
593
+ * Center the active flyout item within its scroll viewport. No-op when there
594
+ * is no active item (nothing selected) or the list isn't scrolled — the
595
+ * `scrollTop` write clamps, so a short list simply stays at the top. Operates
596
+ * only on the items container's own `scrollTop`, never `scrollIntoView`, so it
597
+ * can't perturb the page or the overlay position.
598
+ */
599
+ scrollActiveItemIntoView(overlayEl) {
600
+ const container = overlayEl.querySelector('.fk-sidebar-nav__flyout-items');
601
+ const active = container?.querySelector('.fk-sidebar-nav__flyout-item--active');
602
+ if (!container || !active) {
603
+ return;
604
+ }
605
+ const containerBox = container.getBoundingClientRect();
606
+ const activeBox = active.getBoundingClientRect();
607
+ const delta = activeBox.top -
608
+ containerBox.top -
609
+ (container.clientHeight - activeBox.height) / 2;
610
+ container.scrollTop += delta;
611
+ }
612
+ /**
613
+ * Point the flyout caret at the trigger row's vertical center. The caret is
614
+ * absolutely positioned on the panel, so a fixed `top` drifts off the icon as
615
+ * soon as `withPush` (or a flip) shifts the panel; deriving it from the live
616
+ * trigger and panel boxes keeps it aligned. Clamped to the panel so it can't
617
+ * ride off the rounded corners when the trigger sits beyond the panel's span.
618
+ */
619
+ positionFlyoutCaret(overlayEl) {
620
+ const origin = this.flyoutOrigin;
621
+ const panel = overlayEl.querySelector('.fk-sidebar-nav__flyout-panel');
622
+ if (!origin || !panel) {
623
+ return;
624
+ }
625
+ const originBox = origin.getBoundingClientRect();
626
+ const panelBox = panel.getBoundingClientRect();
627
+ const caretSize = 11; // px — matches the ::before square
628
+ const edge = 12; // keep the caret clear of the panel's rounded corners
629
+ const center = originBox.top + originBox.height / 2 - panelBox.top;
630
+ const top = Math.max(edge, Math.min(center - caretSize / 2, panelBox.height - edge - caretSize));
631
+ panel.style.setProperty('--fk-sidebar-nav-flyout-caret-top', `${top}px`);
632
+ }
507
633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
508
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SidebarNavComponent, isStandalone: true, selector: "fk-sidebar-nav", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "flyoutTpl", first: true, predicate: ["flyoutTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT \u2014 disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'menu' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n } @else {\n <!-- LEAF \u2014 direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"menu\"\n [attr.aria-label]=\"item.label\"\n >\n <div class=\"fk-sidebar-nav__flyout-head\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n @for (child of item.children; track child.id) {\n <a\n role=\"menuitem\"\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span>{{ child.label }}</span>\n </a>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.fk-sidebar-nav__group{display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-group-inner-gap, var(--fk-rhythm-2, .5rem))}.fk-sidebar-nav__group+.fk-sidebar-nav__group{margin-top:var(--fk-sidebar-nav-group-gap, var(--fk-rhythm-5, 1.25rem))}.fk-sidebar-nav__group-label{padding-inline:var(--fk-sidebar-nav-label-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-label-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-label-font-size, .625rem);font-weight:var(--fk-sidebar-nav-label-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-label-color, var(--fk-color-muted, #64748b))}.fk-sidebar-nav__divider{display:none}.fk-sidebar-nav__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-row-stack, var(--fk-rhythm-1, .25rem))}.fk-sidebar-nav__row{box-sizing:border-box;display:flex;align-items:center;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .625rem));background:var(--fk-sidebar-nav-row-bg, transparent);font-family:var(--fk-sidebar-nav-row-font-family, var(--fk-font-family-base));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__row:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__row:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__row--active,.fk-sidebar-nav__row--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--active .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--branch,.fk-sidebar-nav__row--branch:hover{background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__row--branch .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__icon{flex:none;display:inline-flex;color:var(--fk-sidebar-nav-icon-color, var(--fk-color-muted, #8a93a8));transition:color .13s ease}.fk-sidebar-nav__row:hover .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-icon-color-hover, var(--fk-color-text, #36405a))}.fk-sidebar-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.fk-sidebar-nav__chevron{flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--fk-sidebar-nav-chevron-color, var(--fk-color-muted, #8a93a8));transition:transform .18s ease}.fk-sidebar-nav__chevron--open{transform:rotate(90deg)}.fk-sidebar-nav__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-children-gap, var(--fk-rhythm-2, .5rem));list-style:none;margin:var(--fk-sidebar-nav-children-offset, var(--fk-rhythm-2, .5rem)) 0 0;padding-left:var(--fk-sidebar-nav-children-indent, var(--fk-rhythm-6, 1.5rem))}.fk-sidebar-nav__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-sidebar-nav-children-line-offset, var(--fk-rhythm-3, .75rem));width:var(--fk-sidebar-nav-children-line-width, var(--fk-border-width, 1px));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__child{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;padding:var(--fk-sidebar-nav-child-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-child-radius, var(--fk-radius-md, .625rem));font-size:var(--fk-sidebar-nav-child-font-size, .875rem);font-weight:var(--fk-sidebar-nav-child-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-child-color, var(--fk-color-muted, #5c6781));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__child:hover{background:var(--fk-sidebar-nav-child-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-child-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__child:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__child--active,.fk-sidebar-nav__child--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group-label{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group:not(:first-child) .fk-sidebar-nav__divider{display:block;height:1px;margin:0 var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__row{justify-content:center;padding-inline:var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__label,:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__chevron{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__children{display:none}.fk-sidebar-nav__flyout-panel{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-width:var(--fk-sidebar-nav-flyout-min-width, 13rem);padding:var(--fk-sidebar-nav-flyout-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-radius:var(--fk-sidebar-nav-flyout-radius, var(--fk-radius-md, .75rem));box-shadow:var(--fk-sidebar-nav-flyout-shadow, var(--fk-shadow-lg, 0 18px 44px -14px rgba(20, 34, 80, .3)))}.fk-sidebar-nav__flyout-panel:before{content:\"\";position:absolute;left:-.375rem;top:1.375rem;width:.6875rem;height:.6875rem;background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border-left:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));transform:rotate(45deg)}.fk-sidebar-nav__flyout-head{display:flex;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:.4375rem var(--fk-rhythm-3, .625rem) .5625rem;border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__flyout-icon{flex:none;display:grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:var(--fk-sidebar-nav-flyout-icon-radius, var(--fk-radius-sm, .5rem));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0));background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe))}.fk-sidebar-nav__flyout-title{font-family:var(--fk-sidebar-nav-flyout-title-font-family, var(--fk-font-family-display, var(--fk-font-family-base)));font-size:var(--fk-sidebar-nav-flyout-title-font-size, .84375rem);font-weight:var(--fk-sidebar-nav-flyout-title-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-sidebar-nav-flyout-title-color, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .12s ease,color .12s ease}.fk-sidebar-nav__flyout-item:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__flyout-item--active,.fk-sidebar-nav__flyout-item--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-font-weight-semibold, 600)}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IconComponent, selector: "fk-icon", inputs: ["name", "size", "color", "className", "id", "ariaLabel", "ariaHidden"] }, { kind: "directive", type: TooltipDirective, selector: "[fkTooltip]", inputs: ["fkTooltip", "fkTooltipPosition", "fkTooltipOffset", "fkTooltipShowDelay", "fkTooltipHideDelay", "fkTooltipDisabled", "fkTooltipClassName"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
634
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: SidebarNavComponent, isStandalone: true, selector: "fk-sidebar-nav", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClass" } }, queries: [{ propertyName: "itemTpl", first: true, predicate: SidebarNavItemDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "flyoutTpl", first: true, predicate: ["flyoutTpl"], descendants: true, isSignal: true }], ngImport: i0, template: "@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT \u2014 disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'true' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <li class=\"fk-sidebar-nav__subgroup\">\n <span class=\"fk-sidebar-nav__subgroup-title\">{{\n child.label\n }}</span>\n </li>\n } @else {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{\n child.label\n }}</span>\n </a>\n </li>\n }\n }\n </ul>\n }\n } @else {\n <!-- LEAF \u2014 direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <!--\n A labelled group of navigation links \u2014 not an ARIA `menu`. Menu semantics\n promise roving arrow-key focus we don't implement (these are Tab-navigated\n links), so `role=\"group\"` + `aria-label` is the honest, valid structure.\n -->\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"group\"\n [attr.aria-label]=\"item.label\"\n >\n <!-- Decorative: the group is already named by aria-label, so the visual\n title/icon would only repeat it to assistive tech. -->\n <div class=\"fk-sidebar-nav__flyout-head\" aria-hidden=\"true\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n <!--\n The items scroll inside their own bounded container, not the panel: the\n panel hosts the caret (::before) which sits outside its left edge, so\n putting overflow on the panel itself would clip it.\n -->\n <div class=\"fk-sidebar-nav__flyout-items\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <span class=\"fk-sidebar-nav__flyout-subgroup\">{{ child.label }}</span>\n } @else {\n <a\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block}.fk-sidebar-nav__group{display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-group-inner-gap, var(--fk-rhythm-2, .5rem))}.fk-sidebar-nav__group+.fk-sidebar-nav__group{margin-top:var(--fk-sidebar-nav-group-gap, var(--fk-rhythm-5, 1.25rem))}.fk-sidebar-nav__group-label{padding-inline:var(--fk-sidebar-nav-label-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-label-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-label-font-size, .625rem);font-weight:var(--fk-sidebar-nav-label-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-label-color, var(--fk-color-muted, #64748b))}.fk-sidebar-nav__divider{display:none}.fk-sidebar-nav__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-row-stack, var(--fk-rhythm-1, .25rem))}.fk-sidebar-nav__row{box-sizing:border-box;display:flex;align-items:center;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .625rem));background:var(--fk-sidebar-nav-row-bg, transparent);font-family:var(--fk-sidebar-nav-row-font-family, var(--fk-font-family-base));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__row:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__row:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__row--active,.fk-sidebar-nav__row--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--active .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--branch,.fk-sidebar-nav__row--branch:hover{background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__row--branch .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__icon{flex:none;display:inline-flex;color:var(--fk-sidebar-nav-icon-color, var(--fk-color-muted, #8a93a8));transition:color .13s ease}.fk-sidebar-nav__row:hover .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-icon-color-hover, var(--fk-color-text, #36405a))}.fk-sidebar-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.fk-sidebar-nav__chevron{flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--fk-sidebar-nav-chevron-color, var(--fk-color-muted, #8a93a8));transition:transform .18s ease}.fk-sidebar-nav__chevron--open{transform:rotate(90deg)}.fk-sidebar-nav__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-children-gap, var(--fk-rhythm-2, .5rem));list-style:none;margin:var(--fk-sidebar-nav-children-offset, var(--fk-rhythm-2, .5rem)) 0 0;padding-left:var(--fk-sidebar-nav-children-indent, var(--fk-rhythm-6, 1.5rem))}.fk-sidebar-nav__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-sidebar-nav-children-line-offset, var(--fk-rhythm-3, .75rem));width:var(--fk-sidebar-nav-children-line-width, var(--fk-border-width, 1px));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__child{box-sizing:border-box;display:flex;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;margin:0;padding:var(--fk-sidebar-nav-child-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-child-radius, var(--fk-radius-md, .625rem));font-size:var(--fk-sidebar-nav-child-font-size, .875rem);font-weight:var(--fk-sidebar-nav-child-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-child-color, var(--fk-color-muted, #5c6781));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__child:hover{background:var(--fk-sidebar-nav-child-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-child-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__child:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__child--active,.fk-sidebar-nav__child--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}.fk-sidebar-nav__subgroup{margin:var(--fk-sidebar-nav-subgroup-offset, var(--fk-rhythm-2, .5rem)) 0 0}.fk-sidebar-nav__subgroup-title{display:block;padding-inline:var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-subgroup-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-subgroup-font-size, .625rem);font-weight:var(--fk-sidebar-nav-subgroup-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-subgroup-color, var(--fk-color-muted, #8a93a8))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group-label{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group:not(:first-child) .fk-sidebar-nav__divider{display:block;height:1px;margin:0 var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__row{justify-content:center;padding-inline:var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__label,:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__chevron{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__children{display:none}.fk-sidebar-nav__flyout-panel{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-width:var(--fk-sidebar-nav-flyout-min-width, 13rem);max-width:var(--fk-sidebar-nav-flyout-max-width, 22rem);max-height:var(--fk-sidebar-nav-flyout-max-height, min(28rem, 100dvh - 2rem));padding:var(--fk-sidebar-nav-flyout-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-radius:var(--fk-sidebar-nav-flyout-radius, var(--fk-radius-md, .75rem));box-shadow:var(--fk-sidebar-nav-flyout-shadow, var(--fk-shadow-lg, 0 18px 44px -14px rgba(20, 34, 80, .3)))}.fk-sidebar-nav__flyout-panel:before{content:\"\";position:absolute;left:-.375rem;top:var(--fk-sidebar-nav-flyout-caret-top, 1.375rem);width:.6875rem;height:.6875rem;background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border-left:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));transform:rotate(45deg)}.fk-sidebar-nav__flyout-items{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-height:0;overflow-y:auto;overscroll-behavior:contain}.fk-sidebar-nav__flyout-head{display:flex;flex-shrink:0;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:.4375rem var(--fk-rhythm-3, .625rem) .5625rem;border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__flyout-icon{flex:none;display:grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:var(--fk-sidebar-nav-flyout-icon-radius, var(--fk-radius-sm, .5rem));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0));background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe))}.fk-sidebar-nav__flyout-title{min-width:0;overflow:hidden;font-family:var(--fk-sidebar-nav-flyout-title-font-family, var(--fk-font-family-display, var(--fk-font-family-base)));font-size:var(--fk-sidebar-nav-flyout-title-font-size, .84375rem);font-weight:var(--fk-sidebar-nav-flyout-title-font-weight, var(--fk-font-weight-semibold, 600));white-space:nowrap;text-overflow:ellipsis;color:var(--fk-sidebar-nav-flyout-title-color, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-subgroup{display:block;padding:var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem) var(--fk-rhythm-1, .25rem);font-family:var(--fk-sidebar-nav-subgroup-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-subgroup-font-size, .625rem);font-weight:var(--fk-sidebar-nav-subgroup-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--fk-sidebar-nav-subgroup-color, var(--fk-color-muted, #8a93a8))}.fk-sidebar-nav__flyout-item{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .12s ease,color .12s ease}.fk-sidebar-nav__flyout-item:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__flyout-item--active,.fk-sidebar-nav__flyout-item--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IconComponent, selector: "fk-icon", inputs: ["name", "size", "color", "className", "id", "ariaLabel", "ariaHidden"] }, { kind: "directive", type: TooltipDirective, selector: "[fkTooltip]", inputs: ["fkTooltip", "fkTooltipPosition", "fkTooltipOffset", "fkTooltipShowDelay", "fkTooltipHideDelay", "fkTooltipDisabled", "fkTooltipClassName"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
509
635
  }
510
636
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SidebarNavComponent, decorators: [{
511
637
  type: Component,
512
- args: [{ selector: 'fk-sidebar-nav', standalone: true, imports: [RouterLink, IconComponent, TooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT \u2014 disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'menu' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n } @else {\n <!-- LEAF \u2014 direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"menu\"\n [attr.aria-label]=\"item.label\"\n >\n <div class=\"fk-sidebar-nav__flyout-head\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n @for (child of item.children; track child.id) {\n <a\n role=\"menuitem\"\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span>{{ child.label }}</span>\n </a>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.fk-sidebar-nav__group{display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-group-inner-gap, var(--fk-rhythm-2, .5rem))}.fk-sidebar-nav__group+.fk-sidebar-nav__group{margin-top:var(--fk-sidebar-nav-group-gap, var(--fk-rhythm-5, 1.25rem))}.fk-sidebar-nav__group-label{padding-inline:var(--fk-sidebar-nav-label-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-label-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-label-font-size, .625rem);font-weight:var(--fk-sidebar-nav-label-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-label-color, var(--fk-color-muted, #64748b))}.fk-sidebar-nav__divider{display:none}.fk-sidebar-nav__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-row-stack, var(--fk-rhythm-1, .25rem))}.fk-sidebar-nav__row{box-sizing:border-box;display:flex;align-items:center;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .625rem));background:var(--fk-sidebar-nav-row-bg, transparent);font-family:var(--fk-sidebar-nav-row-font-family, var(--fk-font-family-base));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__row:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__row:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__row--active,.fk-sidebar-nav__row--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--active .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--branch,.fk-sidebar-nav__row--branch:hover{background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__row--branch .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__icon{flex:none;display:inline-flex;color:var(--fk-sidebar-nav-icon-color, var(--fk-color-muted, #8a93a8));transition:color .13s ease}.fk-sidebar-nav__row:hover .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-icon-color-hover, var(--fk-color-text, #36405a))}.fk-sidebar-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.fk-sidebar-nav__chevron{flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--fk-sidebar-nav-chevron-color, var(--fk-color-muted, #8a93a8));transition:transform .18s ease}.fk-sidebar-nav__chevron--open{transform:rotate(90deg)}.fk-sidebar-nav__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-children-gap, var(--fk-rhythm-2, .5rem));list-style:none;margin:var(--fk-sidebar-nav-children-offset, var(--fk-rhythm-2, .5rem)) 0 0;padding-left:var(--fk-sidebar-nav-children-indent, var(--fk-rhythm-6, 1.5rem))}.fk-sidebar-nav__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-sidebar-nav-children-line-offset, var(--fk-rhythm-3, .75rem));width:var(--fk-sidebar-nav-children-line-width, var(--fk-border-width, 1px));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__child{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;padding:var(--fk-sidebar-nav-child-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-child-radius, var(--fk-radius-md, .625rem));font-size:var(--fk-sidebar-nav-child-font-size, .875rem);font-weight:var(--fk-sidebar-nav-child-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-child-color, var(--fk-color-muted, #5c6781));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__child:hover{background:var(--fk-sidebar-nav-child-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-child-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__child:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__child--active,.fk-sidebar-nav__child--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group-label{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group:not(:first-child) .fk-sidebar-nav__divider{display:block;height:1px;margin:0 var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__row{justify-content:center;padding-inline:var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__label,:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__chevron{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__children{display:none}.fk-sidebar-nav__flyout-panel{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-width:var(--fk-sidebar-nav-flyout-min-width, 13rem);padding:var(--fk-sidebar-nav-flyout-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-radius:var(--fk-sidebar-nav-flyout-radius, var(--fk-radius-md, .75rem));box-shadow:var(--fk-sidebar-nav-flyout-shadow, var(--fk-shadow-lg, 0 18px 44px -14px rgba(20, 34, 80, .3)))}.fk-sidebar-nav__flyout-panel:before{content:\"\";position:absolute;left:-.375rem;top:1.375rem;width:.6875rem;height:.6875rem;background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border-left:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));transform:rotate(45deg)}.fk-sidebar-nav__flyout-head{display:flex;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:.4375rem var(--fk-rhythm-3, .625rem) .5625rem;border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__flyout-icon{flex:none;display:grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:var(--fk-sidebar-nav-flyout-icon-radius, var(--fk-radius-sm, .5rem));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0));background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe))}.fk-sidebar-nav__flyout-title{font-family:var(--fk-sidebar-nav-flyout-title-font-family, var(--fk-font-family-display, var(--fk-font-family-base)));font-size:var(--fk-sidebar-nav-flyout-title-font-size, .84375rem);font-weight:var(--fk-sidebar-nav-flyout-title-font-weight, var(--fk-font-weight-semibold, 600));color:var(--fk-sidebar-nav-flyout-title-color, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .12s ease,color .12s ease}.fk-sidebar-nav__flyout-item:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__flyout-item--active,.fk-sidebar-nav__flyout-item--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-font-weight-semibold, 600)}\n"] }]
513
- }], ctorParameters: () => [], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], flyoutTpl: [{ type: i0.ViewChild, args: ['flyoutTpl', { isSignal: true }] }], hostClass: [{
638
+ args: [{ selector: 'fk-sidebar-nav', standalone: true, imports: [RouterLink, IconComponent, TooltipDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT \u2014 disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'true' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <li class=\"fk-sidebar-nav__subgroup\">\n <span class=\"fk-sidebar-nav__subgroup-title\">{{\n child.label\n }}</span>\n </li>\n } @else {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{\n child.label\n }}</span>\n </a>\n </li>\n }\n }\n </ul>\n }\n } @else {\n <!-- LEAF \u2014 direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <!--\n A labelled group of navigation links \u2014 not an ARIA `menu`. Menu semantics\n promise roving arrow-key focus we don't implement (these are Tab-navigated\n links), so `role=\"group\"` + `aria-label` is the honest, valid structure.\n -->\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"group\"\n [attr.aria-label]=\"item.label\"\n >\n <!-- Decorative: the group is already named by aria-label, so the visual\n title/icon would only repeat it to assistive tech. -->\n <div class=\"fk-sidebar-nav__flyout-head\" aria-hidden=\"true\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n <!--\n The items scroll inside their own bounded container, not the panel: the\n panel hosts the caret (::before) which sits outside its left edge, so\n putting overflow on the panel itself would clip it.\n -->\n <div class=\"fk-sidebar-nav__flyout-items\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <span class=\"fk-sidebar-nav__flyout-subgroup\">{{ child.label }}</span>\n } @else {\n <a\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block}.fk-sidebar-nav__group{display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-group-inner-gap, var(--fk-rhythm-2, .5rem))}.fk-sidebar-nav__group+.fk-sidebar-nav__group{margin-top:var(--fk-sidebar-nav-group-gap, var(--fk-rhythm-5, 1.25rem))}.fk-sidebar-nav__group-label{padding-inline:var(--fk-sidebar-nav-label-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-label-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-label-font-size, .625rem);font-weight:var(--fk-sidebar-nav-label-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-label-color, var(--fk-color-muted, #64748b))}.fk-sidebar-nav__divider{display:none}.fk-sidebar-nav__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-row-stack, var(--fk-rhythm-1, .25rem))}.fk-sidebar-nav__row{box-sizing:border-box;display:flex;align-items:center;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border:none;border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .625rem));background:var(--fk-sidebar-nav-row-bg, transparent);font-family:var(--fk-sidebar-nav-row-font-family, var(--fk-font-family-base));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__row:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__row:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__row--active,.fk-sidebar-nav__row--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--active .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-row-color-active, #ffffff)}.fk-sidebar-nav__row--branch,.fk-sidebar-nav__row--branch:hover{background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__row--branch .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0))}.fk-sidebar-nav__icon{flex:none;display:inline-flex;color:var(--fk-sidebar-nav-icon-color, var(--fk-color-muted, #8a93a8));transition:color .13s ease}.fk-sidebar-nav__row:hover .fk-sidebar-nav__icon{color:var(--fk-sidebar-nav-icon-color-hover, var(--fk-color-text, #36405a))}.fk-sidebar-nav__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.fk-sidebar-nav__chevron{flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--fk-sidebar-nav-chevron-color, var(--fk-color-muted, #8a93a8));transition:transform .18s ease}.fk-sidebar-nav__chevron--open{transform:rotate(90deg)}.fk-sidebar-nav__children{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-children-gap, var(--fk-rhythm-2, .5rem));list-style:none;margin:var(--fk-sidebar-nav-children-offset, var(--fk-rhythm-2, .5rem)) 0 0;padding-left:var(--fk-sidebar-nav-children-indent, var(--fk-rhythm-6, 1.5rem))}.fk-sidebar-nav__children:before{content:\"\";position:absolute;top:0;bottom:0;left:var(--fk-sidebar-nav-children-line-offset, var(--fk-rhythm-3, .75rem));width:var(--fk-sidebar-nav-children-line-width, var(--fk-border-width, 1px));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__child{box-sizing:border-box;display:flex;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));width:100%;margin:0;padding:var(--fk-sidebar-nav-child-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-child-radius, var(--fk-radius-md, .625rem));font-size:var(--fk-sidebar-nav-child-font-size, .875rem);font-weight:var(--fk-sidebar-nav-child-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-child-color, var(--fk-color-muted, #5c6781));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .13s ease,color .13s ease}.fk-sidebar-nav__child:hover{background:var(--fk-sidebar-nav-child-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-child-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__child:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__child--active,.fk-sidebar-nav__child--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}.fk-sidebar-nav__subgroup{margin:var(--fk-sidebar-nav-subgroup-offset, var(--fk-rhythm-2, .5rem)) 0 0}.fk-sidebar-nav__subgroup-title{display:block;padding-inline:var(--fk-sidebar-nav-child-padding-inline, var(--fk-rhythm-3, .75rem));font-family:var(--fk-sidebar-nav-subgroup-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-subgroup-font-size, .625rem);font-weight:var(--fk-sidebar-nav-subgroup-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;color:var(--fk-sidebar-nav-subgroup-color, var(--fk-color-muted, #8a93a8))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group-label{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__group:not(:first-child) .fk-sidebar-nav__divider{display:block;height:1px;margin:0 var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-children-line-color, var(--fk-color-border, #e5e8ef))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__row{justify-content:center;padding-inline:var(--fk-sidebar-nav-rail-padding, var(--fk-rhythm-2, .5rem))}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__label,:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__chevron{display:none}:host(.fk-sidebar-nav--collapsed) .fk-sidebar-nav__children{display:none}.fk-sidebar-nav__flyout-panel{position:relative;display:flex;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-width:var(--fk-sidebar-nav-flyout-min-width, 13rem);max-width:var(--fk-sidebar-nav-flyout-max-width, 22rem);max-height:var(--fk-sidebar-nav-flyout-max-height, min(28rem, 100dvh - 2rem));padding:var(--fk-sidebar-nav-flyout-padding, var(--fk-rhythm-2, .5rem));background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-radius:var(--fk-sidebar-nav-flyout-radius, var(--fk-radius-md, .75rem));box-shadow:var(--fk-sidebar-nav-flyout-shadow, var(--fk-shadow-lg, 0 18px 44px -14px rgba(20, 34, 80, .3)))}.fk-sidebar-nav__flyout-panel:before{content:\"\";position:absolute;left:-.375rem;top:var(--fk-sidebar-nav-flyout-caret-top, 1.375rem);width:.6875rem;height:.6875rem;background:var(--fk-sidebar-nav-flyout-bg, var(--fk-color-surface, #ffffff));border-left:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef));transform:rotate(45deg)}.fk-sidebar-nav__flyout-items{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fk-sidebar-nav-flyout-gap, var(--fk-rhythm-1, .25rem));min-height:0;overflow-y:auto;overscroll-behavior:contain}.fk-sidebar-nav__flyout-head{display:flex;flex-shrink:0;align-items:center;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:.4375rem var(--fk-rhythm-3, .625rem) .5625rem;border-bottom:var(--fk-border-width, 1px) solid var(--fk-sidebar-nav-flyout-border, var(--fk-color-border, #e5e8ef))}.fk-sidebar-nav__flyout-icon{flex:none;display:grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:var(--fk-sidebar-nav-flyout-icon-radius, var(--fk-radius-sm, .5rem));color:var(--fk-sidebar-nav-branch-color, var(--fk-color-primary, #2348e0));background:var(--fk-sidebar-nav-branch-bg, var(--fk-color-primary-light, #eef2fe))}.fk-sidebar-nav__flyout-title{min-width:0;overflow:hidden;font-family:var(--fk-sidebar-nav-flyout-title-font-family, var(--fk-font-family-display, var(--fk-font-family-base)));font-size:var(--fk-sidebar-nav-flyout-title-font-size, .84375rem);font-weight:var(--fk-sidebar-nav-flyout-title-font-weight, var(--fk-font-weight-semibold, 600));white-space:nowrap;text-overflow:ellipsis;color:var(--fk-sidebar-nav-flyout-title-color, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-subgroup{display:block;padding:var(--fk-rhythm-2, .5rem) var(--fk-rhythm-3, .75rem) var(--fk-rhythm-1, .25rem);font-family:var(--fk-sidebar-nav-subgroup-font-family, var(--fk-font-family-mono));font-size:var(--fk-sidebar-nav-subgroup-font-size, .625rem);font-weight:var(--fk-sidebar-nav-subgroup-font-weight, var(--fk-font-weight-semibold, 600));letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--fk-sidebar-nav-subgroup-color, var(--fk-color-muted, #8a93a8))}.fk-sidebar-nav__flyout-item{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;gap:var(--fk-sidebar-nav-row-gap, var(--fk-rhythm-2, .5rem));padding:var(--fk-sidebar-nav-row-padding-block, var(--fk-rhythm-2, .5rem)) var(--fk-sidebar-nav-row-padding-inline, var(--fk-rhythm-3, .75rem));border-radius:var(--fk-sidebar-nav-row-radius, var(--fk-radius-md, .5rem));font-size:var(--fk-sidebar-nav-row-font-size, .875rem);font-weight:var(--fk-sidebar-nav-row-font-weight, var(--fk-font-weight-medium, 500));color:var(--fk-sidebar-nav-row-color, var(--fk-color-muted, #36405a));text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .12s ease,color .12s ease}.fk-sidebar-nav__flyout-item:hover{background:var(--fk-sidebar-nav-row-bg-hover, var(--fk-color-surface-muted, #f5f6fa));color:var(--fk-sidebar-nav-row-color-hover, var(--fk-color-text, #0e1526))}.fk-sidebar-nav__flyout-item:focus-visible{outline:none;box-shadow:var(--fk-sidebar-nav-focus-ring, var(--fk-focus-ring, 0 0 0 3px rgba(35, 72, 224, .28)))}.fk-sidebar-nav__flyout-item--active,.fk-sidebar-nav__flyout-item--active:hover{background:var(--fk-sidebar-nav-row-bg-active, var(--fk-color-primary, #2348e0));color:var(--fk-sidebar-nav-row-color-active, #ffffff);font-weight:var(--fk-sidebar-nav-child-font-weight-active, var(--fk-font-weight-semibold, 600))}\n"] }]
639
+ }], ctorParameters: () => [], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], flyoutTpl: [{ type: i0.ViewChild, args: ['flyoutTpl', { isSignal: true }] }], itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SidebarNavItemDirective), { isSignal: true }] }], hostClass: [{
514
640
  type: HostBinding,
515
641
  args: ['class']
516
642
  }] } });
@@ -523,5 +649,5 @@ function stripUrl(url) {
523
649
  * Generated bundle index. Do not edit.
524
650
  */
525
651
 
526
- export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, SidebarNavComponent, TableEmptyStateComponent };
652
+ export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, SidebarNavComponent, SidebarNavItemDirective, TableEmptyStateComponent };
527
653
  //# sourceMappingURL=frame-kit-ui-ng-patterns-dashboard.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"frame-kit-ui-ng-patterns-dashboard.mjs","sources":["../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.ts","../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.html","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.ts","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.html","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.ts","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.html","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.ts","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.html","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.ts","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.html","../../../../packages/ui-ng-patterns/dashboard/sidebar-nav/sidebar-nav.component.ts","../../../../packages/ui-ng-patterns/dashboard/sidebar-nav/sidebar-nav.component.html","../../../../packages/ui-ng-patterns/dashboard/frame-kit-ui-ng-patterns-dashboard.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport type { HeadlineLevel } from '@frame-kit/ui-ng/core/headline';\nimport { HeadlineComponent } from '@frame-kit/ui-ng/core/headline';\nimport { TextComponent } from '@frame-kit/ui-ng/core/text';\n\n/**\n * Page-level dashboard header: title + optional description on the left, up to\n * ~3 actions on the right.\n *\n * When the header's own width gets tight, the actions drop below the text and\n * go full-width (mobile-style). The break is driven by a CSS container query on\n * the host, so it responds to the content region's width — not the viewport,\n * which means it behaves correctly inside a narrow panel.\n *\n * Project actions by tagging each button with `pageHeaderActions`:\n *\n * <fk-page-header title=\"Users\" description=\"...\">\n * <fk-button pageHeaderActions variant=\"outline\" size=\"sm\">Import</fk-button>\n * <fk-button pageHeaderActions variant=\"primary\" size=\"sm\">Create user</fk-button>\n * </fk-page-header>\n *\n * Tagging each button (rather than wrapping them in a div) makes every button a\n * direct flex child, so full-width stacking falls out of `align-items: stretch`\n * with no `::ng-deep` reach-in.\n *\n * The title row has two projected slots — `[pageHeaderLeading]` before the\n * title and `[pageHeaderTrailing]` after it. Project a plain `fk-icon` for a\n * decorative mark, or a `button` for a clickable one (the consumer owns the\n * interaction, focus ring, and aria-label):\n *\n * <fk-page-header title=\"Permissions\">\n * <fk-icon pageHeaderLeading name=\"key-outline\" />\n * <button pageHeaderTrailing type=\"button\" aria-label=\"Docs\" (click)=\"openDocs()\">\n * <fk-icon name=\"view-api\" />\n * </button>\n * </fk-page-header>\n */\n@Component({\n selector: 'fk-page-header',\n standalone: true,\n imports: [HeadlineComponent, TextComponent],\n // Emulated (not None) so `:host` works — `:host { display: block }` is what\n // keeps the custom element from defaulting to `display: inline` and\n // collapsing to min-content under the container query below.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './page-header.component.html',\n styleUrl: './page-header.component.scss',\n})\nexport class PageHeaderComponent {\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n readonly headingLevel = input<HeadlineLevel>(1);\n}\n","<header class=\"fk-page-header\">\n <div class=\"fk-page-header__text\">\n <div class=\"fk-page-header__title-row\">\n <ng-content select=\"[pageHeaderLeading]\" />\n\n <fk-headline [level]=\"headingLevel()\">{{ title() }}</fk-headline>\n\n <ng-content select=\"[pageHeaderTrailing]\" />\n </div>\n\n @if (description()) {\n <fk-text class=\"fk-page-header__description\" tone=\"muted\">{{\n description()\n }}</fk-text>\n }\n </div>\n\n <div class=\"fk-page-header__actions\">\n <ng-content select=\"[pageHeaderActions]\" />\n </div>\n</header>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\n\n/**\n * Empty-state panel for dashboard tables and lists: a centered icon, a title,\n * an optional description, and a projected actions slot (typically one primary\n * fk-button). Bordered-card styling, meant to stand in for a table body when\n * there are no rows.\n *\n * The action row flips from a horizontal row to a centered vertical stack on\n * narrow widths, driven by a container query on the host — so it reacts to the\n * empty-state's own width (e.g. inside a split column) rather than the viewport.\n *\n * <fk-table-empty-state\n * icon=\"globe\"\n * title=\"No webhooks configured\"\n * description=\"Add a webhook to receive events.\"\n * >\n * <fk-button variant=\"primary\" size=\"sm\">Add webhook</fk-button>\n * </fk-table-empty-state>\n *\n * The actions slot is an open `ng-content`, so project as many actions as the\n * design needs (one primary is the norm; two reads cleanly).\n */\n@Component({\n selector: 'fk-table-empty-state',\n standalone: true,\n imports: [IconComponent],\n // Emulated (default) so `:host` works — the host is the bordered flex card\n // and establishes the container-query context the actions break against.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './table-empty-state.component.html',\n styleUrl: './table-empty-state.component.scss',\n})\nexport class TableEmptyStateComponent {\n // ===== INPUTS =====\n readonly icon = input<string | null>(null);\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-table-empty-state', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n}\n","@if (icon()) {\n <div class=\"fk-table-empty-state__icon\">\n <fk-icon [name]=\"icon()!\" size=\"lg\" />\n </div>\n}\n\n<h3 class=\"fk-table-empty-state__title\">{{ title() }}</h3>\n\n@if (description()) {\n <p class=\"fk-table-empty-state__description\">{{ description() }}</p>\n}\n\n<div class=\"fk-table-empty-state__actions\">\n <ng-content />\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Two-column split layout with container-query responsive stacking.\n *\n * Projects content into `[start]` (grows to fill) and `[end]` (content-sized or\n * fixed width). Below 700px container width, columns stack vertically.\n *\n * A standalone layout primitive for custom dashboard rows.\n */\n@Component({\n selector: 'fk-content-split-layout',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './content-split-layout.component.html',\n styleUrl: './content-split-layout.component.scss',\n})\nexport class ContentSplitLayoutComponent {\n readonly endWidth = input<string | null>(null);\n readonly gap = input<string>('var(--fk-rhythm-4, 1rem)');\n\n @HostBinding('class')\n readonly hostClass = 'fk-content-split-layout';\n}\n","<div\n class=\"fk-content-split-layout__row\"\n [style.--fk-content-split-gap]=\"gap()\"\n>\n <div class=\"fk-content-split-layout__start\">\n <ng-content select=\"[start]\" />\n </div>\n\n <div\n class=\"fk-content-split-layout__end\"\n [class.fk-content-split-layout__end--fixed]=\"endWidth()\"\n [style.--fk-content-split-end-width]=\"endWidth()\"\n >\n <ng-content select=\"[end]\" />\n </div>\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Responsive column layout for dashboard content. Lays projected children out\n * in equal-width columns that wrap — and ultimately collapse to a single\n * stacked column — based on the layout's *own* available width, never the\n * viewport. So it reacts correctly when the dashboard content region is\n * squeezed (sidenav open, a drawer pushed in, a split pane) while the viewport\n * still reports \"desktop\".\n *\n * Implemented with an intrinsic CSS grid — `repeat(auto-fit, minmax(min, 1fr))`\n * — so there is no container query, no JS, and no viewport breakpoint to keep\n * in sync. `minColumnWidth` is the smallest a column may shrink to before the\n * grid drops a column; empty tracks collapse, so N children never produce more\n * than N columns. Spacing on both axes is a single `gutter` gap, so wrapped\n * rows and side-by-side columns separate consistently with no edge artifacts.\n *\n * <fk-columns minColumnWidth=\"18rem\">\n * <fk-field-group>…</fk-field-group>\n * <fk-field-group>…</fk-field-group>\n * </fk-columns>\n *\n * By default columns grow to fill the row (`1fr`). Set `maxColumnWidth` to cap\n * how wide a column may get — past that, columns stay that width and the extra\n * space is left empty (columns aligned to the start) rather than stretching.\n */\n@Component({\n selector: 'fk-columns',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './columns.component.html',\n styleUrl: './columns.component.scss',\n})\nexport class ColumnsComponent {\n // ===== INPUTS =====\n /**\n * The smallest width a column may shrink to before the grid drops to fewer\n * columns. This is the single knob that controls when columns wrap/stack.\n */\n readonly minColumnWidth = input<string>('16rem');\n /**\n * Optional cap on how wide a column may grow. Unset (default), columns fill\n * the row (`1fr`); when set (e.g. `'22rem'`), columns size between\n * `minColumnWidth` and this value and don't stretch past it — leftover space\n * stays empty, columns aligned to the start.\n */\n readonly maxColumnWidth = input<string | null>(null);\n /** Gap between side-by-side columns and between wrapped rows. */\n readonly gutter = input<string>('var(--fk-rhythm-4, 1rem)');\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-columns', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n @HostBinding('style.--fk-columns-min')\n get hostMin(): string {\n return this.minColumnWidth();\n }\n\n // Null leaves the property unset, so the SCSS falls back to `1fr` (fill).\n @HostBinding('style.--fk-columns-max')\n get hostMax(): string | null {\n return this.maxColumnWidth();\n }\n\n @HostBinding('style.--fk-columns-gutter')\n get hostGutter(): string {\n return this.gutter();\n }\n}\n","<ng-content />\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Padded container for an expanded dashboard detail region — typically the body\n * of a data-table row expansion, holding an `fk-columns` of form controls.\n *\n * The `disabled` input greys the panel and makes its entire subtree `inert`\n * (non-interactive, unfocusable, hidden from assistive technology) — for\n * controls gated behind a feature toggle that must not be editable until the\n * feature is enabled.\n *\n * <fk-detail-panel [disabled]=\"!feature.enabled\">\n * <fk-columns>…</fk-columns>\n * </fk-detail-panel>\n */\n@Component({\n selector: 'fk-detail-panel',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './detail-panel.component.html',\n styleUrl: './detail-panel.component.scss',\n})\nexport class DetailPanelComponent {\n // ===== INPUTS =====\n /**\n * Greys the panel and makes its content inert (locked) — for feature-gated\n * controls that should not be editable until the feature is enabled.\n */\n readonly disabled = input<boolean>(false);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n [\n 'fk-detail-panel',\n this.disabled() ? 'fk-detail-panel--disabled' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n /**\n * Lock the whole subtree when disabled. `inert` blocks pointer interaction,\n * focus, and assistive-tech access in one attribute — stronger and more\n * correct than `pointer-events: none`, which only blocks the mouse.\n */\n @HostBinding('attr.inert')\n get hostInert(): '' | null {\n return this.disabled() ? '' : null;\n }\n}\n","<ng-content />\n","import { Overlay, OverlayRef } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n effect,\n HostBinding,\n inject,\n input,\n signal,\n TemplateRef,\n viewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n ActivatedRoute,\n NavigationEnd,\n Router,\n RouterLink,\n} from '@angular/router';\nimport { filter, map, Subscription } from 'rxjs';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\nimport { TooltipDirective } from '@frame-kit/ui-ng/directives/tooltip';\nimport { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';\n\nimport type {\n SidebarNavItem,\n SidebarNavLink,\n SidebarNavModel,\n} from './sidebar-nav.types';\n\n/**\n * Model-driven dashboard sidebar navigation.\n *\n * Renders a sidebar from a {@link SidebarNavModel}: labelled groups of items,\n * where each item is either a leaf link or a parent with children. The pattern\n * owns the two-state behavior that makes a dashboard rail usable:\n *\n * - **Expanded** — parents disclose their children inline; the section\n * containing the active route stays open and reads as active (tinted).\n * - **Collapsed (icon rail)** — labels collapse to icons with hover tooltips;\n * a parent click opens a **flyout** beside the rail (anchored via CDK\n * Overlay) listing its children, so nested pages stay reachable without\n * expanding the whole sidebar.\n *\n * Drop it into an `fk-app-shell` sidenav slot — it reads the shell's collapse\n * state automatically. Pass `[collapsed]` explicitly to drive the rail without\n * a shell.\n */\n@Component({\n selector: 'fk-sidebar-nav',\n standalone: true,\n imports: [RouterLink, IconComponent, TooltipDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './sidebar-nav.component.html',\n styleUrl: './sidebar-nav.component.scss',\n})\nexport class SidebarNavComponent {\n // ===== INPUTS =====\n /** The navigation model — ordered groups of items. */\n readonly model = input.required<SidebarNavModel>();\n /**\n * Collapsed (icon-rail) state. Left `null`, the component reads the collapse\n * state of an ancestor `fk-app-shell`. Set explicitly to drive the rail\n * without a shell present.\n */\n readonly collapsed = input<boolean | null>(null);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n\n // ===== DEPENDENCIES =====\n private readonly shell = inject(AppShellComponent, { optional: true });\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly overlay = inject(Overlay);\n private readonly vcr = inject(ViewContainerRef);\n private readonly destroyRef = inject(DestroyRef);\n\n // ===== REFS =====\n private readonly flyoutTpl =\n viewChild.required<TemplateRef<unknown>>('flyoutTpl');\n\n // ===== STATE =====\n /** Active URL, recomputed on every navigation — drives section-active styling. */\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(() => stripUrl(this.router.url)),\n ),\n { initialValue: stripUrl(this.router.url) },\n );\n /** Parent ids the user has explicitly opened (the active section is always open). */\n private readonly openIds = signal<ReadonlySet<string>>(new Set());\n\n private overlayRef: OverlayRef | null = null;\n /** Overlay-scoped event subscriptions — torn down with the overlay in closeFlyout(). */\n private flyoutSubs: Subscription | null = null;\n /** The trigger row the open flyout is anchored to — used to ignore its own re-click. */\n private flyoutOrigin: HTMLElement | null = null;\n /** The parent whose flyout is currently open (collapsed rail), or null. */\n protected readonly flyoutItem = signal<SidebarNavItem | null>(null);\n\n constructor() {\n // Tear down an open flyout the moment the rail expands. Clicking the shell\n // toggle would close it via the outside-pointer handler, but a programmatic\n // `[collapsed]` change, a responsive breakpoint flip, or a keyboard toggle\n // produces no pointer event — so the overlay would otherwise linger beside\n // the now-expanded sidebar.\n effect(() => {\n if (!this.isCollapsed()) {\n this.closeFlyout();\n }\n });\n\n // Close on any navigation (covers programmatic router.navigate, which\n // disposeOnNavigation does not catch, and keeps internal state in sync).\n effect(() => {\n this.currentUrl();\n this.closeFlyout();\n });\n\n this.destroyRef.onDestroy(() => this.closeFlyout());\n }\n\n // ===== COMPUTED =====\n readonly isCollapsed = computed(\n () => this.collapsed() ?? this.shell?.isCollapsed() ?? false,\n );\n\n readonly classes = computed(() =>\n [\n 'fk-sidebar-nav',\n this.isCollapsed() ? 'fk-sidebar-nav--collapsed' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n // ===== ACTIVE / EXPANSION =====\n protected isParent(item: SidebarNavItem): boolean {\n return (item.children?.length ?? 0) > 0;\n }\n\n protected linkActive(link: SidebarNavLink): boolean {\n const routerLink = link.routerLink;\n\n if (!routerLink) {\n return false;\n }\n\n // Touch currentUrl so the check re-evaluates on every navigation (OnPush).\n this.currentUrl();\n\n // Resolve through the Router rather than string-joining: this handles array\n // commands, relative segments, leading slashes, and matrix/query params\n // correctly — a naive join silently mismatched for array routerLinks.\n const tree = this.router.createUrlTree(\n Array.isArray(routerLink) ? routerLink : [routerLink],\n { relativeTo: this.route },\n );\n\n return this.router.isActive(tree, {\n paths: link.exact ? 'exact' : 'subset',\n queryParams: 'ignored',\n fragment: 'ignored',\n matrixParams: 'ignored',\n });\n }\n\n /** True when the item itself, or any of its children, is the active route. */\n protected sectionActive(item: SidebarNavItem): boolean {\n if (this.linkActive(item)) {\n return true;\n }\n\n return (item.children ?? []).some((child) => this.linkActive(child));\n }\n\n /** Whether a parent discloses its children inline (expanded rail only). */\n protected isOpen(item: SidebarNavItem): boolean {\n return this.openIds().has(item.id) || this.sectionActive(item);\n }\n\n // ===== INTERACTIONS =====\n /** Parent row click: flyout in the rail, inline disclosure when expanded. */\n protected onParentClick(item: SidebarNavItem, rowEl: HTMLElement): void {\n if (this.isCollapsed()) {\n this.toggleFlyout(item, rowEl);\n\n return;\n }\n\n this.openIds.update((set) => {\n const next = new Set(set);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n return next;\n });\n }\n\n /** Leaf / child / flyout link click — navigation is via routerLink; tidy up. */\n protected onLinkClick(): void {\n this.closeFlyout();\n this.shell?.dismissSidenav();\n }\n\n // ===== FLYOUT (collapsed parent) =====\n private toggleFlyout(item: SidebarNavItem, rowEl: HTMLElement): void {\n if (this.flyoutItem()?.id === item.id) {\n this.closeFlyout();\n\n return;\n }\n\n this.openFlyout(item, rowEl);\n }\n\n private openFlyout(item: SidebarNavItem, rowEl: HTMLElement): void {\n this.closeFlyout();\n\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(rowEl)\n .withPositions([\n {\n originX: 'end',\n originY: 'top',\n overlayX: 'start',\n overlayY: 'top',\n offsetX: 8,\n },\n {\n originX: 'start',\n originY: 'top',\n overlayX: 'end',\n overlayY: 'top',\n offsetX: -8,\n },\n ]);\n\n const overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.overlay.scrollStrategies.reposition(),\n panelClass: 'fk-sidebar-nav__flyout',\n // Dispose on browser back/forward so the flyout can't linger over a new\n // page (matches fk-drawer). Programmatic navigation is covered by the\n // route-change effect in the constructor.\n disposeOnNavigation: true,\n });\n\n this.overlayRef = overlayRef;\n this.flyoutOrigin = rowEl;\n this.flyoutItem.set(item);\n overlayRef.attach(\n new TemplatePortal(this.flyoutTpl(), this.vcr, { $implicit: item }),\n );\n\n // Scope the event subscriptions to this overlay's lifetime via a\n // Subscription torn down in closeFlyout() — not to the component lifetime,\n // and not relying on CDK completing the streams on dispose.\n const subscriptions = new Subscription();\n\n subscriptions.add(\n overlayRef.outsidePointerEvents().subscribe((event) => {\n // Ignore clicks on the trigger row itself — the row's own click handler\n // toggles the flyout, so closing here would just let it reopen.\n const target = event.target as Node | null;\n\n if (target && this.flyoutOrigin?.contains(target)) {\n return;\n }\n\n this.closeFlyout();\n }),\n );\n\n subscriptions.add(\n overlayRef.keydownEvents().subscribe((event) => {\n if (event.key === 'Escape') {\n this.closeFlyout();\n }\n }),\n );\n\n this.flyoutSubs = subscriptions;\n }\n\n private closeFlyout(): void {\n this.flyoutSubs?.unsubscribe();\n this.flyoutSubs = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.flyoutOrigin = null;\n this.flyoutItem.set(null);\n }\n}\n\n/** Strip query string and fragment so URL comparison matches route paths. */\nfunction stripUrl(url: string): string {\n return url.split('?')[0].split('#')[0];\n}\n\n","@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT — disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'menu' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n </li>\n }\n </ul>\n }\n } @else {\n <!-- LEAF — direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"menu\"\n [attr.aria-label]=\"item.label\"\n >\n <div class=\"fk-sidebar-nav__flyout-head\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n @for (child of item.children; track child.id) {\n <a\n role=\"menuitem\"\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n <span>{{ child.label }}</span>\n </a>\n }\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frame-kit-ui-ng-patterns-dashboard.mjs","sources":["../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.ts","../../../../packages/ui-ng-patterns/dashboard/page-header/page-header.component.html","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.ts","../../../../packages/ui-ng-patterns/dashboard/table-empty-state/table-empty-state.component.html","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.ts","../../../../packages/ui-ng-patterns/dashboard/content-split-layout/content-split-layout.component.html","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.ts","../../../../packages/ui-ng-patterns/dashboard/columns/columns.component.html","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.ts","../../../../packages/ui-ng-patterns/dashboard/detail-panel/detail-panel.component.html","../../../../packages/ui-ng-patterns/dashboard/sidebar-nav/sidebar-nav-item.directive.ts","../../../../packages/ui-ng-patterns/dashboard/sidebar-nav/sidebar-nav.component.ts","../../../../packages/ui-ng-patterns/dashboard/sidebar-nav/sidebar-nav.component.html","../../../../packages/ui-ng-patterns/dashboard/frame-kit-ui-ng-patterns-dashboard.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport type { HeadlineLevel } from '@frame-kit/ui-ng/core/headline';\nimport { HeadlineComponent } from '@frame-kit/ui-ng/core/headline';\nimport { TextComponent } from '@frame-kit/ui-ng/core/text';\n\n/**\n * Page-level dashboard header: title + optional description on the left, up to\n * ~3 actions on the right.\n *\n * When the header's own width gets tight, the actions drop below the text and\n * go full-width (mobile-style). The break is driven by a CSS container query on\n * the host, so it responds to the content region's width — not the viewport,\n * which means it behaves correctly inside a narrow panel.\n *\n * Project actions by tagging each button with `pageHeaderActions`:\n *\n * <fk-page-header title=\"Users\" description=\"...\">\n * <fk-button pageHeaderActions variant=\"outline\" size=\"sm\">Import</fk-button>\n * <fk-button pageHeaderActions variant=\"primary\" size=\"sm\">Create user</fk-button>\n * </fk-page-header>\n *\n * Tagging each button (rather than wrapping them in a div) makes every button a\n * direct flex child, so full-width stacking falls out of `align-items: stretch`\n * with no `::ng-deep` reach-in.\n *\n * The title row has two projected slots — `[pageHeaderLeading]` before the\n * title and `[pageHeaderTrailing]` after it. Project a plain `fk-icon` for a\n * decorative mark, or a `button` for a clickable one (the consumer owns the\n * interaction, focus ring, and aria-label):\n *\n * <fk-page-header title=\"Permissions\">\n * <fk-icon pageHeaderLeading name=\"key-outline\" />\n * <button pageHeaderTrailing type=\"button\" aria-label=\"Docs\" (click)=\"openDocs()\">\n * <fk-icon name=\"view-api\" />\n * </button>\n * </fk-page-header>\n */\n@Component({\n selector: 'fk-page-header',\n standalone: true,\n imports: [HeadlineComponent, TextComponent],\n // Emulated (not None) so `:host` works — `:host { display: block }` is what\n // keeps the custom element from defaulting to `display: inline` and\n // collapsing to min-content under the container query below.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './page-header.component.html',\n styleUrl: './page-header.component.scss',\n})\nexport class PageHeaderComponent {\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n readonly headingLevel = input<HeadlineLevel>(1);\n}\n","<header class=\"fk-page-header\">\n <div class=\"fk-page-header__text\">\n <div class=\"fk-page-header__title-row\">\n <ng-content select=\"[pageHeaderLeading]\" />\n\n <fk-headline [level]=\"headingLevel()\">{{ title() }}</fk-headline>\n\n <ng-content select=\"[pageHeaderTrailing]\" />\n </div>\n\n @if (description()) {\n <fk-text class=\"fk-page-header__description\" tone=\"muted\">{{\n description()\n }}</fk-text>\n }\n </div>\n\n <div class=\"fk-page-header__actions\">\n <ng-content select=\"[pageHeaderActions]\" />\n </div>\n</header>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n ViewEncapsulation,\n} from '@angular/core';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\n\n/**\n * Empty-state panel for dashboard tables and lists: a centered icon, a title,\n * an optional description, and a projected actions slot (typically one primary\n * fk-button). Bordered-card styling, meant to stand in for a table body when\n * there are no rows.\n *\n * The action row flips from a horizontal row to a centered vertical stack on\n * narrow widths, driven by a container query on the host — so it reacts to the\n * empty-state's own width (e.g. inside a split column) rather than the viewport.\n *\n * <fk-table-empty-state\n * icon=\"globe\"\n * title=\"No webhooks configured\"\n * description=\"Add a webhook to receive events.\"\n * >\n * <fk-button variant=\"primary\" size=\"sm\">Add webhook</fk-button>\n * </fk-table-empty-state>\n *\n * The actions slot is an open `ng-content`, so project as many actions as the\n * design needs (one primary is the norm; two reads cleanly).\n */\n@Component({\n selector: 'fk-table-empty-state',\n standalone: true,\n imports: [IconComponent],\n // Emulated (default) so `:host` works — the host is the bordered flex card\n // and establishes the container-query context the actions break against.\n encapsulation: ViewEncapsulation.Emulated,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './table-empty-state.component.html',\n styleUrl: './table-empty-state.component.scss',\n})\nexport class TableEmptyStateComponent {\n // ===== INPUTS =====\n readonly icon = input<string | null>(null);\n readonly title = input.required<string>();\n readonly description = input<string | null>(null);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-table-empty-state', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n}\n","@if (icon()) {\n <div class=\"fk-table-empty-state__icon\">\n <fk-icon [name]=\"icon()!\" size=\"lg\" />\n </div>\n}\n\n<h3 class=\"fk-table-empty-state__title\">{{ title() }}</h3>\n\n@if (description()) {\n <p class=\"fk-table-empty-state__description\">{{ description() }}</p>\n}\n\n<div class=\"fk-table-empty-state__actions\">\n <ng-content />\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Two-column split layout with container-query responsive stacking.\n *\n * Projects content into `[start]` (grows to fill) and `[end]` (content-sized or\n * fixed width). Below 700px container width, columns stack vertically.\n *\n * A standalone layout primitive for custom dashboard rows.\n */\n@Component({\n selector: 'fk-content-split-layout',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './content-split-layout.component.html',\n styleUrl: './content-split-layout.component.scss',\n})\nexport class ContentSplitLayoutComponent {\n readonly endWidth = input<string | null>(null);\n readonly gap = input<string>('var(--fk-rhythm-4, 1rem)');\n\n @HostBinding('class')\n readonly hostClass = 'fk-content-split-layout';\n}\n","<div\n class=\"fk-content-split-layout__row\"\n [style.--fk-content-split-gap]=\"gap()\"\n>\n <div class=\"fk-content-split-layout__start\">\n <ng-content select=\"[start]\" />\n </div>\n\n <div\n class=\"fk-content-split-layout__end\"\n [class.fk-content-split-layout__end--fixed]=\"endWidth()\"\n [style.--fk-content-split-end-width]=\"endWidth()\"\n >\n <ng-content select=\"[end]\" />\n </div>\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Responsive column layout for dashboard content. Lays projected children out\n * in equal-width columns that wrap — and ultimately collapse to a single\n * stacked column — based on the layout's *own* available width, never the\n * viewport. So it reacts correctly when the dashboard content region is\n * squeezed (sidenav open, a drawer pushed in, a split pane) while the viewport\n * still reports \"desktop\".\n *\n * Implemented with an intrinsic CSS grid — `repeat(auto-fit, minmax(min, 1fr))`\n * — so there is no container query, no JS, and no viewport breakpoint to keep\n * in sync. `minColumnWidth` is the smallest a column may shrink to before the\n * grid drops a column; empty tracks collapse, so N children never produce more\n * than N columns. Spacing on both axes is a single `gutter` gap, so wrapped\n * rows and side-by-side columns separate consistently with no edge artifacts.\n *\n * <fk-columns minColumnWidth=\"18rem\">\n * <fk-field-group>…</fk-field-group>\n * <fk-field-group>…</fk-field-group>\n * </fk-columns>\n *\n * By default columns grow to fill the row (`1fr`). Set `maxColumnWidth` to cap\n * how wide a column may get — past that, columns stay that width and the extra\n * space is left empty (columns aligned to the start) rather than stretching.\n */\n@Component({\n selector: 'fk-columns',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './columns.component.html',\n styleUrl: './columns.component.scss',\n})\nexport class ColumnsComponent {\n // ===== INPUTS =====\n /**\n * The smallest width a column may shrink to before the grid drops to fewer\n * columns. This is the single knob that controls when columns wrap/stack.\n */\n readonly minColumnWidth = input<string>('16rem');\n /**\n * Optional cap on how wide a column may grow. Unset (default), columns fill\n * the row (`1fr`); when set (e.g. `'22rem'`), columns size between\n * `minColumnWidth` and this value and don't stretch past it — leftover space\n * stays empty, columns aligned to the start.\n */\n readonly maxColumnWidth = input<string | null>(null);\n /** Gap between side-by-side columns and between wrapped rows. */\n readonly gutter = input<string>('var(--fk-rhythm-4, 1rem)');\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n ['fk-columns', this.className()].filter(Boolean).join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n @HostBinding('style.--fk-columns-min')\n get hostMin(): string {\n return this.minColumnWidth();\n }\n\n // Null leaves the property unset, so the SCSS falls back to `1fr` (fill).\n @HostBinding('style.--fk-columns-max')\n get hostMax(): string | null {\n return this.maxColumnWidth();\n }\n\n @HostBinding('style.--fk-columns-gutter')\n get hostGutter(): string {\n return this.gutter();\n }\n}\n","<ng-content />\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n HostBinding,\n input,\n} from '@angular/core';\n\n/**\n * Padded container for an expanded dashboard detail region — typically the body\n * of a data-table row expansion, holding an `fk-columns` of form controls.\n *\n * The `disabled` input greys the panel and makes its entire subtree `inert`\n * (non-interactive, unfocusable, hidden from assistive technology) — for\n * controls gated behind a feature toggle that must not be editable until the\n * feature is enabled.\n *\n * <fk-detail-panel [disabled]=\"!feature.enabled\">\n * <fk-columns>…</fk-columns>\n * </fk-detail-panel>\n */\n@Component({\n selector: 'fk-detail-panel',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './detail-panel.component.html',\n styleUrl: './detail-panel.component.scss',\n})\nexport class DetailPanelComponent {\n // ===== INPUTS =====\n /**\n * Greys the panel and makes its content inert (locked) — for feature-gated\n * controls that should not be editable until the feature is enabled.\n */\n readonly disabled = input<boolean>(false);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n readonly id = input<string | null>(null);\n readonly ariaLabel = input<string | null>(null);\n\n // ===== COMPUTED =====\n readonly classes = computed(() =>\n [\n 'fk-detail-panel',\n this.disabled() ? 'fk-detail-panel--disabled' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n @HostBinding('attr.id')\n get hostId(): string | null {\n return this.id();\n }\n\n @HostBinding('attr.aria-label')\n get hostAriaLabel(): string | null {\n return this.ariaLabel();\n }\n\n /**\n * Lock the whole subtree when disabled. `inert` blocks pointer interaction,\n * focus, and assistive-tech access in one attribute — stronger and more\n * correct than `pointer-events: none`, which only blocks the mouse.\n */\n @HostBinding('attr.inert')\n get hostInert(): '' | null {\n return this.disabled() ? '' : null;\n }\n}\n","<ng-content />\n","import { Directive, inject, TemplateRef } from '@angular/core';\n\nimport type { SidebarNavLink } from './sidebar-nav.types';\n\n/**\n * Marks an `<ng-template>` whose content `fk-sidebar-nav` renders as an item's\n * leading content — e.g. a method badge or any custom adornment — receiving the\n * item as `$implicit`. The component still owns the row, label, disclosure,\n * active/branch state, collapse, and flyout; this only customizes what sits\n * before the label.\n *\n * The template is applied to any item whose `data` is set; items without `data`\n * fall back to their `icon`.\n *\n * <fk-sidebar-nav [model]=\"nav\">\n * <ng-template fkSidebarNavItem let-item>\n * <fk-method-badge [method]=\"item.data\" />\n * </ng-template>\n * </fk-sidebar-nav>\n */\n@Directive({\n selector: 'ng-template[fkSidebarNavItem]',\n standalone: true,\n})\nexport class SidebarNavItemDirective {\n readonly template =\n inject<TemplateRef<{ $implicit: SidebarNavLink }>>(TemplateRef);\n\n /** Types `let-item` as a `SidebarNavLink` in the consumer's template. */\n static ngTemplateContextGuard(\n _directive: SidebarNavItemDirective,\n _context: unknown,\n ): _context is { $implicit: SidebarNavLink } {\n return true;\n }\n}\n","import { Overlay, OverlayRef } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n DestroyRef,\n effect,\n HostBinding,\n inject,\n Injector,\n input,\n signal,\n TemplateRef,\n viewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n ActivatedRoute,\n NavigationEnd,\n Router,\n RouterLink,\n} from '@angular/router';\nimport { filter, map, Subscription } from 'rxjs';\n\nimport { IconComponent } from '@frame-kit/ui-ng/core/icon';\nimport { TooltipDirective } from '@frame-kit/ui-ng/directives/tooltip';\nimport { AppShellComponent } from '@frame-kit/ui-ng/layouts/app-shell';\n\nimport { SidebarNavItemDirective } from './sidebar-nav-item.directive';\nimport type {\n SidebarNavItem,\n SidebarNavLink,\n SidebarNavModel,\n} from './sidebar-nav.types';\n\n/**\n * Model-driven dashboard sidebar navigation.\n *\n * Renders a sidebar from a {@link SidebarNavModel}: labelled groups of items,\n * where each item is either a leaf link or a parent with children. The pattern\n * owns the two-state behavior that makes a dashboard rail usable:\n *\n * - **Expanded** — parents disclose their children inline; the section\n * containing the active route stays open and reads as active (tinted).\n * - **Collapsed (icon rail)** — labels collapse to icons with hover tooltips;\n * a parent click opens a **flyout** beside the rail (anchored via CDK\n * Overlay) listing its children, so nested pages stay reachable without\n * expanding the whole sidebar.\n *\n * Drop it into an `fk-app-shell` sidenav slot — it reads the shell's collapse\n * state automatically. Pass `[collapsed]` explicitly to drive the rail without\n * a shell.\n */\n@Component({\n selector: 'fk-sidebar-nav',\n standalone: true,\n imports: [RouterLink, IconComponent, TooltipDirective, NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './sidebar-nav.component.html',\n styleUrl: './sidebar-nav.component.scss',\n})\nexport class SidebarNavComponent {\n // ===== INPUTS =====\n /** The navigation model — ordered groups of items. */\n readonly model = input<SidebarNavModel>([]);\n /**\n * Collapsed (icon-rail) state. Left `null`, the component reads the collapse\n * state of an ancestor `fk-app-shell`. Set explicitly to drive the rail\n * without a shell present.\n */\n readonly collapsed = input<boolean | null>(null);\n\n // ===== BASE PROPS =====\n readonly className = input<string>('');\n\n // ===== DEPENDENCIES =====\n private readonly shell = inject(AppShellComponent, { optional: true });\n private readonly router = inject(Router);\n private readonly route = inject(ActivatedRoute);\n private readonly overlay = inject(Overlay);\n private readonly vcr = inject(ViewContainerRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly injector = inject(Injector);\n\n // ===== REFS =====\n private readonly flyoutTpl =\n viewChild.required<TemplateRef<unknown>>('flyoutTpl');\n /** Optional consumer template for an item's leading content (e.g. a badge). */\n private readonly itemTpl = contentChild(SidebarNavItemDirective);\n private readonly itemTemplate = computed(\n () => this.itemTpl()?.template ?? null,\n );\n\n /**\n * The leading-content template for an item, or null. Only items carrying\n * `data` use it; others fall back to their `icon`.\n */\n protected leadingTemplate(\n item: SidebarNavLink,\n ): TemplateRef<{ $implicit: SidebarNavLink }> | null {\n return item.data !== undefined && item.data !== null\n ? this.itemTemplate()\n : null;\n }\n\n // ===== STATE =====\n /** Active URL, recomputed on every navigation — drives section-active styling. */\n private readonly currentUrl = toSignal(\n this.router.events.pipe(\n filter((e): e is NavigationEnd => e instanceof NavigationEnd),\n map(() => stripUrl(this.router.url)),\n ),\n { initialValue: stripUrl(this.router.url) },\n );\n /** Parent ids the user has explicitly opened (the active section is always open). */\n private readonly openIds = signal<ReadonlySet<string>>(new Set());\n\n private overlayRef: OverlayRef | null = null;\n /** Overlay-scoped event subscriptions — torn down with the overlay in closeFlyout(). */\n private flyoutSubs: Subscription | null = null;\n /** The trigger row the open flyout is anchored to — used to ignore its own re-click. */\n private flyoutOrigin: HTMLElement | null = null;\n /** The parent whose flyout is currently open (collapsed rail), or null. */\n protected readonly flyoutItem = signal<SidebarNavItem | null>(null);\n\n constructor() {\n // Tear down an open flyout the moment the rail expands. Clicking the shell\n // toggle would close it via the outside-pointer handler, but a programmatic\n // `[collapsed]` change, a responsive breakpoint flip, or a keyboard toggle\n // produces no pointer event — so the overlay would otherwise linger beside\n // the now-expanded sidebar.\n effect(() => {\n if (!this.isCollapsed()) {\n this.closeFlyout();\n }\n });\n\n // Close on any navigation (covers programmatic router.navigate, which\n // disposeOnNavigation does not catch, and keeps internal state in sync).\n effect(() => {\n this.currentUrl();\n this.closeFlyout();\n });\n\n // Auto-open the section that holds the active route — on first load and\n // whenever navigation lands inside a (possibly different, or previously\n // collapsed) section. This only ever *adds* to openIds, so a section the\n // user collapses by hand stays closed until they navigate back into it.\n effect(() => {\n this.currentUrl();\n\n const activeIds = this.model()\n .flatMap((group) => group.items)\n .filter((item) => this.isParent(item) && this.sectionActive(item))\n .map((item) => item.id);\n\n if (activeIds.length === 0) {\n return;\n }\n\n this.openIds.update((set) => {\n const next = new Set(set);\n activeIds.forEach((id) => next.add(id));\n\n return next;\n });\n });\n\n this.destroyRef.onDestroy(() => this.closeFlyout());\n }\n\n // ===== COMPUTED =====\n readonly isCollapsed = computed(\n () => this.collapsed() ?? this.shell?.isCollapsed() ?? false,\n );\n\n readonly classes = computed(() =>\n [\n 'fk-sidebar-nav',\n this.isCollapsed() ? 'fk-sidebar-nav--collapsed' : '',\n this.className(),\n ]\n .filter(Boolean)\n .join(' '),\n );\n\n @HostBinding('class')\n get hostClass(): string {\n return this.classes();\n }\n\n // ===== ACTIVE / EXPANSION =====\n protected isParent(item: SidebarNavItem): boolean {\n return (item.children?.length ?? 0) > 0;\n }\n\n protected linkActive(link: SidebarNavLink): boolean {\n const routerLink = link.routerLink;\n\n if (!routerLink) {\n return false;\n }\n\n // Touch currentUrl so the check re-evaluates on every navigation (OnPush).\n this.currentUrl();\n\n // Resolve through the Router rather than string-joining: this handles array\n // commands, relative segments, leading slashes, and matrix/query params\n // correctly — a naive join silently mismatched for array routerLinks.\n const tree = this.router.createUrlTree(\n Array.isArray(routerLink) ? routerLink : [routerLink],\n { relativeTo: this.route },\n );\n\n return this.router.isActive(tree, {\n paths: link.exact ? 'exact' : 'subset',\n queryParams: 'ignored',\n fragment: 'ignored',\n matrixParams: 'ignored',\n });\n }\n\n /** True when the item itself, or any of its children, is the active route. */\n protected sectionActive(item: SidebarNavItem): boolean {\n if (this.linkActive(item)) {\n return true;\n }\n\n return (item.children ?? []).some((child) => this.linkActive(child));\n }\n\n /**\n * Whether a parent discloses its children inline (expanded rail only).\n * Disclosure is owned entirely by `openIds` — the active section is seeded\n * into it by the auto-open effect, so it starts open yet can still be\n * collapsed by clicking the parent (the effect only re-opens on navigation).\n */\n protected isOpen(item: SidebarNavItem): boolean {\n return this.openIds().has(item.id);\n }\n\n // ===== INTERACTIONS =====\n /** Parent row click: flyout in the rail, inline disclosure when expanded. */\n protected onParentClick(item: SidebarNavItem, rowEl: HTMLElement): void {\n if (this.isCollapsed()) {\n this.toggleFlyout(item, rowEl);\n\n return;\n }\n\n this.openIds.update((set) => {\n const next = new Set(set);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n return next;\n });\n }\n\n /** Leaf / child / flyout link click — navigation is via routerLink; tidy up. */\n protected onLinkClick(): void {\n this.closeFlyout();\n this.shell?.dismissSidenav();\n }\n\n // ===== FLYOUT (collapsed parent) =====\n private toggleFlyout(item: SidebarNavItem, rowEl: HTMLElement): void {\n if (this.flyoutItem()?.id === item.id) {\n this.closeFlyout();\n\n return;\n }\n\n this.openFlyout(item, rowEl);\n }\n\n private openFlyout(item: SidebarNavItem, rowEl: HTMLElement): void {\n this.closeFlyout();\n\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(rowEl)\n // Nudge the panel back into the viewport when a low trigger row would push\n // its (now height-capped) box off the bottom edge.\n .withPush(true)\n .withPositions([\n {\n originX: 'end',\n originY: 'top',\n overlayX: 'start',\n overlayY: 'top',\n offsetX: 8,\n },\n {\n originX: 'start',\n originY: 'top',\n overlayX: 'end',\n overlayY: 'top',\n offsetX: -8,\n },\n ]);\n\n const overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.overlay.scrollStrategies.reposition(),\n panelClass: 'fk-sidebar-nav__flyout',\n // Dispose on browser back/forward so the flyout can't linger over a new\n // page (matches fk-drawer). Programmatic navigation is covered by the\n // route-change effect in the constructor.\n disposeOnNavigation: true,\n });\n\n this.overlayRef = overlayRef;\n this.flyoutOrigin = rowEl;\n this.flyoutItem.set(item);\n overlayRef.attach(\n new TemplatePortal(this.flyoutTpl(), this.vcr, { $implicit: item }),\n );\n\n // After the panel renders: bring the active item into view (so a selection\n // deep in a long, scrolled list is visible on reopen, matching a native\n // select), and point the caret at the trigger's center now that CDK has\n // applied (and possibly pushed) the panel's final position.\n afterNextRender(\n () => {\n this.scrollActiveItemIntoView(overlayRef.overlayElement);\n this.positionFlyoutCaret(overlayRef.overlayElement);\n },\n { injector: this.injector },\n );\n\n // Scope the event subscriptions to this overlay's lifetime via a\n // Subscription torn down in closeFlyout() — not to the component lifetime,\n // and not relying on CDK completing the streams on dispose.\n const subscriptions = new Subscription();\n\n // Re-point the caret whenever CDK repositions the panel (push on open, or a\n // reposition while scrolling) so it keeps tracking the trigger's center.\n subscriptions.add(\n positionStrategy.positionChanges.subscribe(() =>\n this.positionFlyoutCaret(overlayRef.overlayElement),\n ),\n );\n\n subscriptions.add(\n overlayRef.outsidePointerEvents().subscribe((event) => {\n // Ignore clicks on the trigger row itself — the row's own click handler\n // toggles the flyout, so closing here would just let it reopen.\n const target = event.target as Node | null;\n\n if (target && this.flyoutOrigin?.contains(target)) {\n return;\n }\n\n this.closeFlyout();\n }),\n );\n\n subscriptions.add(\n overlayRef.keydownEvents().subscribe((event) => {\n if (event.key === 'Escape') {\n this.closeFlyout();\n }\n }),\n );\n\n this.flyoutSubs = subscriptions;\n }\n\n private closeFlyout(): void {\n this.flyoutSubs?.unsubscribe();\n this.flyoutSubs = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.flyoutOrigin = null;\n this.flyoutItem.set(null);\n }\n\n /**\n * Center the active flyout item within its scroll viewport. No-op when there\n * is no active item (nothing selected) or the list isn't scrolled — the\n * `scrollTop` write clamps, so a short list simply stays at the top. Operates\n * only on the items container's own `scrollTop`, never `scrollIntoView`, so it\n * can't perturb the page or the overlay position.\n */\n private scrollActiveItemIntoView(overlayEl: HTMLElement): void {\n const container = overlayEl.querySelector<HTMLElement>(\n '.fk-sidebar-nav__flyout-items',\n );\n const active = container?.querySelector<HTMLElement>(\n '.fk-sidebar-nav__flyout-item--active',\n );\n\n if (!container || !active) {\n return;\n }\n\n const containerBox = container.getBoundingClientRect();\n const activeBox = active.getBoundingClientRect();\n const delta =\n activeBox.top -\n containerBox.top -\n (container.clientHeight - activeBox.height) / 2;\n\n container.scrollTop += delta;\n }\n\n /**\n * Point the flyout caret at the trigger row's vertical center. The caret is\n * absolutely positioned on the panel, so a fixed `top` drifts off the icon as\n * soon as `withPush` (or a flip) shifts the panel; deriving it from the live\n * trigger and panel boxes keeps it aligned. Clamped to the panel so it can't\n * ride off the rounded corners when the trigger sits beyond the panel's span.\n */\n private positionFlyoutCaret(overlayEl: HTMLElement): void {\n const origin = this.flyoutOrigin;\n const panel = overlayEl.querySelector<HTMLElement>(\n '.fk-sidebar-nav__flyout-panel',\n );\n\n if (!origin || !panel) {\n return;\n }\n\n const originBox = origin.getBoundingClientRect();\n const panelBox = panel.getBoundingClientRect();\n\n const caretSize = 11; // px — matches the ::before square\n const edge = 12; // keep the caret clear of the panel's rounded corners\n const center = originBox.top + originBox.height / 2 - panelBox.top;\n const top = Math.max(\n edge,\n Math.min(center - caretSize / 2, panelBox.height - edge - caretSize),\n );\n\n panel.style.setProperty('--fk-sidebar-nav-flyout-caret-top', `${top}px`);\n }\n}\n\n/** Strip query string and fragment so URL comparison matches route paths. */\nfunction stripUrl(url: string): string {\n return url.split('?')[0].split('#')[0];\n}\n\n","@for (group of model(); track $index) {\n <div class=\"fk-sidebar-nav__group\">\n @if (group.label) {\n <div class=\"fk-sidebar-nav__group-label\">{{ group.label }}</div>\n }\n <div class=\"fk-sidebar-nav__divider\" aria-hidden=\"true\"></div>\n\n <ul class=\"fk-sidebar-nav__list\">\n @for (item of group.items; track item.id) {\n <li class=\"fk-sidebar-nav__item\">\n @if (isParent(item)) {\n <!-- PARENT — disclosure row -->\n <button\n #row\n type=\"button\"\n class=\"fk-sidebar-nav__row fk-sidebar-nav__row--parent\"\n [class.fk-sidebar-nav__row--branch]=\"sectionActive(item)\"\n [attr.aria-expanded]=\"\n isCollapsed() ? flyoutItem()?.id === item.id : isOpen(item)\n \"\n [attr.aria-haspopup]=\"isCollapsed() ? 'true' : null\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onParentClick(item, row)\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n <span\n class=\"fk-sidebar-nav__chevron\"\n [class.fk-sidebar-nav__chevron--open]=\"isOpen(item)\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6.75 4.5L11.25 9L6.75 13.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </span>\n </button>\n\n @if (!isCollapsed() && isOpen(item)) {\n <ul class=\"fk-sidebar-nav__children\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <li class=\"fk-sidebar-nav__subgroup\">\n <span class=\"fk-sidebar-nav__subgroup-title\">{{\n child.label\n }}</span>\n </li>\n } @else {\n <li>\n <a\n class=\"fk-sidebar-nav__child\"\n [class.fk-sidebar-nav__child--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{\n child.label\n }}</span>\n </a>\n </li>\n }\n }\n </ul>\n }\n } @else {\n <!-- LEAF — direct link -->\n <a\n class=\"fk-sidebar-nav__row\"\n [class.fk-sidebar-nav__row--active]=\"linkActive(item)\"\n [attr.aria-current]=\"linkActive(item) ? 'page' : null\"\n [routerLink]=\"item.routerLink\"\n [attr.aria-label]=\"isCollapsed() ? item.label : null\"\n [fkTooltip]=\"item.label\"\n [fkTooltipDisabled]=\"!isCollapsed()\"\n fkTooltipPosition=\"right\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(item); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n />\n } @else if (item.icon) {\n <fk-icon\n class=\"fk-sidebar-nav__icon\"\n [name]=\"item.icon\"\n size=\"sm\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ item.label }}</span>\n </a>\n }\n </li>\n }\n </ul>\n </div>\n}\n\n<!-- Flyout panel for a collapsed parent (anchored beside the rail via CDK Overlay) -->\n<ng-template #flyoutTpl let-item>\n <!--\n A labelled group of navigation links — not an ARIA `menu`. Menu semantics\n promise roving arrow-key focus we don't implement (these are Tab-navigated\n links), so `role=\"group\"` + `aria-label` is the honest, valid structure.\n -->\n <div\n class=\"fk-sidebar-nav__flyout-panel\"\n role=\"group\"\n [attr.aria-label]=\"item.label\"\n >\n <!-- Decorative: the group is already named by aria-label, so the visual\n title/icon would only repeat it to assistive tech. -->\n <div class=\"fk-sidebar-nav__flyout-head\" aria-hidden=\"true\">\n @if (item.icon) {\n <span class=\"fk-sidebar-nav__flyout-icon\">\n <fk-icon [name]=\"item.icon\" size=\"sm\" />\n </span>\n }\n <span class=\"fk-sidebar-nav__flyout-title\">{{ item.label }}</span>\n </div>\n\n <!--\n The items scroll inside their own bounded container, not the panel: the\n panel hosts the caret (::before) which sits outside its left edge, so\n putting overflow on the panel itself would clip it.\n -->\n <div class=\"fk-sidebar-nav__flyout-items\">\n @for (child of item.children; track child.id) {\n @if (child.heading) {\n <span class=\"fk-sidebar-nav__flyout-subgroup\">{{ child.label }}</span>\n } @else {\n <a\n class=\"fk-sidebar-nav__flyout-item\"\n [class.fk-sidebar-nav__flyout-item--active]=\"linkActive(child)\"\n [attr.aria-current]=\"linkActive(child) ? 'page' : null\"\n [routerLink]=\"child.routerLink\"\n (click)=\"onLinkClick()\"\n >\n @if (leadingTemplate(child); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child }\"\n />\n }\n <span class=\"fk-sidebar-nav__label\">{{ child.label }}</span>\n </a>\n }\n }\n </div>\n </div>\n</ng-template>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frame-kit/ui-ng-patterns",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Opinionated, dashboard-ready Angular component patterns composed from @frame-kit/ui-ng.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,7 +17,7 @@
17
17
  "@angular/forms": "^21.2.0",
18
18
  "@angular/platform-browser": "^21.2.0",
19
19
  "@angular/router": "^21.2.0",
20
- "@frame-kit/ui-ng": "0.1.0",
20
+ "@frame-kit/ui-ng": "0.1.2",
21
21
  "rxjs": "^7.0.0"
22
22
  },
23
23
  "sideEffects": false,
@@ -1,4 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
2
3
  import { HeadlineLevel } from '@frame-kit/ui-ng/core/headline';
3
4
 
4
5
  /**
@@ -208,6 +209,20 @@ interface SidebarNavLink {
208
209
  * (prefix match), which is the right behavior for most nested routes.
209
210
  */
210
211
  exact?: boolean;
212
+ /**
213
+ * Arbitrary consumer payload. When set, the item's leading content is
214
+ * rendered by the `fkSidebarNavItem` template (which receives the item),
215
+ * letting consumers add custom adornments — e.g. an HTTP method badge — while
216
+ * the component keeps ownership of the row, label, disclosure, and flyout.
217
+ */
218
+ data?: unknown;
219
+ /**
220
+ * Renders this child as a non-navigable sub-section divider (a quiet
221
+ * uppercase label) instead of a link — for grouping a parent's children, e.g.
222
+ * tag headings ("Permissions", "Roles") above their endpoints. Heading
223
+ * children carry no `routerLink` and are skipped by active-state resolution.
224
+ */
225
+ heading?: boolean;
211
226
  }
212
227
  /** A child destination rendered under a parent item. Always terminal. */
213
228
  type SidebarNavChild = SidebarNavLink;
@@ -270,7 +285,18 @@ declare class SidebarNavComponent {
270
285
  private readonly overlay;
271
286
  private readonly vcr;
272
287
  private readonly destroyRef;
288
+ private readonly injector;
273
289
  private readonly flyoutTpl;
290
+ /** Optional consumer template for an item's leading content (e.g. a badge). */
291
+ private readonly itemTpl;
292
+ private readonly itemTemplate;
293
+ /**
294
+ * The leading-content template for an item, or null. Only items carrying
295
+ * `data` use it; others fall back to their `icon`.
296
+ */
297
+ protected leadingTemplate(item: SidebarNavLink): TemplateRef<{
298
+ $implicit: SidebarNavLink;
299
+ }> | null;
274
300
  /** Active URL, recomputed on every navigation — drives section-active styling. */
275
301
  private readonly currentUrl;
276
302
  /** Parent ids the user has explicitly opened (the active section is always open). */
@@ -290,7 +316,12 @@ declare class SidebarNavComponent {
290
316
  protected linkActive(link: SidebarNavLink): boolean;
291
317
  /** True when the item itself, or any of its children, is the active route. */
292
318
  protected sectionActive(item: SidebarNavItem): boolean;
293
- /** Whether a parent discloses its children inline (expanded rail only). */
319
+ /**
320
+ * Whether a parent discloses its children inline (expanded rail only).
321
+ * Disclosure is owned entirely by `openIds` — the active section is seeded
322
+ * into it by the auto-open effect, so it starts open yet can still be
323
+ * collapsed by clicking the parent (the effect only re-opens on navigation).
324
+ */
294
325
  protected isOpen(item: SidebarNavItem): boolean;
295
326
  /** Parent row click: flyout in the rail, inline disclosure when expanded. */
296
327
  protected onParentClick(item: SidebarNavItem, rowEl: HTMLElement): void;
@@ -299,9 +330,53 @@ declare class SidebarNavComponent {
299
330
  private toggleFlyout;
300
331
  private openFlyout;
301
332
  private closeFlyout;
333
+ /**
334
+ * Center the active flyout item within its scroll viewport. No-op when there
335
+ * is no active item (nothing selected) or the list isn't scrolled — the
336
+ * `scrollTop` write clamps, so a short list simply stays at the top. Operates
337
+ * only on the items container's own `scrollTop`, never `scrollIntoView`, so it
338
+ * can't perturb the page or the overlay position.
339
+ */
340
+ private scrollActiveItemIntoView;
341
+ /**
342
+ * Point the flyout caret at the trigger row's vertical center. The caret is
343
+ * absolutely positioned on the panel, so a fixed `top` drifts off the icon as
344
+ * soon as `withPush` (or a flip) shifts the panel; deriving it from the live
345
+ * trigger and panel boxes keeps it aligned. Clamped to the panel so it can't
346
+ * ride off the rounded corners when the trigger sits beyond the panel's span.
347
+ */
348
+ private positionFlyoutCaret;
302
349
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarNavComponent, never>;
303
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarNavComponent, "fk-sidebar-nav", never, { "model": { "alias": "model"; "required": true; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
350
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarNavComponent, "fk-sidebar-nav", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, {}, ["itemTpl"], never, true, never>;
351
+ }
352
+
353
+ /**
354
+ * Marks an `<ng-template>` whose content `fk-sidebar-nav` renders as an item's
355
+ * leading content — e.g. a method badge or any custom adornment — receiving the
356
+ * item as `$implicit`. The component still owns the row, label, disclosure,
357
+ * active/branch state, collapse, and flyout; this only customizes what sits
358
+ * before the label.
359
+ *
360
+ * The template is applied to any item whose `data` is set; items without `data`
361
+ * fall back to their `icon`.
362
+ *
363
+ * <fk-sidebar-nav [model]="nav">
364
+ * <ng-template fkSidebarNavItem let-item>
365
+ * <fk-method-badge [method]="item.data" />
366
+ * </ng-template>
367
+ * </fk-sidebar-nav>
368
+ */
369
+ declare class SidebarNavItemDirective {
370
+ readonly template: TemplateRef<{
371
+ $implicit: SidebarNavLink;
372
+ }>;
373
+ /** Types `let-item` as a `SidebarNavLink` in the consumer's template. */
374
+ static ngTemplateContextGuard(_directive: SidebarNavItemDirective, _context: unknown): _context is {
375
+ $implicit: SidebarNavLink;
376
+ };
377
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarNavItemDirective, never>;
378
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SidebarNavItemDirective, "ng-template[fkSidebarNavItem]", never, {}, {}, never, never, true, never>;
304
379
  }
305
380
 
306
- export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, SidebarNavComponent, TableEmptyStateComponent };
381
+ export { ColumnsComponent, ContentSplitLayoutComponent, DetailPanelComponent, PageHeaderComponent, SidebarNavComponent, SidebarNavItemDirective, TableEmptyStateComponent };
307
382
  export type { SidebarNavChild, SidebarNavGroup, SidebarNavItem, SidebarNavLink, SidebarNavModel };