# Navigation

`@ojiepermana/angular-navigation` renders input-driven navigation with configurable types and instance-aware state.

```ts
import {
  NavigationContainerComponent,
  NavigationSidebarComponent, // type: NavigationDockbar/Navbar/FlyoutComponent
  NavigationHeaderComponent,
  NavigationContentComponent,
  NavigationFooterComponent,
  type NavigationItem,
} from '@ojiepermana/angular-navigation';
import { NavigationService } from '@ojiepermana/angular-navigation/service';

const items: NavigationItem[] = [
  { id: 'dashboard', title: 'Dashboard', link: '/dashboard', icon: 'dashboard' },
  {
    id: 'reports',
    title: 'Reports',
    type: 'collapsible',
    children: [{ id: 'monthly', title: 'Monthly', link: '/reports/monthly' }],
  },
];
```

```html
<Navigation id="erp-sidebar" [data]="items" class="h-full">
  <NavigationSidebar>
    <NavigationHeader class="px-1" [toggle]="true">
      <div class="text-sm font-semibold">Workspace</div>
    </NavigationHeader>

    <NavigationContent />

    <NavigationFooter class="p-3">
      <button type="button">Sign out</button>
    </NavigationFooter>
  </NavigationSidebar>
</Navigation>

<Navigation id="erp-topbar" [data]="items">
  <NavigationNavbar />
</Navigation>
```

Type dipilih lewat komponen anak deklaratif — `NavigationSidebar`, `NavigationDockbar`, `NavigationNavbar`, atau `NavigationFlyout` — satu type hidup per `<Navigation>`. Di dalam type, `NavigationHeader` dan `NavigationFooter` opsional, sedangkan `NavigationContent` selalu dirender (otomatis dibuat bila tidak diproyeksikan).

## API Notes

- `data` is the source of navigation data.
- Type is declared by the child component: `NavigationSidebar`/`NavigationDockbar` (vertical), `NavigationNavbar`/`NavigationFlyout` (horizontal). Orientation is implied by the type child.
- `id` identifies a navigation instance so multiple navs can keep distinct service state.
- `data-id` optionally publishes the input data under a different registry id. This lets a
  mobile mirror use isolated state (`id="main-mobile"`) while exposing the same data contract
  as the desktop navigation (`data-id="main"`).
- `position` on `NavigationSidebar`/`NavigationDockbar` accepts `left` or `right` (vertical).
- `collapsed` on `NavigationSidebar` controls the icon-only sidebar state.
- `mode` on `NavigationDockbar` accepts `sticky` or `drawer`.
- `dockbar-route-context` on `NavigationDockbar` opts into Router-aware header, rail deduplication,
  and sticky-panel restore behavior. Pass `true` for the default Angular Router adapter or a
  `NavigationDockbarRouteConfig` to override URL, metadata, or the fallback icon.
- `dockbar-badge` on `NavigationDockbar` accepts a generic badge registry keyed by
  `NavigationItem.badge.source` or route `data.navigationBadge`.
