@ojiepermana/angular-navigation 22.0.32 → 22.0.33

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.
@@ -43,7 +43,8 @@ function toKeyPart(value) {
43
43
  .replace(/^-+|-+$/g, '');
44
44
  }
45
45
  function itemFallbackKey(item, index) {
46
- return toKeyPart(item.title ?? item.link?.toString() ?? item.href ?? `item-${index}`) || `item-${index}`;
46
+ return (toKeyPart(item.title ?? item.link?.toString() ?? item.href ?? `item-${index}`) ||
47
+ `item-${index}`);
47
48
  }
48
49
  function normalizeItem(item, index, path) {
49
50
  if (item.isHidden?.(item)) {
@@ -82,10 +83,7 @@ function injectNavigationController(inputs) {
82
83
  ...(ngDevMode ? [{ debugName: "normalizedNavId" }] : /* istanbul ignore next */ []));
83
84
  const collapsedPreference = computed(() => inputs.collapsed() ?? (inputs.compact() ? true : null), /* @ts-ignore */
84
85
  ...(ngDevMode ? [{ debugName: "collapsedPreference" }] : /* istanbul ignore next */ []));
85
- const normalizedItems = computed(() => {
86
- const data = inputs.data();
87
- return normalizeUiNavItems(data.length > 0 ? data : inputs.items());
88
- }, /* @ts-ignore */
86
+ const normalizedItems = computed(() => normalizeUiNavItems(inputs.data()), /* @ts-ignore */
89
87
  ...(ngDevMode ? [{ debugName: "normalizedItems" }] : /* istanbul ignore next */ []));
90
88
  const fallbackState = computed(() => ({
91
89
  id: normalizedNavId(),
@@ -172,8 +170,6 @@ class NavigationContainerComponent {
172
170
  navId = input('default', { ...(ngDevMode ? { debugName: "navId" } : /* istanbul ignore next */ {}), alias: 'id' });
173
171
  data = input([], /* @ts-ignore */
174
172
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
175
- items = input([], /* @ts-ignore */
176
- ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
177
173
  ariaLabel = input('Primary navigation', /* @ts-ignore */
178
174
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
179
175
  compact = input(false, /* @ts-ignore */
@@ -212,7 +208,7 @@ class NavigationContainerComponent {
212
208
  ...(ngDevMode ? [{ debugName: "sidebarCollapse" }] : /* istanbul ignore next */ []));
213
209
  previewExpanded = computed(() => this.typeConfig()?.previewExpanded?.() ?? false, /* @ts-ignore */
214
210
  ...(ngDevMode ? [{ debugName: "previewExpanded" }] : /* istanbul ignore next */ []));
215
- typeStyle = computed(() => this.typeConfig()?.typeStyle?.() ?? 'default', /* @ts-ignore */
211
+ typeStyle = computed(() => this.typeConfig()?.typeStyle?.() ?? 'flat', /* @ts-ignore */
216
212
  ...(ngDevMode ? [{ debugName: "typeStyle" }] : /* istanbul ignore next */ []));
217
213
  flyoutLabel = computed(() => this.typeConfig()?.flyoutLabel?.() ?? 'Menu', /* @ts-ignore */
218
214
  ...(ngDevMode ? [{ debugName: "flyoutLabel" }] : /* istanbul ignore next */ []));
@@ -222,10 +218,15 @@ class NavigationContainerComponent {
222
218
  ...(ngDevMode ? [{ debugName: "flyoutIconOnly" }] : /* istanbul ignore next */ []));
223
219
  flyoutIconPosition = computed(() => this.typeConfig()?.flyoutIconPosition?.() ?? 'start', /* @ts-ignore */
224
220
  ...(ngDevMode ? [{ debugName: "flyoutIconPosition" }] : /* istanbul ignore next */ []));
221
+ flyoutTriggerVariant = computed(() => this.typeConfig()?.flyoutTriggerVariant?.() ?? 'button', /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "flyoutTriggerVariant" }] : /* istanbul ignore next */ []));
223
+ flyoutTriggerFloating = computed(() => this.typeConfig()?.flyoutTriggerFloating?.() ?? false, /* @ts-ignore */
224
+ ...(ngDevMode ? [{ debugName: "flyoutTriggerFloating" }] : /* istanbul ignore next */ []));
225
+ flyoutTriggerClass = computed(() => this.typeConfig()?.flyoutTriggerClass?.() ?? '', /* @ts-ignore */
226
+ ...(ngDevMode ? [{ debugName: "flyoutTriggerClass" }] : /* istanbul ignore next */ []));
225
227
  controller = injectNavigationController({
226
228
  navId: this.navId,
227
229
  data: this.data,
228
- items: this.items,
229
230
  orientation: this.orientationPreference,
230
231
  type: this.typePreference,
231
232
  position: this.positionPreference,
@@ -288,7 +289,7 @@ class NavigationContainerComponent {
288
289
  ...(ngDevMode ? [{ debugName: "shellClasses" }] : /* istanbul ignore next */ []));
289
290
  state = this.resolvedState;
290
291
  hostClasses = computed(() => cn('relative block min-h-0 text-foreground transition-[width] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none', this.resolvedState().orientation === 'horizontal'
291
- ? 'w-full'
292
+ ? 'h-full w-full'
292
293
  : this.resolvedState().type === 'dockbar'
293
294
  ? this.dockbarAsideOpen()
294
295
  ? 'w-76'
@@ -323,14 +324,18 @@ class NavigationContainerComponent {
323
324
  this.hoverPreviewExpanded.set(this.sidebarCollapse() ? value : false);
324
325
  }
325
326
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
326
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.2", type: NavigationContainerComponent, isStandalone: true, selector: "Navigation", inputs: { navId: { classPropertyName: "navId", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapse-tree", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "nav-group-class", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIds: "openedIdsChange", itemSelected: "itemSelected" }, host: { attributes: { "role": "navigation" }, listeners: { "mouseenter": "setHoverPreview(true)", "mouseleave": "setHoverPreview(false)" }, properties: { "class": "hostClasses()", "attr.aria-label": "ariaLabel() || null", "attr.data-navigation-id": "resolvedState().id", "attr.data-orientation": "resolvedState().orientation", "attr.data-type": "resolvedState().type", "attr.data-type-style": "typeStyle()", "attr.data-position": "resolvedState().position", "attr.data-collapse-tree": "collapseTree()", "attr.data-preview-expanded": "previewRailExpanded() ? \"true\" : null" } }, providers: [{ provide: NAVIGATION_SHELL, useExisting: forwardRef(() => NavigationContainerComponent) }], queries: [{ propertyName: "iconTemplate", first: true, predicate: NavigationIconDirective, descendants: true, isSignal: true }], ngImport: i0, template: ` <ng-content /> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
327
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.2", type: NavigationContainerComponent, isStandalone: true, selector: "Navigation", inputs: { navId: { classPropertyName: "navId", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapse-tree", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "nav-group-class", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIds: "openedIdsChange", itemSelected: "itemSelected" }, host: { attributes: { "role": "navigation" }, listeners: { "mouseenter": "setHoverPreview(true)", "mouseleave": "setHoverPreview(false)" }, properties: { "class": "hostClasses()", "attr.aria-label": "ariaLabel() || null", "attr.data-navigation-id": "resolvedState().id", "attr.data-orientation": "resolvedState().orientation", "attr.data-type": "resolvedState().type", "attr.data-type-style": "typeStyle()", "attr.data-position": "resolvedState().position", "attr.data-collapse-tree": "collapseTree()", "attr.data-preview-expanded": "previewRailExpanded() ? \"true\" : null" } }, providers: [
328
+ { provide: NAVIGATION_SHELL, useExisting: forwardRef(() => NavigationContainerComponent) },
329
+ ], queries: [{ propertyName: "iconTemplate", first: true, predicate: NavigationIconDirective, descendants: true, isSignal: true }], ngImport: i0, template: ` <ng-content /> `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
327
330
  }
328
331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationContainerComponent, decorators: [{
329
332
  type: Component,
330
333
  args: [{
331
334
  selector: 'Navigation',
332
335
  changeDetection: ChangeDetectionStrategy.OnPush,
333
- providers: [{ provide: NAVIGATION_SHELL, useExisting: forwardRef(() => NavigationContainerComponent) }],
336
+ providers: [
337
+ { provide: NAVIGATION_SHELL, useExisting: forwardRef(() => NavigationContainerComponent) },
338
+ ],
334
339
  host: {
335
340
  '[class]': 'hostClasses()',
336
341
  role: 'navigation',
@@ -347,7 +352,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
347
352
  },
348
353
  template: ` <ng-content /> `,
349
354
  }]
350
- }], propDecorators: { navId: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], collapseTree: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapse-tree", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], itemClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-group-class", required: false }] }], activeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIds", required: false }] }], activeUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeUrl", required: false }] }], openedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "openedIds", required: false }] }, { type: i0.Output, args: ["openedIdsChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], iconTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationIconDirective), { isSignal: true }] }] } });
355
+ }], propDecorators: { navId: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], collapseTree: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapse-tree", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], itemClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-group-class", required: false }] }], activeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIds", required: false }] }], activeUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeUrl", required: false }] }], openedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "openedIds", required: false }] }, { type: i0.Output, args: ["openedIdsChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], iconTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationIconDirective), { isSignal: true }] }] } });
351
356
 
352
357
  class NavigationItemContentComponent {
353
358
  nav = inject(NavigationService);
@@ -1531,7 +1536,7 @@ class NavigationEntryGridComponent {
1531
1536
  ...(ngDevMode ? [{ debugName: "navId" }] : /* istanbul ignore next */ []));
1532
1537
  branch = input.required(/* @ts-ignore */
1533
1538
  ...(ngDevMode ? [{ debugName: "branch" }] : /* istanbul ignore next */ []));
1534
- typeStyle = input('default', /* @ts-ignore */
1539
+ typeStyle = input('flat', /* @ts-ignore */
1535
1540
  ...(ngDevMode ? [{ debugName: "typeStyle" }] : /* istanbul ignore next */ []));
1536
1541
  itemClass = input('', /* @ts-ignore */
1537
1542
  ...(ngDevMode ? [{ debugName: "itemClass" }] : /* istanbul ignore next */ []));
@@ -2042,10 +2047,19 @@ class NavigationFlyoutMenuComponent {
2042
2047
  /** Penempatan ikon relatif label: `start` (default) atau `end`. */
2043
2048
  iconPosition = input('start', /* @ts-ignore */
2044
2049
  ...(ngDevMode ? [{ debugName: "iconPosition" }] : /* istanbul ignore next */ []));
2050
+ /** Bentuk visual tombol trigger: `button` (pil) | `link` | `plain` (ghost). */
2051
+ variant = input('button', /* @ts-ignore */
2052
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2053
+ /** Trigger mengambang (`fixed`) — host menjadi popover lepas, panel menempel ke trigger. */
2054
+ floating = input(false, /* @ts-ignore */
2055
+ ...(ngDevMode ? [{ debugName: "floating" }] : /* istanbul ignore next */ []));
2056
+ /** Kelas Tailwind untuk host trigger (posisi pojok floating / styling wrapper in-flow). */
2057
+ triggerClass = input('', /* @ts-ignore */
2058
+ ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
2045
2059
  /** `bottom` = bar berada di bawah konten; panel menempel sisi bawah container dan tumbuh ke atas. */
2046
2060
  position = input('top', /* @ts-ignore */
2047
2061
  ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2048
- typeStyle = input('default', /* @ts-ignore */
2062
+ typeStyle = input('flat', /* @ts-ignore */
2049
2063
  ...(ngDevMode ? [{ debugName: "typeStyle" }] : /* istanbul ignore next */ []));
2050
2064
  compact = input(false, /* @ts-ignore */
2051
2065
  ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
@@ -2090,6 +2104,12 @@ class NavigationFlyoutMenuComponent {
2090
2104
  return this.branches().find((branch) => branch.key === key) ?? null;
2091
2105
  }, /* @ts-ignore */
2092
2106
  ...(ngDevMode ? [{ debugName: "activeBranch" }] : /* istanbul ignore next */ []));
2107
+ /**
2108
+ * Floating: sisi popover relatif trigger — `true` = rata kanan (buka ke kiri, untuk trigger di
2109
+ * pojok kanan), `false` = rata kiri (buka ke kanan). Diukur dari posisi trigger saat dibuka.
2110
+ */
2111
+ floatingAlignEnd = signal(true, /* @ts-ignore */
2112
+ ...(ngDevMode ? [{ debugName: "floatingAlignEnd" }] : /* istanbul ignore next */ []));
2093
2113
  /**
2094
2114
  * Offset panel terhadap host agar panel menimpa penuh container parent dari
2095
2115
  * `Navigation` (lebar sama persis; posisi atas di mode top, posisi bawah di
@@ -2098,14 +2118,40 @@ class NavigationFlyoutMenuComponent {
2098
2118
  */
2099
2119
  panelPlacement = signal(null, /* @ts-ignore */
2100
2120
  ...(ngDevMode ? [{ debugName: "panelPlacement" }] : /* istanbul ignore next */ []));
2101
- panelWrapperClasses = computed(() => cn('absolute z-50', !this.panelPlacement() && cn('inset-x-0', this.isBottom() ? 'bottom-0' : 'top-0')), /* @ts-ignore */
2121
+ panelWrapperClasses = computed(() => {
2122
+ if (this.floating()) {
2123
+ // Floating: popover menempel ke trigger — atas/bawah ikut nav-position; sisi kiri/kanan
2124
+ // ikut posisi trigger (floatingAlignEnd) agar tidak keluar layar saat trigger di pojok kiri.
2125
+ return cn('absolute z-50 w-[min(22rem,85vw)] max-w-[85vw]', this.floatingAlignEnd() ? 'right-0' : 'left-0', this.isBottom() ? 'bottom-full mb-2' : 'top-full mt-2');
2126
+ }
2127
+ // Default: panel menutupi (overlay) container bar persis — posisi & lebar ikut container.
2128
+ return cn('absolute z-50', !this.panelPlacement() && cn('inset-x-0', this.isBottom() ? 'bottom-0' : 'top-0'));
2129
+ }, /* @ts-ignore */
2102
2130
  ...(ngDevMode ? [{ debugName: "panelWrapperClasses" }] : /* istanbul ignore next */ []));
2103
- /** Mode bottom membalik urutan visual: grid entri di atas, row tab di bawah (dekat bar). */
2104
- menuClasses = computed(() => cn('flex', this.isBottom() ? 'flex-col-reverse' : 'flex-col'), /* @ts-ignore */
2131
+ /**
2132
+ * Mode bottom membalik urutan visual: grid entri di atas, bar di bawah (dekat bar trigger).
2133
+ * `min-h-0 flex-1` membuat menu mengisi penuh panel card (yang di-clip) sehingga ROW 2
2134
+ * bisa jadi satu-satunya area scroll.
2135
+ */
2136
+ menuClasses = computed(() => cn('flex min-h-0 flex-1', this.isBottom() ? 'flex-col-reverse' : 'flex-col'), /* @ts-ignore */
2105
2137
  ...(ngDevMode ? [{ debugName: "menuClasses" }] : /* istanbul ignore next */ []));
2106
- closeButtonClasses() {
2107
- return cn('absolute right-2.5 z-10 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', this.isBottom() ? 'bottom-2' : 'top-2');
2108
- }
2138
+ /**
2139
+ * ROW 1 = bar flyout: satu baris horizontal seperti bar navbar
2140
+ * Header (kiri) · menu item (tengah, flex-1) · Footer (kanan). `shrink-0` membuatnya DIAM
2141
+ * (tidak ikut scroll) — hanya ROW 2 (grid child) yang punya area scroll sendiri.
2142
+ * Border memisahkan bar dari ROW 2 di sisi yang menjauh dari bar (bawah di mode top).
2143
+ */
2144
+ barRowClasses = computed(() => cn('flex shrink-0 items-center gap-2 bg-popover px-2 md:px-3', this.isBorderRail()
2145
+ ? cn('min-h-12 border-border', this.isBottom() ? 'border-t-[1.5px]' : 'border-b-[1.5px]')
2146
+ : cn('min-h-[3.25rem] border-[hsl(var(--border)/var(--opacity-60))]', this.isBottom() ? 'border-t' : 'border-b')), /* @ts-ignore */
2147
+ ...(ngDevMode ? [{ debugName: "barRowClasses" }] : /* istanbul ignore next */ []));
2148
+ /**
2149
+ * ROW 2 = HANYA area ini yang scroll: children dari nav item aktif. `min-h-0 flex-1`
2150
+ * membatasi tingginya ke sisa ruang panel lalu `overflow-y-auto` men-scroll isinya —
2151
+ * bar ROW 1 & kontainer panel sendiri tetap diam.
2152
+ */
2153
+ scrollRegionClasses = computed(() => cn('min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain'), /* @ts-ignore */
2154
+ ...(ngDevMode ? [{ debugName: "scrollRegionClasses" }] : /* istanbul ignore next */ []));
2109
2155
  isOpen() {
2110
2156
  return this.nav.isDrawerOpen(this.navId());
2111
2157
  }
@@ -2115,6 +2161,17 @@ class NavigationFlyoutMenuComponent {
2115
2161
  }
2116
2162
  }
2117
2163
  measurePanelPlacement() {
2164
+ // Floating: panel anchored ke trigger via CSS, bukan overlay container. Pilih sisi popover
2165
+ // (kanan→buka kiri / kiri→buka kanan) dari posisi trigger agar tidak keluar layar.
2166
+ if (this.floating()) {
2167
+ const triggerEl = this.host.nativeElement.querySelector('[data-navigation-flyout-trigger="true"]');
2168
+ const rect = triggerEl?.getBoundingClientRect();
2169
+ if (rect && typeof window !== 'undefined' && window.innerWidth > 0) {
2170
+ this.floatingAlignEnd.set(rect.left + rect.width / 2 > window.innerWidth / 2);
2171
+ }
2172
+ this.panelPlacement.set(null);
2173
+ return;
2174
+ }
2118
2175
  const hostEl = this.host.nativeElement;
2119
2176
  const navEl = hostEl.closest('[data-navigation-id]');
2120
2177
  let container = navEl?.parentElement ?? hostEl.parentElement;
@@ -2183,8 +2240,30 @@ class NavigationFlyoutMenuComponent {
2183
2240
  openBranch(item) {
2184
2241
  this.nav.openPanel(this.navId(), item);
2185
2242
  }
2243
+ /**
2244
+ * Host trigger+panel. Default `relative block w-full` (mengalir di dalam bar/div). Saat
2245
+ * `floating`, jadi `fixed` (mengambang) — default pojok kanan-atas, override via `triggerClass`.
2246
+ */
2247
+ hostClasses = computed(() => this.floating()
2248
+ ? cn('fixed z-50 block w-max', this.triggerClass() || 'right-6 top-6')
2249
+ : cn('relative block w-full', this.triggerClass()), /* @ts-ignore */
2250
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
2251
+ /** Bentuk visual trigger: `button` (pil), `link` (tautan), atau `plain` (ghost); + ikon/teks. */
2186
2252
  triggerClasses() {
2187
- return cn('nav-trigger group/nav inline-flex h-10 min-w-0 items-center gap-2 rounded-full border border-[hsl(var(--border)/var(--opacity-60))] bg-background px-4 py-2 text-sm font-medium shadow-sm transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', this.showIconOnly() && 'w-10 justify-center gap-0 px-0', this.isBorderRail()
2253
+ const iconOnly = this.showIconOnly();
2254
+ const base = 'nav-trigger group/nav inline-flex min-w-0 items-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
2255
+ if (this.variant() === 'link') {
2256
+ return cn(base, 'text-sm underline-offset-4 hover:underline',
2257
+ // Icon-only tetap punya target ≥ 36px (WCAG 2.5.8) walau tanpa chrome tautan.
2258
+ iconOnly ? 'h-9 w-9 justify-center gap-0' : 'gap-1.5', this.isOpen() ? 'text-primary underline' : 'text-primary/90 hover:text-primary', this.itemClass());
2259
+ }
2260
+ if (this.variant() === 'plain') {
2261
+ return cn(base, 'gap-2 rounded-md text-sm', iconOnly ? 'h-9 w-9 justify-center' : 'h-9 px-3 py-2', this.isOpen()
2262
+ ? 'bg-accent text-accent-foreground'
2263
+ : 'text-foreground/80 hover:bg-accent hover:text-accent-foreground', this.itemClass());
2264
+ }
2265
+ // button (pil, default)
2266
+ return cn(base, 'h-10 gap-2 rounded-full border border-[hsl(var(--border)/var(--opacity-60))] bg-background px-4 py-2 text-sm shadow-sm', iconOnly && 'w-10 justify-center gap-0 px-0', this.isBorderRail()
2188
2267
  ? this.isOpen()
2189
2268
  ? 'text-primary'
2190
2269
  : 'text-foreground/80 hover:text-primary'
@@ -2193,23 +2272,28 @@ class NavigationFlyoutMenuComponent {
2193
2272
  : 'text-foreground/80 hover:bg-accent hover:text-accent-foreground', this.itemClass());
2194
2273
  }
2195
2274
  panelCardClasses() {
2275
+ if (this.floating()) {
2276
+ // Floating: kartu popover mandiri (rounded penuh + border + shadow), tinggi dibatasi & di-clip;
2277
+ // hanya ROW 2 (children) yang scroll.
2278
+ return cn('relative flex flex-col rounded-xl border border-[hsl(var(--border)/var(--opacity-70))] bg-popover text-popover-foreground shadow-2xl', 'max-h-[min(28rem,70vh)] overflow-hidden');
2279
+ }
2196
2280
  // Border-rail: panel menutup tepat content-box frame layout, jadi garis
2197
2281
  // sisi bar memakai border frame (rail) itu sendiri — panel cukup menggambar
2198
2282
  // border 1.5px senada rail pada sisi yang menjauh dari bar agar sambungan
2199
2283
  // tetap lurus (bawah di mode top, atas di mode bottom).
2200
- return cn('relative overflow-hidden bg-popover text-popover-foreground shadow-2xl', this.isBorderRail()
2284
+ return cn('relative flex flex-col bg-popover text-popover-foreground shadow-2xl',
2285
+ // Batasi tinggi panel ke viewport lalu CLIP — bukan scroll. Hanya ROW 2 (children, lihat
2286
+ // scrollRegionClasses) yang scroll; bar ROW 1 & kontainer panel sendiri tetap diam.
2287
+ 'max-h-[calc(100dvh-5rem)] overflow-hidden', this.isBorderRail()
2201
2288
  ? cn('rounded-none border-border', this.isBottom() ? 'border-t-[1.5px]' : 'border-b-[1.5px]')
2202
2289
  : 'rounded-xl border border-[hsl(var(--border)/var(--opacity-70))]');
2203
2290
  }
2204
2291
  rowClasses() {
2205
- // `min-h` menjaga tinggi row stabil meski hanya berisi tab group yang memakai
2206
- // `-my-2`. Border pemisah row↔grid selalu di sisi yang menjauh dari bar:
2207
- // border-b di mode top, border-t di mode bottom (row berada di bawah grid).
2208
- // Border-rail: total tinggi row 48px (min-h-12) + border 1.5px sehingga garis
2209
- // row jatuh persis di posisi rail horizontal layout dan menyambung lurus.
2210
- return cn('m-0 flex min-w-0 list-none flex-wrap items-center gap-1 px-2 py-2 pr-12 md:px-3 md:pr-14', this.isBorderRail()
2211
- ? cn('min-h-12 border-border', this.isBottom() ? 'border-t-[1.5px]' : 'border-b-[1.5px]')
2212
- : cn('min-h-[3.25rem] border-[hsl(var(--border)/var(--opacity-60))]', this.isBottom() ? 'border-t' : 'border-b'));
2292
+ // Menu item = bagian tengah ROW 1: isi ruang antara Footer & Header (flex-1) lalu
2293
+ // di-center seperti menubar navbar. Saat jumlah/lebar item melebihi band, band ini
2294
+ // scroll horizontal (overflow-x-auto) agar tidak ada item ter-clip & takterjangkau
2295
+ // (panel card meng-clip overflow-x). Tinggi & border bar ditangani barRowClasses().
2296
+ return cn('m-0 flex min-w-0 flex-1 list-none items-center justify-center gap-1 overflow-x-auto overscroll-x-contain py-2');
2213
2297
  }
2214
2298
  /**
2215
2299
  * Container `<li>` tab group. Basis `flex -my-2 self-stretch` membuat tab setinggi
@@ -2258,14 +2342,22 @@ class NavigationFlyoutMenuComponent {
2258
2342
  }
2259
2343
  focusFirstPanelItem() {
2260
2344
  const panel = this.host.nativeElement.querySelector('[data-navigation-flyout-panel="true"]');
2261
- const target = panel?.querySelector('[role="menuitem"], a[href], button:not([disabled])');
2345
+ if (!panel) {
2346
+ return;
2347
+ }
2348
+ // Fokus awal ke menu item ROW 1 (lalu grid ROW 2) — BUKAN kontrol Header/Footer yang
2349
+ // juga ada di bar (mis. tombol logout). querySelector mengembalikan match pertama dalam
2350
+ // urutan DOM, dan Header/Footer bisa mendahului menu, jadi lingkup ke baris menu dulu.
2351
+ const target = panel.querySelector('[data-navigation-flyout-row="true"] [role="menuitem"], [data-navigation-flyout-row="true"] a[href]') ?? panel.querySelector('[role="menuitem"], a[href]');
2262
2352
  target?.focus();
2263
2353
  }
2264
2354
  focusTrigger() {
2265
- this.host.nativeElement.querySelector('[data-navigation-flyout-trigger="true"]')?.focus();
2355
+ this.host.nativeElement
2356
+ .querySelector('[data-navigation-flyout-trigger="true"]')
2357
+ ?.focus();
2266
2358
  }
2267
2359
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFlyoutMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2268
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationFlyoutMenuComponent, isStandalone: true, selector: "NavigationFlyoutMenu", inputs: { navId: { classPropertyName: "navId", publicName: "navId", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "typeStyle", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapseTree", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIdsChange: "openedIdsChange", itemSelected: "itemSelected" }, host: { listeners: { "keydown.escape": "onEscape($event)", "document:click": "onDocumentClick($event)", "window:resize": "onWindowResize()" }, classAttribute: "relative block w-full" }, ngImport: i0, template: `
2360
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationFlyoutMenuComponent, isStandalone: true, selector: "NavigationFlyoutMenu", inputs: { navId: { classPropertyName: "navId", publicName: "navId", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, floating: { classPropertyName: "floating", publicName: "floating", isSignal: true, isRequired: false, transformFunction: null }, triggerClass: { classPropertyName: "triggerClass", publicName: "triggerClass", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "typeStyle", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapseTree", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIdsChange: "openedIdsChange", itemSelected: "itemSelected" }, host: { listeners: { "keydown.escape": "onEscape($event)", "document:click": "onDocumentClick($event)", "window:resize": "onWindowResize()" }, properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
2269
2361
  <button
2270
2362
  type="button"
2271
2363
  data-navigation-flyout-trigger="true"
@@ -2275,7 +2367,8 @@ class NavigationFlyoutMenuComponent {
2275
2367
  [attr.aria-controls]="isOpen() ? panelId() : null"
2276
2368
  [attr.aria-label]="showIconOnly() ? label() : null"
2277
2369
  [attr.title]="showIconOnly() ? label() : null"
2278
- (click)="toggleFlyout()">
2370
+ (click)="toggleFlyout()"
2371
+ >
2279
2372
  @if (showIconOnly()) {
2280
2373
  <Icon [name]="triggerIcon() ?? 'menu'" [size]="18" class="text-current" />
2281
2374
  } @else {
@@ -2296,88 +2389,109 @@ class NavigationFlyoutMenuComponent {
2296
2389
  [id]="panelId()"
2297
2390
  data-navigation-flyout-panel="true"
2298
2391
  [class]="panelWrapperClasses()"
2299
- [style.left.px]="panelPlacement()?.left"
2300
- [style.top.px]="isBottom() ? null : panelPlacement()?.top"
2301
- [style.bottom.px]="isBottom() ? panelPlacement()?.bottom : null"
2302
- [style.width.px]="panelPlacement()?.width">
2392
+ [style.left.px]="floating() ? null : panelPlacement()?.left"
2393
+ [style.top.px]="floating() || isBottom() ? null : panelPlacement()?.top"
2394
+ [style.bottom.px]="!floating() && isBottom() ? panelPlacement()?.bottom : null"
2395
+ [style.width.px]="floating() ? null : panelPlacement()?.width"
2396
+ >
2303
2397
  <div [class]="panelCardClasses()">
2304
- <button
2305
- type="button"
2306
- data-navigation-flyout-close="true"
2307
- [class]="closeButtonClasses()"
2308
- aria-label="Close menu"
2309
- (click)="closeFlyout(true)">
2310
- <span aria-hidden="true" class="text-base leading-none">✕</span>
2311
- </button>
2312
-
2313
2398
  <div role="menu" [class]="menuClasses()" [attr.aria-label]="label()">
2314
- <ul role="none" data-navigation-flyout-row="true" [class]="rowClasses()" (keydown)="onRowKeydown($event)">
2315
- @for (item of items(); track item.key) {
2316
- @switch (item.type) {
2317
- @case ('divider') {
2318
- <li role="separator" class="mx-1 h-5 w-px bg-border"></li>
2319
- }
2320
- @case ('spacer') {
2321
- <li role="none" aria-hidden="true" class="flex-1"></li>
2322
- }
2323
- @default {
2324
- @if (item.children.length > 0) {
2325
- <li role="none" [class]="groupContainerClasses(item)" [attr.data-navigation-item-key]="item.key">
2326
- <button
2327
- type="button"
2328
- role="menuitem"
2329
- data-navigation-flyout-tab="true"
2330
- [class]="tabClasses(item)"
2331
- [attr.aria-expanded]="isBranchOpen(item)"
2332
- [attr.aria-controls]="item.panelId"
2333
- [disabled]="item.disabled || null"
2334
- (click)="openBranch(item)">
2335
- <NavigationItemContent
2336
- [item]="item"
2337
- [active]="isItemActive(item)"
2338
- [compact]="false"
2339
- [orientation]="'horizontal'"
2340
- [level]="0"
2341
- [iconTemplate]="iconTemplate()" />
2342
- <Icon name="chevron_right" [size]="16" [class]="tabChevronClasses(item)" />
2343
- </button>
2344
- </li>
2345
- } @else {
2346
- <li
2347
- NavigationItem
2348
- [navId]="navId()"
2349
- [item]="item"
2350
- [level]="0"
2351
- [orientation]="'horizontal'"
2352
- [compact]="false"
2353
- [itemClass]="navItemClass()"
2354
- [activeIds]="activeIds()"
2355
- [activeUrl]="activeUrl()"
2356
- [iconTemplate]="iconTemplate()"
2357
- [collapseTree]="collapseTree()"
2358
- [openedIds]="openedIds()"
2359
- (openedIdsChange)="openedIdsChange.emit($event)"
2360
- (itemSelected)="itemSelected.emit($event)"></li>
2399
+ <!-- ROW 1 = bar (sticky, menutup bar trigger), struktur seperti bar navbar:
2400
+ Header (kiri) | menu item (tengah) | Footer (kanan). Hanya ROW 2 (grid child)
2401
+ di bawah bar yang ikut scroll. -->
2402
+ <div data-navigation-flyout-bar="true" [class]="barRowClasses()">
2403
+ <ng-content select="NavigationHeader" />
2404
+
2405
+ <ul
2406
+ role="none"
2407
+ data-navigation-flyout-row="true"
2408
+ [class]="rowClasses()"
2409
+ (keydown)="onRowKeydown($event)"
2410
+ >
2411
+ @for (item of items(); track item.key) {
2412
+ @switch (item.type) {
2413
+ @case ('divider') {
2414
+ <li role="separator" class="mx-1 h-5 w-px bg-border"></li>
2415
+ }
2416
+ @case ('spacer') {
2417
+ <li role="none" aria-hidden="true" class="flex-1"></li>
2418
+ }
2419
+ @default {
2420
+ @if (item.children.length > 0) {
2421
+ <li
2422
+ role="none"
2423
+ [class]="groupContainerClasses(item)"
2424
+ [attr.data-navigation-item-key]="item.key"
2425
+ >
2426
+ <button
2427
+ type="button"
2428
+ role="menuitem"
2429
+ data-navigation-flyout-tab="true"
2430
+ [class]="tabClasses(item)"
2431
+ [attr.aria-expanded]="isBranchOpen(item)"
2432
+ [attr.aria-controls]="item.panelId"
2433
+ [disabled]="item.disabled || null"
2434
+ (click)="openBranch(item)"
2435
+ >
2436
+ <NavigationItemContent
2437
+ [item]="item"
2438
+ [active]="isItemActive(item)"
2439
+ [compact]="false"
2440
+ [orientation]="'horizontal'"
2441
+ [level]="0"
2442
+ [iconTemplate]="iconTemplate()"
2443
+ />
2444
+ <Icon
2445
+ name="chevron_right"
2446
+ [size]="16"
2447
+ [class]="tabChevronClasses(item)"
2448
+ />
2449
+ </button>
2450
+ </li>
2451
+ } @else {
2452
+ <li
2453
+ NavigationItem
2454
+ [navId]="navId()"
2455
+ [item]="item"
2456
+ [level]="0"
2457
+ [orientation]="'horizontal'"
2458
+ [compact]="false"
2459
+ [itemClass]="navItemClass()"
2460
+ [activeIds]="activeIds()"
2461
+ [activeUrl]="activeUrl()"
2462
+ [iconTemplate]="iconTemplate()"
2463
+ [collapseTree]="collapseTree()"
2464
+ [openedIds]="openedIds()"
2465
+ (openedIdsChange)="openedIdsChange.emit($event)"
2466
+ (itemSelected)="itemSelected.emit($event)"
2467
+ ></li>
2468
+ }
2361
2469
  }
2362
2470
  }
2363
2471
  }
2472
+ </ul>
2473
+
2474
+ <ng-content select="NavigationFooter" />
2475
+ </div>
2476
+
2477
+ <!-- ROW 2 = child dari menu item aktif; HANYA area ini yang men-scroll. -->
2478
+ <div data-navigation-flyout-scroll="true" [class]="scrollRegionClasses()">
2479
+ @if (activeBranch(); as branch) {
2480
+ <NavigationEntryGrid
2481
+ [navId]="navId()"
2482
+ [branch]="branch"
2483
+ [typeStyle]="typeStyle()"
2484
+ [itemClass]="itemClass()"
2485
+ [activeIds]="activeIds()"
2486
+ [activeUrl]="activeUrl()"
2487
+ [iconTemplate]="iconTemplate()"
2488
+ [collapseTree]="collapseTree()"
2489
+ [openedIds]="openedIds()"
2490
+ (openedIdsChange)="openedIdsChange.emit($event)"
2491
+ (itemSelected)="itemSelected.emit($event)"
2492
+ />
2364
2493
  }
2365
- </ul>
2366
-
2367
- @if (activeBranch(); as branch) {
2368
- <NavigationEntryGrid
2369
- [navId]="navId()"
2370
- [branch]="branch"
2371
- [typeStyle]="typeStyle()"
2372
- [itemClass]="itemClass()"
2373
- [activeIds]="activeIds()"
2374
- [activeUrl]="activeUrl()"
2375
- [iconTemplate]="iconTemplate()"
2376
- [collapseTree]="collapseTree()"
2377
- [openedIds]="openedIds()"
2378
- (openedIdsChange)="openedIdsChange.emit($event)"
2379
- (itemSelected)="itemSelected.emit($event)" />
2380
- }
2494
+ </div>
2381
2495
  </div>
2382
2496
  </div>
2383
2497
  </div>
@@ -2389,9 +2503,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2389
2503
  args: [{
2390
2504
  selector: 'NavigationFlyoutMenu',
2391
2505
  changeDetection: ChangeDetectionStrategy.OnPush,
2392
- imports: [IconComponent, NavigationEntryGridComponent, NavigationItemComponent, NavigationItemContentComponent],
2506
+ imports: [
2507
+ IconComponent,
2508
+ NavigationEntryGridComponent,
2509
+ NavigationItemComponent,
2510
+ NavigationItemContentComponent,
2511
+ ],
2393
2512
  host: {
2394
- class: 'relative block w-full',
2513
+ '[class]': 'hostClasses()',
2395
2514
  '(keydown.escape)': 'onEscape($event)',
2396
2515
  '(document:click)': 'onDocumentClick($event)',
2397
2516
  '(window:resize)': 'onWindowResize()',
@@ -2406,7 +2525,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2406
2525
  [attr.aria-controls]="isOpen() ? panelId() : null"
2407
2526
  [attr.aria-label]="showIconOnly() ? label() : null"
2408
2527
  [attr.title]="showIconOnly() ? label() : null"
2409
- (click)="toggleFlyout()">
2528
+ (click)="toggleFlyout()"
2529
+ >
2410
2530
  @if (showIconOnly()) {
2411
2531
  <Icon [name]="triggerIcon() ?? 'menu'" [size]="18" class="text-current" />
2412
2532
  } @else {
@@ -2427,95 +2547,116 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2427
2547
  [id]="panelId()"
2428
2548
  data-navigation-flyout-panel="true"
2429
2549
  [class]="panelWrapperClasses()"
2430
- [style.left.px]="panelPlacement()?.left"
2431
- [style.top.px]="isBottom() ? null : panelPlacement()?.top"
2432
- [style.bottom.px]="isBottom() ? panelPlacement()?.bottom : null"
2433
- [style.width.px]="panelPlacement()?.width">
2550
+ [style.left.px]="floating() ? null : panelPlacement()?.left"
2551
+ [style.top.px]="floating() || isBottom() ? null : panelPlacement()?.top"
2552
+ [style.bottom.px]="!floating() && isBottom() ? panelPlacement()?.bottom : null"
2553
+ [style.width.px]="floating() ? null : panelPlacement()?.width"
2554
+ >
2434
2555
  <div [class]="panelCardClasses()">
2435
- <button
2436
- type="button"
2437
- data-navigation-flyout-close="true"
2438
- [class]="closeButtonClasses()"
2439
- aria-label="Close menu"
2440
- (click)="closeFlyout(true)">
2441
- <span aria-hidden="true" class="text-base leading-none">✕</span>
2442
- </button>
2443
-
2444
2556
  <div role="menu" [class]="menuClasses()" [attr.aria-label]="label()">
2445
- <ul role="none" data-navigation-flyout-row="true" [class]="rowClasses()" (keydown)="onRowKeydown($event)">
2446
- @for (item of items(); track item.key) {
2447
- @switch (item.type) {
2448
- @case ('divider') {
2449
- <li role="separator" class="mx-1 h-5 w-px bg-border"></li>
2450
- }
2451
- @case ('spacer') {
2452
- <li role="none" aria-hidden="true" class="flex-1"></li>
2453
- }
2454
- @default {
2455
- @if (item.children.length > 0) {
2456
- <li role="none" [class]="groupContainerClasses(item)" [attr.data-navigation-item-key]="item.key">
2457
- <button
2458
- type="button"
2459
- role="menuitem"
2460
- data-navigation-flyout-tab="true"
2461
- [class]="tabClasses(item)"
2462
- [attr.aria-expanded]="isBranchOpen(item)"
2463
- [attr.aria-controls]="item.panelId"
2464
- [disabled]="item.disabled || null"
2465
- (click)="openBranch(item)">
2466
- <NavigationItemContent
2467
- [item]="item"
2468
- [active]="isItemActive(item)"
2469
- [compact]="false"
2470
- [orientation]="'horizontal'"
2471
- [level]="0"
2472
- [iconTemplate]="iconTemplate()" />
2473
- <Icon name="chevron_right" [size]="16" [class]="tabChevronClasses(item)" />
2474
- </button>
2475
- </li>
2476
- } @else {
2477
- <li
2478
- NavigationItem
2479
- [navId]="navId()"
2480
- [item]="item"
2481
- [level]="0"
2482
- [orientation]="'horizontal'"
2483
- [compact]="false"
2484
- [itemClass]="navItemClass()"
2485
- [activeIds]="activeIds()"
2486
- [activeUrl]="activeUrl()"
2487
- [iconTemplate]="iconTemplate()"
2488
- [collapseTree]="collapseTree()"
2489
- [openedIds]="openedIds()"
2490
- (openedIdsChange)="openedIdsChange.emit($event)"
2491
- (itemSelected)="itemSelected.emit($event)"></li>
2557
+ <!-- ROW 1 = bar (sticky, menutup bar trigger), struktur seperti bar navbar:
2558
+ Header (kiri) | menu item (tengah) | Footer (kanan). Hanya ROW 2 (grid child)
2559
+ di bawah bar yang ikut scroll. -->
2560
+ <div data-navigation-flyout-bar="true" [class]="barRowClasses()">
2561
+ <ng-content select="NavigationHeader" />
2562
+
2563
+ <ul
2564
+ role="none"
2565
+ data-navigation-flyout-row="true"
2566
+ [class]="rowClasses()"
2567
+ (keydown)="onRowKeydown($event)"
2568
+ >
2569
+ @for (item of items(); track item.key) {
2570
+ @switch (item.type) {
2571
+ @case ('divider') {
2572
+ <li role="separator" class="mx-1 h-5 w-px bg-border"></li>
2573
+ }
2574
+ @case ('spacer') {
2575
+ <li role="none" aria-hidden="true" class="flex-1"></li>
2576
+ }
2577
+ @default {
2578
+ @if (item.children.length > 0) {
2579
+ <li
2580
+ role="none"
2581
+ [class]="groupContainerClasses(item)"
2582
+ [attr.data-navigation-item-key]="item.key"
2583
+ >
2584
+ <button
2585
+ type="button"
2586
+ role="menuitem"
2587
+ data-navigation-flyout-tab="true"
2588
+ [class]="tabClasses(item)"
2589
+ [attr.aria-expanded]="isBranchOpen(item)"
2590
+ [attr.aria-controls]="item.panelId"
2591
+ [disabled]="item.disabled || null"
2592
+ (click)="openBranch(item)"
2593
+ >
2594
+ <NavigationItemContent
2595
+ [item]="item"
2596
+ [active]="isItemActive(item)"
2597
+ [compact]="false"
2598
+ [orientation]="'horizontal'"
2599
+ [level]="0"
2600
+ [iconTemplate]="iconTemplate()"
2601
+ />
2602
+ <Icon
2603
+ name="chevron_right"
2604
+ [size]="16"
2605
+ [class]="tabChevronClasses(item)"
2606
+ />
2607
+ </button>
2608
+ </li>
2609
+ } @else {
2610
+ <li
2611
+ NavigationItem
2612
+ [navId]="navId()"
2613
+ [item]="item"
2614
+ [level]="0"
2615
+ [orientation]="'horizontal'"
2616
+ [compact]="false"
2617
+ [itemClass]="navItemClass()"
2618
+ [activeIds]="activeIds()"
2619
+ [activeUrl]="activeUrl()"
2620
+ [iconTemplate]="iconTemplate()"
2621
+ [collapseTree]="collapseTree()"
2622
+ [openedIds]="openedIds()"
2623
+ (openedIdsChange)="openedIdsChange.emit($event)"
2624
+ (itemSelected)="itemSelected.emit($event)"
2625
+ ></li>
2626
+ }
2492
2627
  }
2493
2628
  }
2494
2629
  }
2630
+ </ul>
2631
+
2632
+ <ng-content select="NavigationFooter" />
2633
+ </div>
2634
+
2635
+ <!-- ROW 2 = child dari menu item aktif; HANYA area ini yang men-scroll. -->
2636
+ <div data-navigation-flyout-scroll="true" [class]="scrollRegionClasses()">
2637
+ @if (activeBranch(); as branch) {
2638
+ <NavigationEntryGrid
2639
+ [navId]="navId()"
2640
+ [branch]="branch"
2641
+ [typeStyle]="typeStyle()"
2642
+ [itemClass]="itemClass()"
2643
+ [activeIds]="activeIds()"
2644
+ [activeUrl]="activeUrl()"
2645
+ [iconTemplate]="iconTemplate()"
2646
+ [collapseTree]="collapseTree()"
2647
+ [openedIds]="openedIds()"
2648
+ (openedIdsChange)="openedIdsChange.emit($event)"
2649
+ (itemSelected)="itemSelected.emit($event)"
2650
+ />
2495
2651
  }
2496
- </ul>
2497
-
2498
- @if (activeBranch(); as branch) {
2499
- <NavigationEntryGrid
2500
- [navId]="navId()"
2501
- [branch]="branch"
2502
- [typeStyle]="typeStyle()"
2503
- [itemClass]="itemClass()"
2504
- [activeIds]="activeIds()"
2505
- [activeUrl]="activeUrl()"
2506
- [iconTemplate]="iconTemplate()"
2507
- [collapseTree]="collapseTree()"
2508
- [openedIds]="openedIds()"
2509
- (openedIdsChange)="openedIdsChange.emit($event)"
2510
- (itemSelected)="itemSelected.emit($event)" />
2511
- }
2652
+ </div>
2512
2653
  </div>
2513
2654
  </div>
2514
2655
  </div>
2515
2656
  }
2516
2657
  `,
2517
2658
  }]
2518
- }], propDecorators: { navId: [{ type: i0.Input, args: [{ isSignal: true, alias: "navId", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeStyle", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], itemClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupClass", required: false }] }], activeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIds", required: false }] }], activeUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeUrl", required: false }] }], iconTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconTemplate", required: false }] }], collapseTree: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseTree", required: false }] }], openedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "openedIds", required: false }] }], openedIdsChange: [{ type: i0.Output, args: ["openedIdsChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }] } });
2659
+ }], propDecorators: { navId: [{ type: i0.Input, args: [{ isSignal: true, alias: "navId", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], floating: [{ type: i0.Input, args: [{ isSignal: true, alias: "floating", required: false }] }], triggerClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerClass", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeStyle", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], itemClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupClass", required: false }] }], activeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIds", required: false }] }], activeUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeUrl", required: false }] }], iconTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconTemplate", required: false }] }], collapseTree: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseTree", required: false }] }], openedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "openedIds", required: false }] }], openedIdsChange: [{ type: i0.Output, args: ["openedIdsChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }] } });
2519
2660
 
2520
2661
  class NavigationHorizontalComponent {
2521
2662
  nav = inject(NavigationService);
@@ -2527,7 +2668,7 @@ class NavigationHorizontalComponent {
2527
2668
  /** Posisi bar terhadap konten: `bottom` membuka panel ke atas; selain itu ke bawah. */
2528
2669
  position = input('top', /* @ts-ignore */
2529
2670
  ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2530
- typeStyle = input('default', /* @ts-ignore */
2671
+ typeStyle = input('flat', /* @ts-ignore */
2531
2672
  ...(ngDevMode ? [{ debugName: "typeStyle" }] : /* istanbul ignore next */ []));
2532
2673
  compact = input(false, /* @ts-ignore */
2533
2674
  ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
@@ -2555,7 +2696,7 @@ class NavigationHorizontalComponent {
2555
2696
  /** Kelas tambahan untuk NavigationItem leaf di row saat style border-rail aktif. */
2556
2697
  navItemClass = computed(() => cn(this.itemClass(), this.isBorderRail() && 'rounded-none hover:bg-transparent hover:text-primary'), /* @ts-ignore */
2557
2698
  ...(ngDevMode ? [{ debugName: "navItemClass" }] : /* istanbul ignore next */ []));
2558
- listClasses = computed(() => cn('flex min-w-0 list-none p-0', this.compact() ? 'justify-center gap-1' : 'items-center justify-center gap-1'), /* @ts-ignore */
2699
+ listClasses = computed(() => cn('flex h-full min-w-0 items-center justify-center gap-1 list-none p-0'), /* @ts-ignore */
2559
2700
  ...(ngDevMode ? [{ debugName: "listClasses" }] : /* istanbul ignore next */ []));
2560
2701
  isItemActive(item) {
2561
2702
  return this.nav.isItemActive(item, this.activeIds(), this.activeUrl());
@@ -2635,10 +2776,15 @@ class NavigationHorizontalComponent {
2635
2776
  // Panel mega selebar container parent, menempel rapat tanpa gap pada sisi bar:
2636
2777
  // nav-position top membuka ke bawah (sisi atas siku), bottom membuka ke atas
2637
2778
  // (sisi bawah siku) sehingga sambungan border selalu menyatu dengan bar.
2638
- // Border-rail: rail horizontal layout menjadi garis sisi-bar panel — panel
2639
- // cukup menggambar border 1.5px senada rail pada sisi yang menjauh dari bar.
2640
- return cn('absolute z-50 overflow-hidden bg-popover text-popover-foreground shadow-lg', this.isBottom() ? 'bottom-full' : 'top-full', !this.panelPlacement() && 'inset-x-0', this.isBorderRail()
2641
- ? cn('rounded-none border-border', this.isBottom() ? 'border-t-[1.5px]' : 'border-b-[1.5px]')
2779
+ // Border-rail: sisi kiri/kanan panel dibiarkan ditangani rail vertikal dari
2780
+ // layout; panel hanya menggambar border atas & bawah 1.5px senada rail.
2781
+ // Panel dibiarkan pada tepi bawah bar (`top-full`) tanpa offset, sehingga border
2782
+ // atasnya sejajar dengan border-bottom bar (mis. yang dipasang consumer).
2783
+ return cn('absolute z-50 bg-popover text-popover-foreground shadow-lg',
2784
+ // Batasi tinggi ke viewport (dikurangi area bar) lalu scroll vertikal supaya
2785
+ // menu dengan item banyak tetap bisa diakses tanpa keluar layar.
2786
+ 'max-h-[calc(100dvh-5rem)] overflow-x-hidden overflow-y-auto overscroll-contain', this.isBottom() ? 'bottom-full' : 'top-full', !this.panelPlacement() && 'inset-x-0', this.isBorderRail()
2787
+ ? 'rounded-none border-y-[1.5px] border-border'
2642
2788
  : cn('rounded-xl border border-[hsl(var(--border)/var(--opacity-70))]', this.isBottom() ? 'rounded-b-none' : 'rounded-t-none'));
2643
2789
  }
2644
2790
  onKeydown(event) {
@@ -2701,7 +2847,7 @@ class NavigationHorizontalComponent {
2701
2847
  item?.focus();
2702
2848
  }
2703
2849
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHorizontalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2704
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationHorizontalComponent, isStandalone: true, selector: "NavigationHorizontal", inputs: { navId: { classPropertyName: "navId", publicName: "navId", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "typeStyle", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapseTree", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIdsChange: "openedIdsChange", itemSelected: "itemSelected" }, host: { listeners: { "window:resize": "onWindowResize()", "document:click": "onDocumentClick($event)" }, classAttribute: "relative block w-full" }, ngImport: i0, template: `
2850
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationHorizontalComponent, isStandalone: true, selector: "NavigationHorizontal", inputs: { navId: { classPropertyName: "navId", publicName: "navId", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "typeStyle", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, activeIds: { classPropertyName: "activeIds", publicName: "activeIds", isSignal: true, isRequired: false, transformFunction: null }, activeUrl: { classPropertyName: "activeUrl", publicName: "activeUrl", isSignal: true, isRequired: false, transformFunction: null }, iconTemplate: { classPropertyName: "iconTemplate", publicName: "iconTemplate", isSignal: true, isRequired: false, transformFunction: null }, collapseTree: { classPropertyName: "collapseTree", publicName: "collapseTree", isSignal: true, isRequired: false, transformFunction: null }, openedIds: { classPropertyName: "openedIds", publicName: "openedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openedIdsChange: "openedIdsChange", itemSelected: "itemSelected" }, host: { listeners: { "window:resize": "onWindowResize()", "document:click": "onDocumentClick($event)" }, classAttribute: "relative block h-full w-full" }, ngImport: i0, template: `
2705
2851
  <ul [class]="listClasses()" role="menubar" (keydown)="onKeydown($event)">
2706
2852
  @for (item of items(); track item.key) {
2707
2853
  @switch (item.type) {
@@ -2799,7 +2945,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2799
2945
  NavigationItemContentComponent,
2800
2946
  ],
2801
2947
  host: {
2802
- class: 'relative block w-full',
2948
+ class: 'relative block h-full w-full',
2803
2949
  '(window:resize)': 'onWindowResize()',
2804
2950
  '(document:click)': 'onDocumentClick($event)',
2805
2951
  },
@@ -2900,7 +3046,7 @@ class NavigationContentComponent {
2900
3046
  class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
2901
3047
  hostClasses = computed(() => cn('min-h-0 flex-1', this.container.displayState().orientation === 'horizontal'
2902
3048
  ? 'flex h-full min-w-0 items-stretch justify-center overflow-visible'
2903
- : 'block overflow-y-auto overflow-x-hidden', this.class()), /* @ts-ignore */
3049
+ : 'block overflow-y-auto overflow-x-hidden py-2', this.class()), /* @ts-ignore */
2904
3050
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
2905
3051
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2906
3052
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationContentComponent, isStandalone: true, selector: "NavigationContent", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
@@ -2914,6 +3060,9 @@ class NavigationContentComponent {
2914
3060
  [icon]="container.flyoutIcon()"
2915
3061
  [iconOnly]="container.flyoutIconOnly()"
2916
3062
  [iconPosition]="container.flyoutIconPosition()"
3063
+ [variant]="container.flyoutTriggerVariant()"
3064
+ [floating]="container.flyoutTriggerFloating()"
3065
+ [triggerClass]="container.flyoutTriggerClass()"
2917
3066
  [position]="navState.position"
2918
3067
  [typeStyle]="container.typeStyle()"
2919
3068
  [compact]="container.compact()"
@@ -2925,7 +3074,11 @@ class NavigationContentComponent {
2925
3074
  [collapseTree]="container.collapseTree()"
2926
3075
  [openedIds]="container.openedIds()"
2927
3076
  (openedIdsChange)="container.openedIds.set($event)"
2928
- (itemSelected)="container.itemSelected.emit($event)" />
3077
+ (itemSelected)="container.itemSelected.emit($event)"
3078
+ >
3079
+ <ng-content select="NavigationHeader" ngProjectAs="NavigationHeader" />
3080
+ <ng-content select="NavigationFooter" ngProjectAs="NavigationFooter" />
3081
+ </NavigationFlyoutMenu>
2929
3082
  } @else if (navState.orientation === 'horizontal') {
2930
3083
  <NavigationHorizontal
2931
3084
  [navId]="navState.id"
@@ -2941,7 +3094,8 @@ class NavigationContentComponent {
2941
3094
  [collapseTree]="container.collapseTree()"
2942
3095
  [openedIds]="container.openedIds()"
2943
3096
  (openedIdsChange)="container.openedIds.set($event)"
2944
- (itemSelected)="container.itemSelected.emit($event)" />
3097
+ (itemSelected)="container.itemSelected.emit($event)"
3098
+ />
2945
3099
  } @else if (navState.type === 'dockbar') {
2946
3100
  <NavigationDockbarMenu
2947
3101
  [navId]="navState.id"
@@ -2955,7 +3109,8 @@ class NavigationContentComponent {
2955
3109
  [collapseTree]="container.collapseTree()"
2956
3110
  [openedIds]="container.openedIds()"
2957
3111
  (openedIdsChange)="container.openedIds.set($event)"
2958
- (itemSelected)="container.itemSelected.emit($event)" />
3112
+ (itemSelected)="container.itemSelected.emit($event)"
3113
+ />
2959
3114
  } @else {
2960
3115
  <NavigationList
2961
3116
  [navId]="navState.id"
@@ -2969,9 +3124,10 @@ class NavigationContentComponent {
2969
3124
  [collapseTree]="container.collapseTree()"
2970
3125
  [openedIds]="container.openedIds()"
2971
3126
  (openedIdsChange)="container.openedIds.set($event)"
2972
- (itemSelected)="container.itemSelected.emit($event)" />
3127
+ (itemSelected)="container.itemSelected.emit($event)"
3128
+ />
2973
3129
  }
2974
- `, isInline: true, dependencies: [{ kind: "component", type: NavigationDockbarMenuComponent, selector: "NavigationDockbarMenu", inputs: ["navId", "items", "mode", "position", "itemClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationFlyoutMenuComponent, selector: "NavigationFlyoutMenu", inputs: ["navId", "items", "label", "icon", "iconOnly", "iconPosition", "position", "typeStyle", "compact", "itemClass", "groupClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationHorizontalComponent, selector: "NavigationHorizontal", inputs: ["navId", "items", "position", "typeStyle", "compact", "itemClass", "groupClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationListComponent, selector: "NavigationList", inputs: ["navId", "items", "collapsed", "compact", "position", "itemClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3130
+ `, isInline: true, dependencies: [{ kind: "component", type: NavigationDockbarMenuComponent, selector: "NavigationDockbarMenu", inputs: ["navId", "items", "mode", "position", "itemClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationFlyoutMenuComponent, selector: "NavigationFlyoutMenu", inputs: ["navId", "items", "label", "icon", "iconOnly", "iconPosition", "variant", "floating", "triggerClass", "position", "typeStyle", "compact", "itemClass", "groupClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationHorizontalComponent, selector: "NavigationHorizontal", inputs: ["navId", "items", "position", "typeStyle", "compact", "itemClass", "groupClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationListComponent, selector: "NavigationList", inputs: ["navId", "items", "collapsed", "compact", "position", "itemClass", "activeIds", "activeUrl", "iconTemplate", "collapseTree", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2975
3131
  }
2976
3132
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationContentComponent, decorators: [{
2977
3133
  type: Component,
@@ -2998,6 +3154,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2998
3154
  [icon]="container.flyoutIcon()"
2999
3155
  [iconOnly]="container.flyoutIconOnly()"
3000
3156
  [iconPosition]="container.flyoutIconPosition()"
3157
+ [variant]="container.flyoutTriggerVariant()"
3158
+ [floating]="container.flyoutTriggerFloating()"
3159
+ [triggerClass]="container.flyoutTriggerClass()"
3001
3160
  [position]="navState.position"
3002
3161
  [typeStyle]="container.typeStyle()"
3003
3162
  [compact]="container.compact()"
@@ -3009,7 +3168,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3009
3168
  [collapseTree]="container.collapseTree()"
3010
3169
  [openedIds]="container.openedIds()"
3011
3170
  (openedIdsChange)="container.openedIds.set($event)"
3012
- (itemSelected)="container.itemSelected.emit($event)" />
3171
+ (itemSelected)="container.itemSelected.emit($event)"
3172
+ >
3173
+ <ng-content select="NavigationHeader" ngProjectAs="NavigationHeader" />
3174
+ <ng-content select="NavigationFooter" ngProjectAs="NavigationFooter" />
3175
+ </NavigationFlyoutMenu>
3013
3176
  } @else if (navState.orientation === 'horizontal') {
3014
3177
  <NavigationHorizontal
3015
3178
  [navId]="navState.id"
@@ -3025,7 +3188,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3025
3188
  [collapseTree]="container.collapseTree()"
3026
3189
  [openedIds]="container.openedIds()"
3027
3190
  (openedIdsChange)="container.openedIds.set($event)"
3028
- (itemSelected)="container.itemSelected.emit($event)" />
3191
+ (itemSelected)="container.itemSelected.emit($event)"
3192
+ />
3029
3193
  } @else if (navState.type === 'dockbar') {
3030
3194
  <NavigationDockbarMenu
3031
3195
  [navId]="navState.id"
@@ -3039,7 +3203,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3039
3203
  [collapseTree]="container.collapseTree()"
3040
3204
  [openedIds]="container.openedIds()"
3041
3205
  (openedIdsChange)="container.openedIds.set($event)"
3042
- (itemSelected)="container.itemSelected.emit($event)" />
3206
+ (itemSelected)="container.itemSelected.emit($event)"
3207
+ />
3043
3208
  } @else {
3044
3209
  <NavigationList
3045
3210
  [navId]="navState.id"
@@ -3053,7 +3218,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3053
3218
  [collapseTree]="container.collapseTree()"
3054
3219
  [openedIds]="container.openedIds()"
3055
3220
  (openedIdsChange)="container.openedIds.set($event)"
3056
- (itemSelected)="container.itemSelected.emit($event)" />
3221
+ (itemSelected)="container.itemSelected.emit($event)"
3222
+ />
3057
3223
  }
3058
3224
  `,
3059
3225
  }]
@@ -3070,7 +3236,7 @@ class NavigationFooterComponent {
3070
3236
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3071
3237
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3072
3238
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: NavigationFooterComponent, isStandalone: true, selector: "NavigationFooter", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
3073
- <div class="flex h-full items-center">
3239
+ <div class="flex h-full min-w-0 items-center gap-3">
3074
3240
  <ng-content />
3075
3241
  </div>
3076
3242
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
@@ -3084,7 +3250,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3084
3250
  '[class]': 'hostClasses()',
3085
3251
  },
3086
3252
  template: `
3087
- <div class="flex h-full items-center">
3253
+ <div class="flex h-full min-w-0 items-center gap-3">
3088
3254
  <ng-content />
3089
3255
  </div>
3090
3256
  `,
@@ -3109,11 +3275,26 @@ class NavigationHeaderComponent {
3109
3275
  return this.toggle() || (this.shell.collapseEnabled() && !this.displayCollapsed());
3110
3276
  }, /* @ts-ignore */
3111
3277
  ...(ngDevMode ? [{ debugName: "showToggle" }] : /* istanbul ignore next */ []));
3112
- toggleAriaLabel = computed(() => (this.collapsed() ? 'Expand navigation' : 'Collapse navigation'), /* @ts-ignore */
3278
+ toggleAriaLabel = computed(() => this.collapsed() ? 'Expand navigation' : 'Collapse navigation', /* @ts-ignore */
3113
3279
  ...(ngDevMode ? [{ debugName: "toggleAriaLabel" }] : /* istanbul ignore next */ []));
3114
- toggleIconName = computed(() => (this.collapsed() ? 'left_panel_open' : 'left_panel_close'), /* @ts-ignore */
3280
+ toggleIconName = computed(() => this.collapsed() ? 'left_panel_open' : 'left_panel_close', /* @ts-ignore */
3115
3281
  ...(ngDevMode ? [{ debugName: "toggleIconName" }] : /* istanbul ignore next */ []));
3116
- contentClasses = computed(() => (this.isHorizontal() ? 'min-w-0' : 'min-w-0 flex-1'), /* @ts-ignore */
3282
+ /**
3283
+ * Baris isi header. Saat collapsed/dockbar (vertical) isi (inisial/ikon) dipusatkan di tengah
3284
+ * rail agar simetris dengan ikon nav-item di bawahnya (yang juga center di rail); selain itu kiri
3285
+ * dengan padding `px-3`.
3286
+ */
3287
+ rowClasses = computed(() => cn('flex h-full items-center', !this.isHorizontal() && this.displayCollapsed() ? 'justify-center' : 'gap-3 px-3'), /* @ts-ignore */
3288
+ ...(ngDevMode ? [{ debugName: "rowClasses" }] : /* istanbul ignore next */ []));
3289
+ contentClasses = computed(() => {
3290
+ if (this.isHorizontal()) {
3291
+ return 'flex min-w-0 items-center gap-3';
3292
+ }
3293
+ // Collapsed/dockbar: inisial center (tanpa `flex-1` yang membuatnya rata kiri).
3294
+ return this.displayCollapsed()
3295
+ ? 'flex min-w-0 items-center justify-center'
3296
+ : 'flex min-w-0 flex-1 items-center gap-3';
3297
+ }, /* @ts-ignore */
3117
3298
  ...(ngDevMode ? [{ debugName: "contentClasses" }] : /* istanbul ignore next */ []));
3118
3299
  hostClasses = computed(() => cn(this.isHorizontal()
3119
3300
  ? 'relative z-10 block h-full w-auto shrink-0'
@@ -3124,7 +3305,7 @@ class NavigationHeaderComponent {
3124
3305
  }
3125
3306
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3126
3307
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationHeaderComponent, isStandalone: true, selector: "NavigationHeader", inputs: { toggle: { classPropertyName: "toggle", publicName: "toggle", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "attr.data-collapsed": "displayCollapsed()" } }, ngImport: i0, template: `
3127
- <div class="flex h-full items-center gap-3 px-3">
3308
+ <div [class]="rowClasses()">
3128
3309
  <div [class]="contentClasses()">
3129
3310
  <ng-content />
3130
3311
  </div>
@@ -3136,7 +3317,8 @@ class NavigationHeaderComponent {
3136
3317
  [attr.aria-label]="toggleAriaLabel()"
3137
3318
  [attr.title]="toggleAriaLabel()"
3138
3319
  data-navigation-header-toggle="true"
3139
- (click)="toggleCollapsed()">
3320
+ (click)="toggleCollapsed()"
3321
+ >
3140
3322
  <Icon data-navigation-header-toggle-icon="true" [name]="toggleIconName()" [size]="18" />
3141
3323
  </button>
3142
3324
  }
@@ -3154,7 +3336,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3154
3336
  '[attr.data-collapsed]': 'displayCollapsed()',
3155
3337
  },
3156
3338
  template: `
3157
- <div class="flex h-full items-center gap-3 px-3">
3339
+ <div [class]="rowClasses()">
3158
3340
  <div [class]="contentClasses()">
3159
3341
  <ng-content />
3160
3342
  </div>
@@ -3166,7 +3348,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3166
3348
  [attr.aria-label]="toggleAriaLabel()"
3167
3349
  [attr.title]="toggleAriaLabel()"
3168
3350
  data-navigation-header-toggle="true"
3169
- (click)="toggleCollapsed()">
3351
+ (click)="toggleCollapsed()"
3352
+ >
3170
3353
  <Icon data-navigation-header-toggle-icon="true" [name]="toggleIconName()" [size]="18" />
3171
3354
  </button>
3172
3355
  }
@@ -3340,7 +3523,7 @@ class NavigationNavbarComponent {
3340
3523
  destroyRef = inject(DestroyRef);
3341
3524
  /** Posisi bar terhadap konten layout: `top` (default) atau `bottom` — panel grid membuka ke arah sebaliknya. */
3342
3525
  position = input(null, { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'nav-position' });
3343
- typeStyle = input('default', { ...(ngDevMode ? { debugName: "typeStyle" } : /* istanbul ignore next */ {}), alias: 'nav-type-style' });
3526
+ typeStyle = input('flat', { ...(ngDevMode ? { debugName: "typeStyle" } : /* istanbul ignore next */ {}), alias: 'nav-appearance' });
3344
3527
  class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3345
3528
  headerSlot = contentChild(NavigationHeaderComponent, /* @ts-ignore */
3346
3529
  ...(ngDevMode ? [{ debugName: "headerSlot" }] : /* istanbul ignore next */ []));
@@ -3361,7 +3544,7 @@ class NavigationNavbarComponent {
3361
3544
  this.destroyRef.onDestroy(() => this.container.unregisterType(config));
3362
3545
  }
3363
3546
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationNavbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3364
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationNavbarComponent, isStandalone: true, selector: "NavigationNavbar", inputs: { position: { classPropertyName: "position", publicName: "nav-position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "nav-type-style", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, queries: [{ propertyName: "headerSlot", first: true, predicate: NavigationHeaderComponent, descendants: true, isSignal: true }, { propertyName: "contentSlot", first: true, predicate: NavigationContentComponent, descendants: true, isSignal: true }, { propertyName: "footerSlot", first: true, predicate: NavigationFooterComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
3547
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationNavbarComponent, isStandalone: true, selector: "NavigationNavbar", inputs: { position: { classPropertyName: "position", publicName: "nav-position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "nav-appearance", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, queries: [{ propertyName: "headerSlot", first: true, predicate: NavigationHeaderComponent, descendants: true, isSignal: true }, { propertyName: "contentSlot", first: true, predicate: NavigationContentComponent, descendants: true, isSignal: true }, { propertyName: "footerSlot", first: true, predicate: NavigationFooterComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
3365
3548
  @if (headerSlot()) {
3366
3549
  <ng-content select="NavigationHeader" />
3367
3550
  }
@@ -3402,7 +3585,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3402
3585
  }
3403
3586
  `,
3404
3587
  }]
3405
- }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-type-style", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], headerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationHeaderComponent), { isSignal: true }] }], contentSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationContentComponent), { isSignal: true }] }], footerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationFooterComponent), { isSignal: true }] }] } });
3588
+ }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-appearance", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], headerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationHeaderComponent), { isSignal: true }] }], contentSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationContentComponent), { isSignal: true }] }], footerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationFooterComponent), { isSignal: true }] }] } });
3406
3589
 
3407
3590
  /**
3408
3591
  * Type flyout horizontal (trigger + panel menu) untuk `<Navigation>`.
@@ -3421,16 +3604,25 @@ class NavigationFlyoutComponent {
3421
3604
  iconOnly = input(false, { ...(ngDevMode ? { debugName: "iconOnly" } : /* istanbul ignore next */ {}), alias: 'icon-only', transform: booleanAttribute });
3422
3605
  /** Penempatan ikon relatif terhadap label: `start` (default) atau `end`. */
3423
3606
  iconPosition = input('start', { ...(ngDevMode ? { debugName: "iconPosition" } : /* istanbul ignore next */ {}), alias: 'icon-position' });
3607
+ /** Bentuk visual tombol trigger: `button` (pil, default) | `link` | `plain` (ghost). */
3608
+ triggerVariant = input('button', { ...(ngDevMode ? { debugName: "triggerVariant" } : /* istanbul ignore next */ {}), alias: 'trigger-variant' });
3609
+ /**
3610
+ * Trigger mengambang (`fixed`) alih-alih mengalir di dalam bar/div. Default pojok kanan-atas;
3611
+ * pakai `trigger-class` untuk pindah pojok (mis. `bottom-6 right-6 top-auto`).
3612
+ *
3613
+ * Catatan: `position: fixed` ber-anchor ke viewport HANYA bila tak ada ancestor pembentuk
3614
+ * containing block. Di dalam `<Layout>` (frame-nya memakai `backdrop-blur`), trigger ber-anchor
3615
+ * ke kotak konten frame (bukan viewport sebenarnya) dan bisa ter-clip oleh frame `overflow-hidden`.
3616
+ * Untuk floating sebenar-viewport, tempatkan `<Navigation>` flyout di luar frame `<Layout>`.
3617
+ */
3618
+ triggerFloating = input(false, { ...(ngDevMode ? { debugName: "triggerFloating" } : /* istanbul ignore next */ {}), alias: 'trigger-floating',
3619
+ transform: booleanAttribute });
3620
+ /** Kelas Tailwind untuk host trigger (posisi pojok floating / styling wrapper saat in-flow). */
3621
+ triggerClass = input('', { ...(ngDevMode ? { debugName: "triggerClass" } : /* istanbul ignore next */ {}), alias: 'trigger-class' });
3424
3622
  /** Posisi bar terhadap konten layout: `top` (default) atau `bottom` — panel membuka ke arah sebaliknya. */
3425
3623
  position = input(null, { ...(ngDevMode ? { debugName: "position" } : /* istanbul ignore next */ {}), alias: 'nav-position' });
3426
- typeStyle = input('default', { ...(ngDevMode ? { debugName: "typeStyle" } : /* istanbul ignore next */ {}), alias: 'nav-type-style' });
3624
+ typeStyle = input('flat', { ...(ngDevMode ? { debugName: "typeStyle" } : /* istanbul ignore next */ {}), alias: 'nav-appearance' });
3427
3625
  class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3428
- headerSlot = contentChild(NavigationHeaderComponent, /* @ts-ignore */
3429
- ...(ngDevMode ? [{ debugName: "headerSlot" }] : /* istanbul ignore next */ []));
3430
- contentSlot = contentChild(NavigationContentComponent, /* @ts-ignore */
3431
- ...(ngDevMode ? [{ debugName: "contentSlot" }] : /* istanbul ignore next */ []));
3432
- footerSlot = contentChild(NavigationFooterComponent, /* @ts-ignore */
3433
- ...(ngDevMode ? [{ debugName: "footerSlot" }] : /* istanbul ignore next */ []));
3434
3626
  hostClasses = computed(() => cn(this.container.shellClasses(), this.class()), /* @ts-ignore */
3435
3627
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3436
3628
  constructor() {
@@ -3442,26 +3634,24 @@ class NavigationFlyoutComponent {
3442
3634
  flyoutIcon: this.icon,
3443
3635
  flyoutIconOnly: this.iconOnly,
3444
3636
  flyoutIconPosition: this.iconPosition,
3637
+ flyoutTriggerVariant: this.triggerVariant,
3638
+ flyoutTriggerFloating: this.triggerFloating,
3639
+ flyoutTriggerClass: this.triggerClass,
3445
3640
  typeStyle: this.typeStyle,
3446
3641
  };
3447
3642
  this.container.registerType(config);
3448
3643
  this.destroyRef.onDestroy(() => this.container.unregisterType(config));
3449
3644
  }
3450
3645
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFlyoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3451
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationFlyoutComponent, isStandalone: true, selector: "NavigationFlyout", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "icon-only", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "icon-position", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "nav-position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "nav-type-style", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, queries: [{ propertyName: "headerSlot", first: true, predicate: NavigationHeaderComponent, descendants: true, isSignal: true }, { propertyName: "contentSlot", first: true, predicate: NavigationContentComponent, descendants: true, isSignal: true }, { propertyName: "footerSlot", first: true, predicate: NavigationFooterComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
3452
- @if (headerSlot()) {
3453
- <ng-content select="NavigationHeader" />
3454
- }
3455
-
3456
- @if (contentSlot()) {
3457
- <ng-content select="NavigationContent" />
3458
- } @else {
3459
- <NavigationContent />
3460
- }
3461
-
3462
- @if (footerSlot()) {
3463
- <ng-content select="NavigationFooter" />
3464
- }
3646
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: NavigationFlyoutComponent, isStandalone: true, selector: "NavigationFlyout", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "icon-only", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "icon-position", isSignal: true, isRequired: false, transformFunction: null }, triggerVariant: { classPropertyName: "triggerVariant", publicName: "trigger-variant", isSignal: true, isRequired: false, transformFunction: null }, triggerFloating: { classPropertyName: "triggerFloating", publicName: "trigger-floating", isSignal: true, isRequired: false, transformFunction: null }, triggerClass: { classPropertyName: "triggerClass", publicName: "trigger-class", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "nav-position", isSignal: true, isRequired: false, transformFunction: null }, typeStyle: { classPropertyName: "typeStyle", publicName: "nav-appearance", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
3647
+ <!-- Bar hanya berisi trigger (dari NavigationContent → FlyoutMenu); NavigationHeader &
3648
+ NavigationFooter di-re-project ke DALAM panel flyout saat dibuka. Urutan dibaca dari sisi
3649
+ bar ke arah luar: header (menempel bar, sticky) → menu → footer (ujung jauh). Mode top:
3650
+ brand atas, logout bawah; mode bottom: cerminannya (brand bawah dekat bar, logout atas). -->
3651
+ <NavigationContent>
3652
+ <ng-content select="NavigationHeader" ngProjectAs="NavigationHeader" />
3653
+ <ng-content select="NavigationFooter" ngProjectAs="NavigationFooter" />
3654
+ </NavigationContent>
3465
3655
  `, isInline: true, dependencies: [{ kind: "component", type: NavigationContentComponent, selector: "NavigationContent", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3466
3656
  }
3467
3657
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFlyoutComponent, decorators: [{
@@ -3474,22 +3664,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3474
3664
  '[class]': 'hostClasses()',
3475
3665
  },
3476
3666
  template: `
3477
- @if (headerSlot()) {
3478
- <ng-content select="NavigationHeader" />
3479
- }
3480
-
3481
- @if (contentSlot()) {
3482
- <ng-content select="NavigationContent" />
3483
- } @else {
3484
- <NavigationContent />
3485
- }
3486
-
3487
- @if (footerSlot()) {
3488
- <ng-content select="NavigationFooter" />
3489
- }
3667
+ <!-- Bar hanya berisi trigger (dari NavigationContent → FlyoutMenu); NavigationHeader &
3668
+ NavigationFooter di-re-project ke DALAM panel flyout saat dibuka. Urutan dibaca dari sisi
3669
+ bar ke arah luar: header (menempel bar, sticky) → menu → footer (ujung jauh). Mode top:
3670
+ brand atas, logout bawah; mode bottom: cerminannya (brand bawah dekat bar, logout atas). -->
3671
+ <NavigationContent>
3672
+ <ng-content select="NavigationHeader" ngProjectAs="NavigationHeader" />
3673
+ <ng-content select="NavigationFooter" ngProjectAs="NavigationFooter" />
3674
+ </NavigationContent>
3490
3675
  `,
3491
3676
  }]
3492
- }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon-only", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon-position", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-type-style", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], headerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationHeaderComponent), { isSignal: true }] }], contentSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationContentComponent), { isSignal: true }] }], footerSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NavigationFooterComponent), { isSignal: true }] }] } });
3677
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon-only", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon-position", required: false }] }], triggerVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger-variant", required: false }] }], triggerFloating: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger-floating", required: false }] }], triggerClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger-class", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-position", required: false }] }], typeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "nav-appearance", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3493
3678
 
3494
3679
  /**
3495
3680
  * Tampilan compact aktif saat sidebar collapsible sedang collapsed, atau saat
@@ -3560,8 +3745,270 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3560
3745
  }]
3561
3746
  }], ctorParameters: () => [] });
3562
3747
 
3748
+ /**
3749
+ * Slot aksi di sisi kanan `NavigationFooter`. Selalu didorong ke kanan (`ml-auto`),
3750
+ * rata tengah vertikal, dengan jarak antar-child otomatis (`gap`). Isi bebas dengan
3751
+ * ikon, button, atau teks — mis. tombol logout.
3752
+ *
3753
+ * Otomatis disembunyikan saat sidebar vertical sedang collapsed (rail icon-only)
3754
+ * agar tidak overflow; konsisten dengan `NavigationFooterTitle`.
3755
+ */
3756
+ class NavigationFooterActionComponent {
3757
+ shell = inject(NAVIGATION_SHELL, { optional: true });
3758
+ class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3759
+ collapsed = computed(() => {
3760
+ const state = this.shell?.displayState();
3761
+ return state ? state.orientation === 'vertical' && state.collapsed : false;
3762
+ }, /* @ts-ignore */
3763
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
3764
+ hostClasses = computed(() => cn(this.collapsed() ? 'hidden' : 'ml-auto flex shrink-0 items-center gap-1', this.class()), /* @ts-ignore */
3765
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3766
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterActionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3767
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: NavigationFooterActionComponent, isStandalone: true, selector: "NavigationFooterAction", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3768
+ }
3769
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterActionComponent, decorators: [{
3770
+ type: Component,
3771
+ args: [{
3772
+ selector: 'NavigationFooterAction',
3773
+ changeDetection: ChangeDetectionStrategy.OnPush,
3774
+ host: {
3775
+ '[class]': 'hostClasses()',
3776
+ },
3777
+ template: `<ng-content />`,
3778
+ }]
3779
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3780
+
3781
+ /**
3782
+ * Turunkan inisial ringkas dari sebuah nama untuk avatar fallback.
3783
+ *
3784
+ * "Ojie Permana" → "OP"
3785
+ * "Ojiepermana" → "OJ"
3786
+ * "" → ""
3787
+ *
3788
+ * Mengambil huruf depan kata pertama dan kata terakhir; bila hanya satu kata,
3789
+ * memakai dua huruf pertamanya.
3790
+ */
3791
+ function deriveNavigationInitials(name) {
3792
+ const parts = name.trim().split(/\s+/).filter(Boolean);
3793
+ if (parts.length === 0) {
3794
+ return '';
3795
+ }
3796
+ const first = parts[0] ?? '';
3797
+ if (parts.length === 1) {
3798
+ return first.slice(0, 2).toUpperCase();
3799
+ }
3800
+ const last = parts[parts.length - 1] ?? '';
3801
+ return (first.charAt(0) + last.charAt(0)).toUpperCase();
3802
+ }
3803
+
3804
+ /**
3805
+ * Blok avatar/inisial untuk slot `NavigationFooter` (profil user). Sama seperti
3806
+ * `NavigationHeaderInitial` tetapi default-nya bulat (rounded-full) sesuai
3807
+ * konvensi avatar user. Consumer cukup memberi `name`; inisial diturunkan
3808
+ * otomatis. Beri `src` untuk foto, `icon` untuk material symbol, atau `initials`
3809
+ * untuk override.
3810
+ *
3811
+ * Tetap tampil saat sidebar collapsed; pasangannya `NavigationFooterTitle` yang
3812
+ * menyembunyikan diri.
3813
+ */
3814
+ class NavigationFooterInitialComponent {
3815
+ name = input('', /* @ts-ignore */
3816
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3817
+ src = input(null, /* @ts-ignore */
3818
+ ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
3819
+ initials = input(null, /* @ts-ignore */
3820
+ ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
3821
+ icon = input(null, /* @ts-ignore */
3822
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
3823
+ class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3824
+ resolvedInitials = computed(() => this.initials()?.slice(0, 2).toUpperCase() || deriveNavigationInitials(this.name()), /* @ts-ignore */
3825
+ ...(ngDevMode ? [{ debugName: "resolvedInitials" }] : /* istanbul ignore next */ []));
3826
+ hostClasses = computed(() => cn('inline-flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary/10 text-primary', this.class()), /* @ts-ignore */
3827
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterInitialComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3829
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationFooterInitialComponent, isStandalone: true, selector: "NavigationFooterInitial", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "attr.role": "name() ? 'img' : null", "attr.aria-label": "name() || null" } }, ngImport: i0, template: `
3830
+ @if (src(); as source) {
3831
+ <img [src]="source" alt="" class="h-full w-full rounded-[inherit] object-cover" />
3832
+ } @else if (icon(); as glyph) {
3833
+ <Icon [name]="glyph" [size]="18" />
3834
+ } @else {
3835
+ <span class="text-xs font-semibold leading-none">{{ resolvedInitials() }}</span>
3836
+ }
3837
+ `, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "Icon", inputs: ["name", "class", "size", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3838
+ }
3839
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterInitialComponent, decorators: [{
3840
+ type: Component,
3841
+ args: [{
3842
+ selector: 'NavigationFooterInitial',
3843
+ changeDetection: ChangeDetectionStrategy.OnPush,
3844
+ imports: [IconComponent],
3845
+ host: {
3846
+ '[class]': 'hostClasses()',
3847
+ '[attr.role]': "name() ? 'img' : null",
3848
+ '[attr.aria-label]': 'name() || null',
3849
+ },
3850
+ template: `
3851
+ @if (src(); as source) {
3852
+ <img [src]="source" alt="" class="h-full w-full rounded-[inherit] object-cover" />
3853
+ } @else if (icon(); as glyph) {
3854
+ <Icon [name]="glyph" [size]="18" />
3855
+ } @else {
3856
+ <span class="text-xs font-semibold leading-none">{{ resolvedInitials() }}</span>
3857
+ }
3858
+ `,
3859
+ }]
3860
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3861
+
3862
+ /**
3863
+ * Judul + subjudul untuk slot `NavigationFooter` (mis. nama + email user).
3864
+ * Otomatis disembunyikan saat sidebar vertical sedang collapsed (rail icon-only)
3865
+ * dengan membaca `NAVIGATION_SHELL`.
3866
+ */
3867
+ class NavigationFooterTitleComponent {
3868
+ shell = inject(NAVIGATION_SHELL, { optional: true });
3869
+ title = input('', /* @ts-ignore */
3870
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
3871
+ subtitle = input(null, /* @ts-ignore */
3872
+ ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
3873
+ class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3874
+ collapsed = computed(() => {
3875
+ const state = this.shell?.displayState();
3876
+ return state ? state.orientation === 'vertical' && state.collapsed : false;
3877
+ }, /* @ts-ignore */
3878
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
3879
+ hostClasses = computed(() => cn(this.collapsed() ? 'hidden' : 'flex min-w-0 flex-col justify-center', this.class()), /* @ts-ignore */
3880
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3881
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterTitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3882
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationFooterTitleComponent, isStandalone: true, selector: "NavigationFooterTitle", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "attr.data-collapsed": "collapsed()" } }, ngImport: i0, template: `
3883
+ <span class="block truncate text-sm font-semibold leading-tight text-foreground">{{ title() }}</span>
3884
+ @if (subtitle(); as sub) {
3885
+ <span class="block truncate text-xs leading-tight text-muted-foreground">{{ sub }}</span>
3886
+ }
3887
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3888
+ }
3889
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationFooterTitleComponent, decorators: [{
3890
+ type: Component,
3891
+ args: [{
3892
+ selector: 'NavigationFooterTitle',
3893
+ changeDetection: ChangeDetectionStrategy.OnPush,
3894
+ host: {
3895
+ '[class]': 'hostClasses()',
3896
+ '[attr.data-collapsed]': 'collapsed()',
3897
+ },
3898
+ template: `
3899
+ <span class="block truncate text-sm font-semibold leading-tight text-foreground">{{ title() }}</span>
3900
+ @if (subtitle(); as sub) {
3901
+ <span class="block truncate text-xs leading-tight text-muted-foreground">{{ sub }}</span>
3902
+ }
3903
+ `,
3904
+ }]
3905
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3906
+
3907
+ /**
3908
+ * Blok avatar/inisial untuk slot `NavigationHeader` (brand/logo). Consumer cukup
3909
+ * memberi `name`; inisial diturunkan otomatis ("Ojie Permana" → "OP"). Beri `src`
3910
+ * untuk gambar, `icon` (material symbol) sebagai ganti inisial, atau `initials`
3911
+ * untuk override manual.
3912
+ *
3913
+ * Selalu tampil baik saat sidebar expanded maupun collapsed karena ukurannya muat
3914
+ * di rail icon-only — pasangannya, `NavigationHeaderTitle`, yang menyembunyikan
3915
+ * diri saat collapsed.
3916
+ */
3917
+ class NavigationHeaderInitialComponent {
3918
+ name = input('', /* @ts-ignore */
3919
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3920
+ src = input(null, /* @ts-ignore */
3921
+ ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
3922
+ initials = input(null, /* @ts-ignore */
3923
+ ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
3924
+ icon = input(null, /* @ts-ignore */
3925
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
3926
+ class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3927
+ resolvedInitials = computed(() => this.initials()?.slice(0, 2).toUpperCase() || deriveNavigationInitials(this.name()), /* @ts-ignore */
3928
+ ...(ngDevMode ? [{ debugName: "resolvedInitials" }] : /* istanbul ignore next */ []));
3929
+ hostClasses = computed(() => cn('inline-flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md bg-primary/10 text-primary', this.class()), /* @ts-ignore */
3930
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3931
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHeaderInitialComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3932
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationHeaderInitialComponent, isStandalone: true, selector: "NavigationHeaderInitial", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "attr.role": "name() ? 'img' : null", "attr.aria-label": "name() || null" } }, ngImport: i0, template: `
3933
+ @if (src(); as source) {
3934
+ <img [src]="source" alt="" class="h-full w-full rounded-[inherit] object-cover" />
3935
+ } @else if (icon(); as glyph) {
3936
+ <Icon [name]="glyph" [size]="18" />
3937
+ } @else {
3938
+ <span class="text-xs font-semibold leading-none">{{ resolvedInitials() }}</span>
3939
+ }
3940
+ `, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "Icon", inputs: ["name", "class", "size", "fill", "weight", "grade", "opticalSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3941
+ }
3942
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHeaderInitialComponent, decorators: [{
3943
+ type: Component,
3944
+ args: [{
3945
+ selector: 'NavigationHeaderInitial',
3946
+ changeDetection: ChangeDetectionStrategy.OnPush,
3947
+ imports: [IconComponent],
3948
+ host: {
3949
+ '[class]': 'hostClasses()',
3950
+ '[attr.role]': "name() ? 'img' : null",
3951
+ '[attr.aria-label]': 'name() || null',
3952
+ },
3953
+ template: `
3954
+ @if (src(); as source) {
3955
+ <img [src]="source" alt="" class="h-full w-full rounded-[inherit] object-cover" />
3956
+ } @else if (icon(); as glyph) {
3957
+ <Icon [name]="glyph" [size]="18" />
3958
+ } @else {
3959
+ <span class="text-xs font-semibold leading-none">{{ resolvedInitials() }}</span>
3960
+ }
3961
+ `,
3962
+ }]
3963
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3964
+
3965
+ /**
3966
+ * Judul + subjudul untuk slot `NavigationHeader`. Otomatis disembunyikan saat
3967
+ * sidebar vertical sedang collapsed (rail icon-only) dengan membaca
3968
+ * `NAVIGATION_SHELL` — consumer tidak perlu mengelola state collapse sendiri.
3969
+ */
3970
+ class NavigationHeaderTitleComponent {
3971
+ shell = inject(NAVIGATION_SHELL, { optional: true });
3972
+ title = input('', /* @ts-ignore */
3973
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
3974
+ subtitle = input(null, /* @ts-ignore */
3975
+ ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
3976
+ class = input('', { ...(ngDevMode ? { debugName: "class" } : /* istanbul ignore next */ {}), alias: 'class' });
3977
+ collapsed = computed(() => {
3978
+ const state = this.shell?.displayState();
3979
+ return state ? state.orientation === 'vertical' && state.collapsed : false;
3980
+ }, /* @ts-ignore */
3981
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
3982
+ hostClasses = computed(() => cn(this.collapsed() ? 'hidden' : 'flex min-w-0 flex-col justify-center', this.class()), /* @ts-ignore */
3983
+ ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
3984
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHeaderTitleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3985
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: NavigationHeaderTitleComponent, isStandalone: true, selector: "NavigationHeaderTitle", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()", "attr.data-collapsed": "collapsed()" } }, ngImport: i0, template: `
3986
+ <span class="block truncate text-sm font-semibold leading-tight text-foreground">{{ title() }}</span>
3987
+ @if (subtitle(); as sub) {
3988
+ <span class="block truncate text-xs leading-tight text-muted-foreground">{{ sub }}</span>
3989
+ }
3990
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3991
+ }
3992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: NavigationHeaderTitleComponent, decorators: [{
3993
+ type: Component,
3994
+ args: [{
3995
+ selector: 'NavigationHeaderTitle',
3996
+ changeDetection: ChangeDetectionStrategy.OnPush,
3997
+ host: {
3998
+ '[class]': 'hostClasses()',
3999
+ '[attr.data-collapsed]': 'collapsed()',
4000
+ },
4001
+ template: `
4002
+ <span class="block truncate text-sm font-semibold leading-tight text-foreground">{{ title() }}</span>
4003
+ @if (subtitle(); as sub) {
4004
+ <span class="block truncate text-xs leading-tight text-muted-foreground">{{ sub }}</span>
4005
+ }
4006
+ `,
4007
+ }]
4008
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4009
+
3563
4010
  /**
3564
4011
  * Generated bundle index. Do not edit.
3565
4012
  */
3566
4013
 
3567
- export { NavigationCollapseExpandedDirective, NavigationCollapseRootDirective, NavigationContainerComponent, NavigationContentComponent, NavigationDockbarComponent, NavigationFlyoutComponent, NavigationFooterComponent, NavigationHeaderComponent, NavigationIconDirective, NavigationNavbarComponent, NavigationSidebarComponent, normalizeUiNavItems };
4014
+ export { NavigationCollapseExpandedDirective, NavigationCollapseRootDirective, NavigationContainerComponent, NavigationContentComponent, NavigationDockbarComponent, NavigationFlyoutComponent, NavigationFooterActionComponent, NavigationFooterComponent, NavigationFooterInitialComponent, NavigationFooterTitleComponent, NavigationHeaderComponent, NavigationHeaderInitialComponent, NavigationHeaderTitleComponent, NavigationIconDirective, NavigationNavbarComponent, NavigationSidebarComponent, deriveNavigationInitials, normalizeUiNavItems };