- `ariaLabel` overrides the host navigation label and defaults to `Primary navigation`.
- `label` on `NavigationFlyout` sets the single trigger label and defaults to `Menu`.
- `icon` on `NavigationFlyout` renders a Material Symbols icon on the trigger; `icon-position` (`start`/`end`) sets its placement and `icon-only` hides the label visually while keeping it as the accessible name (compact mode is always icon-only and falls back to the `menu` icon).
- `nav-position` on `NavigationNavbar`/`NavigationFlyout` accepts `top` (default) or `bottom`. With `bottom`, the navbar grid panel opens upward with squared bottom corners, and the flyout panel anchors to the container's bottom edge with the tab row docked at the bottom — seamless for bars placed below the content.
- `nav-appearance` on `NavigationNavbar`/`NavigationFlyout` accepts `flat` or `border-rail` (same vocabulary as the layout's `layout-appearance`). `border-rail` squares the panel (no rounding), frames entry icons with dimmed blueprint-style rail lines plus dashed center cross lines (instead of rounded boxes), renders the nested-collapse rail dashed, and replaces hover/active backgrounds with primary-colored text. `flat` keeps the current visuals.
- The `navbar` type renders root items inline immediately; clicking a group (hover does not open it) opens a full-width grid panel below the bar with the same flyout-style entries, responsive columns, and nested collapse. Root group triggers intentionally render without a chevron; open state remains available through the trigger styling and `aria-expanded`.
- The `flyout` type renders one trigger button that opens a floating panel overlaying the parent container exactly (same top position and width, covering the trigger with no visible gap; `display: contents` wrappers are skipped when measuring the container). A `trigger-floating` popover keeps the 22rem / 85vw default through `--nav-flyout-floating-width` and `--nav-flyout-floating-max-width`, while `--nav-flyout-floating-radius` can align its card with a containing shell. Use `trigger-class` only for the floating host/placement and `trigger-button-class` for trigger-specific styling; the latter never leaks into panel items. Add `panel-viewport` when window chrome needs the floating panel fixed to `top: 0` and both viewport edges; these options let desktop chrome place and shape the same interaction without changing the global default. The panel explicitly opts out of native window drag regions so its controls remain clickable inside desktop chrome. Root items with children become the horizontal navigation inside the panel (their `icon` renders in the row), their children render as title/subtitle entries in a responsive grid (1 column on mobile, 2 from `sm`, 3 from `md`, 4 from `lg` and up; `columns` caps the maximum at 1–4), and deeper children render as a vertical collapse below their entry — expanded by default every time the panel opens, collapsible per entry via the chevron, with the child icons aligned on the same vertical line as the parent entry icon. The panel closes on `Escape`, outside clicks, the close button, or leaf selection.
- Items with children render a `chevron_right` icon pinned at the right edge and vertically centered: it points right by default and rotates down (in place, no layout shift) only while open — hover does not move it. This applies to flyout tabs and entries plus collapsible items; navbar root branch triggers are the deliberate exception.
- Angular Router is supported for internal links through `link`, `queryParams`, `fragment`, `exactMatch`, and `isActiveMatchOptions`.
- External links can use `href` or `link` with `externalLink: true`.
- `activeIds` lets consumers force active state by nav id or normalized key.
- `activeUrl` lets consumers drive active matching without relying on the current router URL.
- `openedIds` owns collapsible state and supports two-way binding.
- `previewExpanded` on `NavigationSidebar` forces a collapsed sidebar instance to render in expanded-preview mode.
- `itemSelected` emits for action, router, and external items.
- `class` applies classes to the host `nav` element.
- `itemClass` applies shared classes to rendered items.
- `nav-group-class` (`groupClass`) applies Tailwind classes to the container of each horizontal group — the flyout tab `<li>` and the navbar group `<li>` — so consumers can tune height, padding, or borders of the group container. Per-group control is also available via `item.classes.container`.
- `compact` is still supported as a compatibility alias for collapsed vertical sidebars.
- `nav-sidebar-collapse` on `NavigationSidebar` enables hover-preview behavior for collapsed vertical sidebars.
- `collapse-tree` accepts `stairs` or `straight` for nested collapsible connector styling.
- `NavigationHeader` is optional, accepts Tailwind classes through `class`, and can render a built-in collapse toggle with `[toggle]="true"`.
- `NavigationFooter` is optional and accepts Tailwind classes through `class`.
- `NavigationContent` wraps the active navigation renderer and owns the scrollable region between the optional header and footer slots. It is rendered automatically by the type component when not projected, and accepts `class` when used explicitly.

## Nav Inputs And Outputs

Inputs pada `<Navigation>` (container):

| API               | Type                                               | Notes                                                                           |
| ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------- |
| `id`              | `string`                                           | Navigation instance id. `main` is the only id persisted to `localStorage`.      |
| `data-id`         | `string \| null`                                   | Optional data registry id; defaults to the normalized navigation `id`.          |
| `data`            | `readonly NavigationItem[]`                        | Source of navigation items.                                                     |
| `ariaLabel`       | `string`                                           | Accessible label on the host navigation landmark.                               |
| `compact`         | `boolean`                                          | Compatibility alias for collapsed-rail item layout.                             |
| `collapse-tree`   | `stairs \| straight`                               | Controls nested connector styling for collapsible items.                        |
| `class`           | `string`                                           | Extra classes for the host `nav`.                                               |
| `itemClass`       | `string`                                           | Shared classes applied to rendered nav items.                                   |
| `nav-group-class` | `string`                                           | Classes for each horizontal group container (flyout tab / navbar group `<li>`). |
| `activeIds`       | `ReadonlySet<string> \| readonly string[] \| null` | Explicit active state override by item id or normalized key.                    |
| `activeUrl`       | `string \| null`                                   | Explicit URL used during active matching.                                       |
| `openedIds`       | `readonly string[]`                                | Two-way bound open state for collapsible items.                                 |
| `itemSelected`    | `NavigationSelection`                              | Emits when a leaf item is selected.                                             |

Inputs pada komponen type:

| Component           | API                     | Type                                              | Notes                                                                         |
| ------------------- | ----------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------- |
| `NavigationSidebar` | `position`              | `left \| right \| null`                           | Side the rail docks to.                                                       |
| `NavigationSidebar` | `collapsed`             | `boolean \| null`                                 | Controls collapsed vertical sidebar state.                                    |
| `NavigationSidebar` | `nav-sidebar-collapse`  | `boolean`                                         | Enables hover-driven preview expansion on collapsed sidebars.                 |
| `NavigationSidebar` | `previewExpanded`       | `boolean`                                         | Forces a collapsed sidebar to render expanded for preview.                    |
| `NavigationDockbar` | `mode`                  | `sticky \| drawer \| null`                        | Docked aside beside the rail, or overlay drawer.                              |
| `NavigationDockbar` | `position`              | `left \| right \| null`                           | Side the rail docks to.                                                       |
| `NavigationDockbar` | `dockbar-route-context` | `boolean \| NavigationDockbarRouteConfig \| null` | Opt-in Router context and optional overrides.                                 |
| `NavigationDockbar` | `dockbar-badge`         | `NavigationBadgeContext`                          | Reactive badge sources keyed by item or route metadata.                       |
| `NavigationNavbar`  | `nav-appearance`        | `flat \| border-rail`                             | Style variant. `border-rail` = squared panel, dashed icon rails, no hover bg. |
| `NavigationFlyout`  | `label`                 | `string`                                          | Trigger label. Defaults to `Menu`.                                            |
| `NavigationFlyout`  | `nav-appearance`        | `flat \| border-rail`                             | Style variant. `border-rail` = squared panel, dashed icon rails, no hover bg. |

Semua type juga menerima `class` untuk kelas tambahan pada elemen shell-nya.

## NavigationItem

`NavigationItem` supports deeper trees, mixed item kinds, and both router and external navigation.

| Field                                                                | Type                                                                                         | Notes                                                                                                                       |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id`                                                                 | `string`                                                                                     | Stable item id. Reused for active/open state when present.                                                                  |
| `type`                                                               | `item \| basic \| aside \| group \| collapsible \| collapsable \| mega \| divider \| spacer` | `collapsable` is accepted as a compatibility alias of `collapsible`.                                                        |
| `title`, `subtitle`, `tooltip`                                       | `string`                                                                                     | Primary labels and optional compact tooltip.                                                                                |
| `active`, `disabled`                                                 | `boolean`                                                                                    | Explicit active and disabled flags.                                                                                         |
| `icon`                                                               | `string`                                                                                     | Rendered through the default icon slot unless overridden.                                                                   |
| `badge`                                                              | `{ title?: string; classes?: string; source?: string }`                                      | Static item label plus an optional dockbar badge-source key.                                                                |
| `classes`                                                            | `{ wrapper?, container?, icon?, title?, subtitle? }`                                         | Per-item class overrides. `container` targets the horizontal group `<li>` wrapper.                                          |
| `meta`                                                               | `Record<string, unknown>`                                                                    | Consumer-owned metadata bag.                                                                                                |
| `isHidden`                                                           | `(item) => boolean`                                                                          | Filters items at normalization time.                                                                                        |
| `link`                                                               | `string \| readonly unknown[] \| UrlTree`                                                    | Angular Router link target.                                                                                                 |
| `href`                                                               | `string`                                                                                     | Direct external URL.                                                                                                        |
| `queryParams`, `queryParamsHandling`, `fragment`, `preserveFragment` | Router options                                                                               | Extra Angular Router navigation options.                                                                                    |
| `externalLink`, `target`, `rel`                                      | link options                                                                                 | External navigation behavior. A `rel` is unioned with `noopener noreferrer` on `_blank`/external links, never replacing it. |
| `exactMatch`, `isActiveMatchOptions`                                 | Router match options                                                                         | Control how router activity is resolved.                                                                                    |
| `action`                                                             | `(item) => void`                                                                             | Callback for action-driven leaf items.                                                                                      |
| `columns`                                                            | `number`                                                                                     | Column count for `mega` style items.                                                                                        |
| `children`                                                           | `readonly NavigationItem[]`                                                                  | Nested items. Trees can be deeper than one level.                                                                           |

## Route-aware dockbar

The feature is opt-in so existing dockbar, sidebar, navbar, and flyout consumers keep their
current rendering. Once enabled, the library resolves the active root entry from the Angular
Router URL: either the root entry's own link or any nested descendant link can match.

```ts
import { signal } from '@angular/core';
import type {
  NavigationBadgeContext,
  NavigationItem,
  NavigationRouteData,
} from '@ojiepermana/angular-navigation';

export const notificationsRoute = {
  path: 'notifications',
  loadComponent: () => import('./notifications.page').then((module) => module.NotificationsPage),
  data: {
    navigationIcon: 'notifications',
    navigationBadge: 'unreadNotifications',
    navigationLabel: 'Notifications',
  } satisfies NavigationRouteData,
};

export class AppNavigation {
  readonly unreadNotifications = signal(0);

  readonly badgeContext: NavigationBadgeContext = {
    unreadNotifications: {
      value: this.unreadNotifications,
      ariaLabel: (count) => `${count} unread notifications`,
    },
  };

  readonly items: readonly NavigationItem[] = [
    {
      id: 'permission',
      title: 'Permission',
      icon: 'admin_panel_settings',
      children: [{ id: 'permission-groups', title: 'Groups', link: '/permission/group' }],
    },
  ];
}
```

```html
<Navigation id="main" [data]="items">
  <NavigationDockbar [dockbar-route-context]="true" [dockbar-badge]="badgeContext" />
</Navigation>
```

When a navigation root is active, the header uses that root's icon (or
`NAVIGATION_DOCKBAR_FALLBACK_ICON` when the root has no icon). When no navigation root matches,
it uses the deepest active route's `navigationIcon`, then the library fallback. When the built-in
route header is visible, its active root entry is structurally removed from the rail, preserving
every other entry's order without CSS hiding or an empty slot. Route metadata can still provide a
badge for an active navigation group when the item itself has no `badge.source`.

In sticky mode, clicking another root group opens that group's aside without changing the URL.
Moving the pointer anywhere inside the rail, header, aside, or footer keeps it open. Leaving the
complete dockbar restores the route's active group; if the route has no navigation group, it
closes the aside. Drawer Escape, backdrop, close-button, and focus-return behavior are unchanged.

`NavigationBadgeSource.value` accepts a number, Angular `Signal`, RxJS `Observable`, or callback.
Zero and invalid values are hidden, `1` through `99` render unchanged, and larger counts render
as `99+` while the configured accessible label receives the real count. Badge keys and labels are
domain-neutral.

A projected `NavigationHeader` continues to take precedence over the built-in route header. In
that case the library leaves the rail intact because it cannot infer whether custom projected
content renders the active icon. `LayoutNavDockbar` and `LayoutWrapperDefault` expose the same
`dockbar-route-context` and `dockbar-badge` pass-through inputs; enabling them replaces the compact
brand header with the built-in route header.

## Public Shell APIs

- `NavigationHeader` exposes `[toggle]` and `class`.
- `NavigationFooter` exposes `class`.
- `NavigationContent` exposes `class` and renders the active type menu; declare it explicitly to control slot order, or omit it for the automatic default.
- `[NavigationCollapseRoot]` adjusts layout for collapsible shell content when the sidebar rail is collapsed.
- `[NavigationCollapseExpanded]` only renders its template while the vertical shell is not in collapsed display mode.

```html
<div NavigationCollapseRoot class="flex items-center gap-3 px-3">
  <Icon name="work" />

  <span *NavigationCollapseExpanded>Workspace</span>
</div>
```

## Nav Service

`@ojiepermana/angular-navigation/service` exposes `NavigationService` as a singleton registry for navigation type and overlay state.

```ts
import { NavigationService } from '@ojiepermana/angular-navigation/service';

const nav = inject(NavigationService);

nav.setType('erp-sidebar', 'dockbar');
nav.setCollapsed('erp-sidebar', true);
nav.toggleCollapsed('erp-sidebar');
nav.setPosition('erp-sidebar', 'right');
nav.setDockbarMode('erp-sidebar', 'drawer');
nav.openDrawer('erp-sidebar');
nav.closeDrawer('erp-sidebar');
```

Available service helpers include:

- `register`, `unregister`, `state`, `currentState`, and `update` for instance lifecycle and state access.
- `setType`, `setPosition`, `setCollapsed`, `toggleCollapsed`, and `setDockbarMode` for instance configuration.
- `currentPanelKey`, `isPanelOpen`, `openPanel`, `togglePanel`, and `closePanel` for flyout or mega-panel state.
- `isDrawerOpen`, `openDrawer`, `toggleDrawer`, and `closeDrawer` for drawer dockbar mode.
- `exactMatchOptions` and `subsetMatchOptions` for router matching defaults.
- `routeContext` for the current Router URL plus deepest `navigationIcon`, `navigationBadge`, and
  `navigationLabel` metadata.

Panel methods accept exactly one of these forms: `openPanel(item)` for the default navigation,
or `openPanel(navId, item)` for an explicit instance. The same overload contract applies to
`isPanelOpen` and `togglePanel`; a string without an item is rejected at compile time and throws
a diagnostic error for untyped runtime callers.

Data publication is owner-aware. If desktop and mobile navigation publish the same `data-id`,
destroying an older publisher cannot clear the newer one; when the newest publisher is destroyed,
the registry falls back to the most recent remaining live publisher.

Navigation UI depends on the `NAVIGATION_ADAPTER` contract, not directly on the
service class. The default token factory uses `NavigationService`; an application
can override the token with a compatible adapter to change persistence or state
ownership without replacing the components.

Router metadata is supplied through the separate `NAVIGATION_ROUTE_CONTEXT_ADAPTER` token. This
keeps existing custom `NavigationAdapter` implementations source-compatible; override the route
token only when the application uses a nonstandard Router/state host. Its matcher must recursively
compare only item links against the supplied URL (never `item.active` or `activeIds`) and honor the
item's complete `IsActiveMatchOptions` contract.

```ts
interface NavigationRouteContextAdapter {
  readonly routeContext: Signal<NavigationResolvedRouteContext>;
  isItemRouteActive(
    item: NavigationNormalizedItem,
    explicitActiveUrl: string | null,
    relativeTo?: ActivatedRoute | null,
  ): boolean;
}
```

```ts
import {
  NAVIGATION_ADAPTER,
  NAVIGATION_ROUTE_CONTEXT_ADAPTER,
} from '@ojiepermana/angular-navigation/core';

const providers = [
  { provide: NAVIGATION_ADAPTER, useExisting: WorkspaceNavigationAdapter },
  { provide: NAVIGATION_ROUTE_CONTEXT_ADAPTER, useExisting: WorkspaceRouteContextAdapter },
];
```

Only the primary sidebar navigation (`id="main"`) persists preferences in `localStorage` under:

- `nav-orientation`
- `nav-type`
- `nav-position`
- `nav-type-mode`

`nav-type-mode` depends on the active vertical type:

- sidebar: `default` or `collapsed`
- dockbar: `sticky` or `drawer`

Any other navigation id, such as `erp-navbar`, only keeps its state in memory for the active instance. Consumers should provide those values explicitly at usage time or rely on the built-in defaults.

## Icons

Saat `item.icon` tersedia, `nav` akan merender Material Symbols secara default melalui `@ojiepermana/angular-component/icon`. Jalankan `provideUiTheme()` untuk preload global, atau biarkan `IconComponent` melakukan lazy-load saat icon pertama dipakai.

```html
<Navigation [data]="items">
  <NavigationSidebar />
</Navigation>
```

Gunakan template `NavigationIcon` hanya bila consumer ingin override renderer bawaan.

```html
<Navigation [data]="items">
  <NavigationSidebar />

  <ng-template NavigationIcon let-icon let-item="item" let-active="active" let-level="level">
    <Icon [name]="icon" class="text-primary" />
  </ng-template>
</Navigation>
```

`NavigationIcon` template context exposes:

- `$implicit` and `icon`: resolved icon name.
- `item`: source `NavigationItem`.
- `active`: current active state.
- `orientation`: current nav orientation.
- `level`: current nesting depth.

## Boundaries

Entry point ini tetap extensible di layer consumer. Type defaults, instance registration, overlay state, dan affordance icon bawaan dikelola di library, sementara consumer masih bisa mengganti renderer icon dan komposisi layout sesuai kebutuhan.

## Bundle / performance

`NavigationContent` me-render renderer sesuai type aktif. Renderer yang lebih jarang
dipakai — **flyout**, **horizontal/navbar**, dan **dockbar** — dibungkus `@defer (on immediate)`
sehingga bundler aplikasi memecahnya menjadi lazy chunk: aplikasi yang hanya memakai
**sidebar** (vertical list, renderer default yang tetap eager) tidak ikut membundel ketiga
renderer tersebut. Tidak ada perubahan API — komposisi `<Navigation><NavigationSidebar>…`
tetap sama; hanya distribusi chunk di sisi consumer yang lebih ringan.

## Support and security

Angular 22.1–22.x CSR/browser usage is supported; SSR, prerendering, and hydration are not. See the
bundled `SUPPORT.md`. Report suspected vulnerabilities privately using the instructions in the
bundled `SECURITY.md`. This package is MIT licensed; see the bundled `LICENSE`.
