@ojiepermana/angular-theme 22.0.44 → 22.0.46

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.
package/README.md CHANGED
@@ -11,26 +11,39 @@ bun add @ojiepermana/angular-theme
11
11
 
12
12
  ## Entry points
13
13
 
14
- | Import path | Contents |
15
- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
16
- | `@ojiepermana/angular-theme/styles` | `provideUiTheme`, `ThemeModeService`, `ThemeColorService`, `ThemeBrandService`, `ThemeRadiusService`, `ThemeSpaceService`, … |
17
- | `@ojiepermana/angular-theme/layout` | Layout shell building blocks |
18
- | `@ojiepermana/angular-theme/page` | Page-level scaffolding |
19
- | `@ojiepermana/angular-theme/theme.css` | Base tokens + component styles (CSS) |
20
- | `@ojiepermana/angular-theme/styles/css/*` | Raw CSS assets (color + neutral palettes, Tailwind map) |
14
+ | Import path | Contents |
15
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
16
+ | `@ojiepermana/angular-theme/styles` | `provideUiTheme`, `ThemeModeService`, `ThemeColorService`, `ThemeBrandService`, `ThemeRadiusService`, `ThemeSpaceService`, … |
17
+ | `@ojiepermana/angular-theme/layout` | Layout shell building blocks |
18
+ | `@ojiepermana/angular-theme/page` | Page-level scaffolding |
19
+ | `@ojiepermana/angular-theme/theme.css` | Base tokens + component styles only — lightweight, no runtime axes |
20
+ | `@ojiepermana/angular-theme/theme-full.css` | Base + every runtime axis (color, neutral, radius, space) — needed for `ThemeColorService`/`ThemeNeutralService` switching |
21
+ | `@ojiepermana/angular-theme/styles/css/*` | Raw CSS assets (per-axis color + neutral palettes, Tailwind map) |
21
22
 
22
23
  ## Tailwind v4 setup
23
24
 
24
- The CSS is published with the package and addressable by name. In your global
25
- stylesheet:
25
+ The CSS is published with the package and addressable by name. Use `theme-full.css`
26
+ for the full design system (runtime color/neutral switching); use the lightweight
27
+ `theme.css` plus only the axes you need for a smaller bundle.
26
28
 
27
29
  ```css
28
- /* styles.css */
29
- @import '@ojiepermana/angular-theme/styles/css/index.css'; /* base + all color + neutral palettes */
30
+ /* styles.css — full design system */
31
+ @import '@ojiepermana/angular-theme/theme-full.css'; /* base + all color + neutral palettes */
30
32
  @import 'tailwindcss';
31
33
  @import '@ojiepermana/angular-theme/styles/css/base/tailwind.css'; /* maps tokens → bg-primary, bg-brand, text-foreground, … */
32
34
  ```
33
35
 
36
+ For a lightweight setup, import the base then opt in to specific axes:
37
+
38
+ ```css
39
+ /* styles.css — base + only the axes you use */
40
+ @import '@ojiepermana/angular-theme/theme.css'; /* base tokens + components, no axes */
41
+ @import 'tailwindcss';
42
+ @import '@ojiepermana/angular-theme/styles/css/base/tailwind.css';
43
+ @import '@ojiepermana/angular-theme/styles/css/color/index.css'; /* opt in to accent palettes */
44
+ @import '@ojiepermana/angular-theme/styles/css/neutral/index.css'; /* opt in to neutral families */
45
+ ```
46
+
34
47
  Requires Tailwind CSS `^4.3.0`.
35
48
 
36
49
  ## Provider
@@ -83,8 +96,10 @@ Four independent axes switch at runtime via attribute selectors on `<html>`:
83
96
  - **space** (`theme-space`) — `compact`, `normal` (default), `relaxed`, `spacious`.
84
97
  Sets the `--spacing-base` knob; every `p-*` / `m-*` / `gap-*` / `w-*` / `h-*` utility follows.
85
98
 
86
- `styles/css/index.css` bundles the core base theme plus every accent and neutral
87
- palette, so switching needs no runtime CSS loading.
99
+ `theme-full.css` (i.e. `styles/css/index.css`) bundles the core base theme plus
100
+ every accent and neutral palette, so switching needs no runtime CSS loading. The
101
+ lightweight `theme.css` ships base only — import the axis files you need on top of
102
+ it (or use `theme-full.css`) to enable runtime switching.
88
103
 
89
104
  ### Material Symbols icons (opt-in)
90
105
 
@@ -160,14 +160,24 @@ class LayoutNavSidebar {
160
160
  ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
161
161
  isBorderRail = computed(() => this.appearance() === 'border-rail', /* @ts-ignore */
162
162
  ...(ngDevMode ? [{ debugName: "isBorderRail" }] : /* istanbul ignore next */ []));
163
- /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
164
- shellClass = computed(() => this.isBorderRail() ? 'border-r-[1.5px] border-border' : 'border-r border-border', /* @ts-ignore */
163
+ /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` = `--layout-rail-width` (1.21px, selaras rail layout), `flat` 1px. */
164
+ shellClass = computed(() => this.isBorderRail()
165
+ ? 'border-r-[length:var(--layout-rail-width)] border-border'
166
+ : 'border-r border-border', /* @ts-ignore */
165
167
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
166
- /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
167
- headerClass = computed(() => this.isBorderRail() ? 'border-b-[1.5px] border-border' : '', /* @ts-ignore */
168
+ /**
169
+ * Header divider: di `border-rail` border bawaan dimatikan (`border-b-0`) karena pemisahnya
170
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-top-rail` di `top-12`) — supaya
171
+ * tidak dempet/dobel dengan garis itu. `flat` pakai default (border bawaan NavigationHeader).
172
+ */
173
+ headerClass = computed(() => (this.isBorderRail() ? 'border-b-0' : ''), /* @ts-ignore */
168
174
  ...(ngDevMode ? [{ debugName: "headerClass" }] : /* istanbul ignore next */ []));
169
- /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
170
- footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-[1.5px] border-border' : 'px-3', /* @ts-ignore */
175
+ /**
176
+ * Footer divider: di `border-rail` border bawaan dimatikan (`border-t-0`) karena pemisahnya
177
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-bottom-rail` di `bottom-12`);
178
+ * `px-3` tetap dipertahankan.
179
+ */
180
+ footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-0' : 'px-3', /* @ts-ignore */
171
181
  ...(ngDevMode ? [{ debugName: "footerClass" }] : /* istanbul ignore next */ []));
172
182
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutNavSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
173
183
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutNavSidebar, isStandalone: true, selector: "LayoutNavSidebar", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
@@ -220,14 +230,24 @@ class LayoutNavDockbar {
220
230
  ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
221
231
  isBorderRail = computed(() => this.appearance() === 'border-rail', /* @ts-ignore */
222
232
  ...(ngDevMode ? [{ debugName: "isBorderRail" }] : /* istanbul ignore next */ []));
223
- /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
224
- shellClass = computed(() => this.isBorderRail() ? 'border-r-[1.5px] border-border' : 'border-r border-border', /* @ts-ignore */
233
+ /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` = `--layout-rail-width` (1.21px, selaras rail layout), `flat` 1px. */
234
+ shellClass = computed(() => this.isBorderRail()
235
+ ? 'border-r-[length:var(--layout-rail-width)] border-border'
236
+ : 'border-r border-border', /* @ts-ignore */
225
237
  ...(ngDevMode ? [{ debugName: "shellClass" }] : /* istanbul ignore next */ []));
226
- /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
227
- headerClass = computed(() => this.isBorderRail() ? 'border-b-[1.5px] border-border' : '', /* @ts-ignore */
238
+ /**
239
+ * Header divider: di `border-rail` border bawaan dimatikan (`border-b-0`) karena pemisahnya
240
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-top-rail` di `top-12`) — supaya
241
+ * tidak dempet/dobel dengan garis itu. `flat` pakai default (border bawaan NavigationHeader).
242
+ */
243
+ headerClass = computed(() => (this.isBorderRail() ? 'border-b-0' : ''), /* @ts-ignore */
228
244
  ...(ngDevMode ? [{ debugName: "headerClass" }] : /* istanbul ignore next */ []));
229
- /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
230
- footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-[1.5px] border-border' : 'px-3', /* @ts-ignore */
245
+ /**
246
+ * Footer divider: di `border-rail` border bawaan dimatikan (`border-t-0`) karena pemisahnya
247
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-bottom-rail` di `bottom-12`);
248
+ * `px-3` tetap dipertahankan.
249
+ */
250
+ footerClass = computed(() => this.isBorderRail() ? 'px-3 border-t-0' : 'px-3', /* @ts-ignore */
231
251
  ...(ngDevMode ? [{ debugName: "footerClass" }] : /* istanbul ignore next */ []));
232
252
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutNavDockbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
233
253
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutNavDockbar, isStandalone: true, selector: "LayoutNavDockbar", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: true, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, input, computed, DestroyRef, Component, effect, ChangeDetectionStrategy, contentChild } from '@angular/core';
2
+ import { inject, input, computed, DestroyRef, Component, effect, contentChild } from '@angular/core';
3
3
  import { cn } from '@ojiepermana/angular-component/utils';
4
4
  import { LayoutLoadingService, LayoutService } from '@ojiepermana/angular-theme/layout/services';
5
5
  export { LAYOUT_LOADING_HIDE_DELAY_MS, LAYOUT_LOADING_INITIAL_PROGRESS, LAYOUT_LOADING_SKIP, LAYOUT_LOADING_TRICKLE_CEILING, LAYOUT_LOADING_TRICKLE_INTERVAL_MS, LayoutLoadingService, LayoutService, layoutLoadingInterceptor } from '@ojiepermana/angular-theme/layout/services';
@@ -377,13 +377,12 @@ class LayoutComponent {
377
377
  </div>
378
378
  </div>
379
379
  </div>
380
- `, isInline: true, dependencies: [{ kind: "component", type: LayoutLoadingComponent, selector: "LayoutLoading", inputs: ["class", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
380
+ `, isInline: true, dependencies: [{ kind: "component", type: LayoutLoadingComponent, selector: "LayoutLoading", inputs: ["class", "ariaLabel"] }] });
381
381
  }
382
382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutComponent, decorators: [{
383
383
  type: Component,
384
384
  args: [{
385
385
  selector: 'Layout',
386
- changeDetection: ChangeDetectionStrategy.OnPush,
387
386
  imports: [LayoutLoadingComponent],
388
387
  host: {
389
388
  '[class]': 'hostClasses()',
@@ -511,13 +510,12 @@ class LayoutVerticalComponent {
511
510
  this.layout.setType('vertical', { persist: false });
512
511
  }
513
512
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutVerticalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
514
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutVerticalComponent, isStandalone: true, selector: "LayoutVertical", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "vertical" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
513
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutVerticalComponent, isStandalone: true, selector: "LayoutVertical", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "vertical" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
515
514
  }
516
515
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutVerticalComponent, decorators: [{
517
516
  type: Component,
518
517
  args: [{
519
518
  selector: 'LayoutVertical',
520
- changeDetection: ChangeDetectionStrategy.OnPush,
521
519
  host: {
522
520
  '[class]': 'classes()',
523
521
  'data-layout-type': 'vertical',
@@ -536,13 +534,12 @@ class LayoutHorizontalComponent {
536
534
  this.layout.setType('horizontal', { persist: false });
537
535
  }
538
536
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutHorizontalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
539
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutHorizontalComponent, isStandalone: true, selector: "LayoutHorizontal", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "horizontal" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
537
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutHorizontalComponent, isStandalone: true, selector: "LayoutHorizontal", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "horizontal" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
540
538
  }
541
539
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutHorizontalComponent, decorators: [{
542
540
  type: Component,
543
541
  args: [{
544
542
  selector: 'LayoutHorizontal',
545
- changeDetection: ChangeDetectionStrategy.OnPush,
546
543
  host: {
547
544
  '[class]': 'classes()',
548
545
  'data-layout-type': 'horizontal',
@@ -561,13 +558,12 @@ class LayoutEmptyComponent {
561
558
  this.layout.setType('empty', { persist: false });
562
559
  }
563
560
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutEmptyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
564
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutEmptyComponent, isStandalone: true, selector: "LayoutEmpty", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "empty" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
561
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutEmptyComponent, isStandalone: true, selector: "LayoutEmpty", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "empty" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
565
562
  }
566
563
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutEmptyComponent, decorators: [{
567
564
  type: Component,
568
565
  args: [{
569
566
  selector: 'LayoutEmpty',
570
- changeDetection: ChangeDetectionStrategy.OnPush,
571
567
  host: {
572
568
  '[class]': 'classes()',
573
569
  'data-layout-type': 'empty',
@@ -586,13 +582,12 @@ class LayoutFluidComponent {
586
582
  this.layout.setType('fluid', { persist: false });
587
583
  }
588
584
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutFluidComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
589
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutFluidComponent, isStandalone: true, selector: "LayoutFluid", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "fluid" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
585
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutFluidComponent, isStandalone: true, selector: "LayoutFluid", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-layout-type": "fluid" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
590
586
  }
591
587
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutFluidComponent, decorators: [{
592
588
  type: Component,
593
589
  args: [{
594
590
  selector: 'LayoutFluid',
595
- changeDetection: ChangeDetectionStrategy.OnPush,
596
591
  host: {
597
592
  '[class]': 'classes()',
598
593
  'data-layout-type': 'fluid',
@@ -635,29 +630,31 @@ class LayoutNavigationComponent {
635
630
  <div
636
631
  aria-hidden="true"
637
632
  data-layout-nav-rail
638
- class="pointer-events-none absolute inset-y-0 right-0 z-20 w-[1.5px] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
633
+ class="pointer-events-none absolute inset-y-0 right-0 z-20 w-(--layout-rail-width) transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
639
634
  [style.transform]="railTransform()"
640
635
  >
641
- <div data-layout-nav-rail-line class="absolute inset-y-0 right-0 w-[1.5px] bg-border"></div>
636
+ <div
637
+ data-layout-nav-rail-line
638
+ class="absolute inset-y-0 right-0 w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
639
+ ></div>
642
640
  <div
643
641
  data-layout-nav-rail-top
644
- class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
642
+ class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
645
643
  ></div>
646
644
  <div
647
645
  data-layout-nav-rail-bottom
648
- class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
646
+ class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
649
647
  ></div>
650
648
  </div>
651
649
  }
652
650
 
653
651
  <ng-content />
654
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
652
+ `, isInline: true });
655
653
  }
656
654
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutNavigationComponent, decorators: [{
657
655
  type: Component,
658
656
  args: [{
659
657
  selector: 'LayoutNavigation',
660
- changeDetection: ChangeDetectionStrategy.OnPush,
661
658
  host: {
662
659
  '[class]': 'classes()',
663
660
  role: 'navigation',
@@ -671,17 +668,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
671
668
  <div
672
669
  aria-hidden="true"
673
670
  data-layout-nav-rail
674
- class="pointer-events-none absolute inset-y-0 right-0 z-20 w-[1.5px] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
671
+ class="pointer-events-none absolute inset-y-0 right-0 z-20 w-(--layout-rail-width) transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
675
672
  [style.transform]="railTransform()"
676
673
  >
677
- <div data-layout-nav-rail-line class="absolute inset-y-0 right-0 w-[1.5px] bg-border"></div>
674
+ <div
675
+ data-layout-nav-rail-line
676
+ class="absolute inset-y-0 right-0 w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
677
+ ></div>
678
678
  <div
679
679
  data-layout-nav-rail-top
680
- class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
680
+ class="absolute bottom-full right-0 h-[calc((100dvh-100%)/2)] w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
681
681
  ></div>
682
682
  <div
683
683
  data-layout-nav-rail-bottom
684
- class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-[1.5px] bg-border"
684
+ class="absolute top-full right-0 h-[calc((100dvh-100%)/2)] w-(--layout-rail-width) bg-[hsl(var(--layout-rail-color)/var(--layout-rail-opacity))]"
685
685
  ></div>
686
686
  </div>
687
687
  }
@@ -703,13 +703,12 @@ class LayoutContentComponent {
703
703
  }, /* @ts-ignore */
704
704
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
705
705
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
706
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutContentComponent, isStandalone: true, selector: "LayoutContent", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.data-layout-type": "layout.type()", "attr.data-layout-width": "layout.width()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
706
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: LayoutContentComponent, isStandalone: true, selector: "LayoutContent", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.data-layout-type": "layout.type()", "attr.data-layout-width": "layout.width()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
707
707
  }
708
708
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: LayoutContentComponent, decorators: [{
709
709
  type: Component,
710
710
  args: [{
711
711
  selector: 'LayoutContent',
712
- changeDetection: ChangeDetectionStrategy.OnPush,
713
712
  host: {
714
713
  '[class]': 'classes()',
715
714
  '[attr.data-layout-type]': 'layout.type()',
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { effect, signal, computed, Injectable, inject, ElementRef, input, booleanAttribute, untracked, ChangeDetectionStrategy, Component, contentChild, output } from '@angular/core';
2
+ import { effect, signal, computed, Injectable, inject, ElementRef, input, booleanAttribute, untracked, Component, contentChild, output } from '@angular/core';
3
3
  import { cn } from '@ojiepermana/angular-component/utils';
4
4
  import { NavigationContainerComponent, NavigationFlyoutComponent, NavigationHeaderComponent, NavigationFooterComponent } from '@ojiepermana/angular-navigation';
5
5
  import { NavigationService } from '@ojiepermana/angular-navigation/service';
@@ -317,7 +317,7 @@ class PageFilterComponent {
317
317
  ariaHidden = computed(() => !this.isSticky() && !this.page.filterOpen() ? 'true' : null, /* @ts-ignore */
318
318
  ...(ngDevMode ? [{ debugName: "ariaHidden" }] : /* istanbul ignore next */ []));
319
319
  /** Saat panel mengambang tertutup, `inert` mengeluarkan isinya dari tab order & pohon aksesibilitas (AXE). */
320
- inertWhenClosed = computed(() => (this.isInteractive() && !this.page.filterOpen() ? '' : null), /* @ts-ignore */
320
+ inertWhenClosed = computed(() => this.isInteractive() && !this.page.filterOpen() ? '' : null, /* @ts-ignore */
321
321
  ...(ngDevMode ? [{ debugName: "inertWhenClosed" }] : /* istanbul ignore next */ []));
322
322
  dialogRole = computed(() => (this.isInteractive() ? 'dialog' : null), /* @ts-ignore */
323
323
  ...(ngDevMode ? [{ debugName: "dialogRole" }] : /* istanbul ignore next */ []));
@@ -345,10 +345,18 @@ class PageFilterComponent {
345
345
  this.isSticky() && 'shrink-0 w-[var(--page-filter-width)]', this.isSticky() && position === 'left' && 'border-r', this.isSticky() && position === 'right' && 'order-last border-l',
346
346
  // drawer → meluncur dari tepi content region, mengambang di atas content.
347
347
  this.isDrawer() &&
348
- 'absolute inset-y-0 z-20 w-[var(--page-filter-width)] border shadow-sm transition-transform duration-200 ease-out', this.isDrawer() && position === 'left' && (open ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isDrawer() && position === 'right' && (open ? 'right-0 translate-x-0' : 'right-0 translate-x-full'),
348
+ 'absolute inset-y-0 z-20 w-[var(--page-filter-width)] border shadow-sm transition-transform duration-200 ease-out', this.isDrawer() &&
349
+ position === 'left' &&
350
+ (open ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isDrawer() &&
351
+ position === 'right' &&
352
+ (open ? 'right-0 translate-x-0' : 'right-0 translate-x-full'),
349
353
  // overlay → seperti drawer, tapi di atas drawer (z lebih tinggi) dengan backdrop.
350
354
  this.isOverlay() &&
351
- 'absolute inset-y-0 z-30 w-[var(--page-filter-width)] border shadow-md transition-transform duration-200 ease-out', this.isOverlay() && position === 'left' && (open ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isOverlay() && position === 'right' && (open ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.class());
355
+ 'absolute inset-y-0 z-30 w-[var(--page-filter-width)] border shadow-md transition-transform duration-200 ease-out', this.isOverlay() &&
356
+ position === 'left' &&
357
+ (open ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isOverlay() &&
358
+ position === 'right' &&
359
+ (open ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.class());
352
360
  }, /* @ts-ignore */
353
361
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
354
362
  constructor() {
@@ -391,13 +399,12 @@ class PageFilterComponent {
391
399
  });
392
400
  }
393
401
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
394
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageFilterComponent, isStandalone: true, selector: "PageFilter", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnEsc: { classPropertyName: "closeOnEsc", publicName: "closeOnEsc", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.id": "resolvedId", "attr.data-page-slot": "\"filter\"", "attr.data-page-filter-placement": "resolvedPlacement()", "attr.data-page-filter-mode": "effectiveMode()", "attr.data-page-filter-open": "page.filterOpen()", "attr.data-page-position": "resolvedPosition()", "attr.aria-hidden": "ariaHidden()", "attr.inert": "inertWhenClosed()", "attr.role": "dialogRole()", "attr.aria-modal": "ariaModal()", "attr.aria-label": "dialogLabel()", "attr.tabindex": "dialogTabindex()", "style.--page-filter-width": "resolvedWidth()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
402
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageFilterComponent, isStandalone: true, selector: "PageFilter", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnEsc: { classPropertyName: "closeOnEsc", publicName: "closeOnEsc", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.id": "resolvedId", "attr.data-page-slot": "\"filter\"", "attr.data-page-filter-placement": "resolvedPlacement()", "attr.data-page-filter-mode": "effectiveMode()", "attr.data-page-filter-open": "page.filterOpen()", "attr.data-page-position": "resolvedPosition()", "attr.aria-hidden": "ariaHidden()", "attr.inert": "inertWhenClosed()", "attr.role": "dialogRole()", "attr.aria-modal": "ariaModal()", "attr.aria-label": "dialogLabel()", "attr.tabindex": "dialogTabindex()", "style.--page-filter-width": "resolvedWidth()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
395
403
  }
396
404
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterComponent, decorators: [{
397
405
  type: Component,
398
406
  args: [{
399
407
  selector: 'PageFilter',
400
- changeDetection: ChangeDetectionStrategy.OnPush,
401
408
  host: {
402
409
  '[class]': 'classes()',
403
410
  '[attr.id]': 'resolvedId',
@@ -440,13 +447,12 @@ class PageHeaderComponent {
440
447
  this.isBorderRail() ? 'border-b-[1.5px]' : 'border-b', this.class(), this.resolvedHeight() === 'fix' && 'h-12 overflow-hidden'), /* @ts-ignore */
441
448
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
442
449
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
443
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageHeaderComponent, isStandalone: true, selector: "PageHeader", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "header" }, properties: { "class": "classes()", "style.padding-right": "appsLauncherReservePadding()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
450
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageHeaderComponent, isStandalone: true, selector: "PageHeader", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "header" }, properties: { "class": "classes()", "style.padding-right": "appsLauncherReservePadding()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
444
451
  }
445
452
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageHeaderComponent, decorators: [{
446
453
  type: Component,
447
454
  args: [{
448
455
  selector: 'PageHeader',
449
- changeDetection: ChangeDetectionStrategy.OnPush,
450
456
  host: {
451
457
  '[class]': 'classes()',
452
458
  // Inline style menang atas utility responsif konsumen (mis. `md:px-6`), jadi ruang
@@ -466,15 +472,17 @@ class PageContentComponent {
466
472
  classes = computed(() => buildPageBodyClasses(this.resolvedScroll(), this.class()), /* @ts-ignore */
467
473
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
468
474
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
469
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageContentComponent, isStandalone: true, selector: "PageContent", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "content" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
475
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageContentComponent, isStandalone: true, selector: "PageContent", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "content" }, properties: { "class": "classes()", "attr.tabindex": "resolvedScroll() === \"content\" ? \"0\" : null" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
470
476
  }
471
477
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageContentComponent, decorators: [{
472
478
  type: Component,
473
479
  args: [{
474
480
  selector: 'PageContent',
475
- changeDetection: ChangeDetectionStrategy.OnPush,
476
481
  host: {
477
482
  '[class]': 'classes()',
483
+ // When this region scrolls its own content, make it keyboard-focusable so it
484
+ // can be scrolled without a mouse (axe scrollable-region-focusable).
485
+ '[attr.tabindex]': 'resolvedScroll() === "content" ? "0" : null',
478
486
  'data-page-slot': 'content',
479
487
  },
480
488
  template: `<ng-content />`,
@@ -489,13 +497,12 @@ class PageDashboardComponent {
489
497
  classes = computed(() => buildPageBodyClasses(this.resolvedScroll(), this.class()), /* @ts-ignore */
490
498
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
491
499
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
492
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageDashboardComponent, isStandalone: true, selector: "PageDashboard", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "dashboard" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
500
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageDashboardComponent, isStandalone: true, selector: "PageDashboard", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "dashboard" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
493
501
  }
494
502
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageDashboardComponent, decorators: [{
495
503
  type: Component,
496
504
  args: [{
497
505
  selector: 'PageDashboard',
498
- changeDetection: ChangeDetectionStrategy.OnPush,
499
506
  host: {
500
507
  '[class]': 'classes()',
501
508
  'data-page-slot': 'dashboard',
@@ -516,13 +523,12 @@ class PageFooterComponent {
516
523
  this.isBorderRail() ? 'border-t-[1.5px]' : 'border-t', this.class(), this.resolvedHeight() === 'fix' && 'h-12 overflow-hidden'), /* @ts-ignore */
517
524
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
518
525
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
519
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageFooterComponent, isStandalone: true, selector: "PageFooter", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "footer" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
526
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageFooterComponent, isStandalone: true, selector: "PageFooter", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-page-slot": "footer" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
520
527
  }
521
528
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFooterComponent, decorators: [{
522
529
  type: Component,
523
530
  args: [{
524
531
  selector: 'PageFooter',
525
- changeDetection: ChangeDetectionStrategy.OnPush,
526
532
  host: {
527
533
  '[class]': 'classes()',
528
534
  'data-page-slot': 'footer',
@@ -567,10 +573,10 @@ class PageSideComponent {
567
573
  ...(ngDevMode ? [{ debugName: "isInteractive" }] : /* istanbul ignore next */ []));
568
574
  resolvedScroll = computed(() => this.page.scroll() ?? PAGE_DEFAULT_SCROLL, /* @ts-ignore */
569
575
  ...(ngDevMode ? [{ debugName: "resolvedScroll" }] : /* istanbul ignore next */ []));
570
- ariaHidden = computed(() => (!this.isSticky() && !this.page.sideOpen() ? 'true' : null), /* @ts-ignore */
576
+ ariaHidden = computed(() => !this.isSticky() && !this.page.sideOpen() ? 'true' : null, /* @ts-ignore */
571
577
  ...(ngDevMode ? [{ debugName: "ariaHidden" }] : /* istanbul ignore next */ []));
572
578
  /** Saat panel mengambang tertutup, `inert` mengeluarkan isinya dari tab order & pohon aksesibilitas (AXE). */
573
- inertWhenClosed = computed(() => (this.isInteractive() && !this.page.sideOpen() ? '' : null), /* @ts-ignore */
579
+ inertWhenClosed = computed(() => this.isInteractive() && !this.page.sideOpen() ? '' : null, /* @ts-ignore */
574
580
  ...(ngDevMode ? [{ debugName: "inertWhenClosed" }] : /* istanbul ignore next */ []));
575
581
  dialogRole = computed(() => (this.isInteractive() ? 'dialog' : null), /* @ts-ignore */
576
582
  ...(ngDevMode ? [{ debugName: "dialogRole" }] : /* istanbul ignore next */ []));
@@ -586,8 +592,16 @@ class PageSideComponent {
586
592
  return cn('block min-h-0 border-border bg-background', this.resolvedScroll() === 'content' && 'h-full overflow-auto', this.resolvedScroll() === 'page' && 'overflow-visible', this.isSticky() && 'shrink-0 w-[var(--page-side-width)]',
587
593
  // Urutan DOM body = rail-lalu-content; `left` tak perlu reorder, `right` mendorong rail ke kolom kedua.
588
594
  this.isSticky() && position === 'left' && 'border-r', this.isSticky() && position === 'right' && 'order-last border-l', this.isDrawer() &&
589
- 'absolute inset-y-0 z-20 w-[var(--page-side-width)] border shadow-sm transition-transform duration-200 ease-out', this.isDrawer() && position === 'left' && (sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isDrawer() && position === 'right' && (sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.isOverlay() &&
590
- 'absolute inset-y-0 z-30 w-[var(--page-side-width)] border shadow-md transition-transform duration-200 ease-out', this.isOverlay() && position === 'left' && (sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isOverlay() && position === 'right' && (sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.class());
595
+ 'absolute inset-y-0 z-20 w-[var(--page-side-width)] border shadow-sm transition-transform duration-200 ease-out', this.isDrawer() &&
596
+ position === 'left' &&
597
+ (sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isDrawer() &&
598
+ position === 'right' &&
599
+ (sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.isOverlay() &&
600
+ 'absolute inset-y-0 z-30 w-[var(--page-side-width)] border shadow-md transition-transform duration-200 ease-out', this.isOverlay() &&
601
+ position === 'left' &&
602
+ (sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isOverlay() &&
603
+ position === 'right' &&
604
+ (sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.class());
591
605
  }, /* @ts-ignore */
592
606
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
593
607
  constructor() {
@@ -629,13 +643,12 @@ class PageSideComponent {
629
643
  });
630
644
  }
631
645
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
632
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageSideComponent, isStandalone: true, selector: "PageSide", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnEsc: { classPropertyName: "closeOnEsc", publicName: "closeOnEsc", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.id": "resolvedId", "attr.data-page-slot": "\"side\"", "attr.data-page-side-mode": "resolvedMode()", "attr.data-page-side-open": "page.sideOpen()", "attr.data-page-position": "resolvedPosition()", "attr.aria-hidden": "ariaHidden()", "attr.inert": "inertWhenClosed()", "attr.role": "dialogRole()", "attr.aria-modal": "ariaModal()", "attr.aria-label": "dialogLabel()", "attr.tabindex": "dialogTabindex()", "style.--page-side-width": "resolvedWidth()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
646
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageSideComponent, isStandalone: true, selector: "PageSide", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, closeOnEsc: { classPropertyName: "closeOnEsc", publicName: "closeOnEsc", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.id": "resolvedId", "attr.data-page-slot": "\"side\"", "attr.data-page-side-mode": "resolvedMode()", "attr.data-page-side-open": "page.sideOpen()", "attr.data-page-position": "resolvedPosition()", "attr.aria-hidden": "ariaHidden()", "attr.inert": "inertWhenClosed()", "attr.role": "dialogRole()", "attr.aria-modal": "ariaModal()", "attr.aria-label": "dialogLabel()", "attr.tabindex": "dialogTabindex()", "style.--page-side-width": "resolvedWidth()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
633
647
  }
634
648
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideComponent, decorators: [{
635
649
  type: Component,
636
650
  args: [{
637
651
  selector: 'PageSide',
638
- changeDetection: ChangeDetectionStrategy.OnPush,
639
652
  host: {
640
653
  '[class]': 'classes()',
641
654
  '[attr.id]': 'resolvedId',
@@ -718,10 +731,14 @@ class PageComponent {
718
731
  // (diisi `registerFilter`), lalu default — pola seragam agar Page & PageFilter selalu sepakat.
719
732
  hasFilter = computed(() => this.projectedFilter() !== undefined, /* @ts-ignore */
720
733
  ...(ngDevMode ? [{ debugName: "hasFilter" }] : /* istanbul ignore next */ []));
721
- filterPlacement = computed(() => this.projectedFilter()?.placement() ?? this.page.filterPlacement() ?? PAGE_DEFAULT_FILTER_PLACEMENT, /* @ts-ignore */
734
+ filterPlacement = computed(() => this.projectedFilter()?.placement() ??
735
+ this.page.filterPlacement() ??
736
+ PAGE_DEFAULT_FILTER_PLACEMENT, /* @ts-ignore */
722
737
  ...(ngDevMode ? [{ debugName: "filterPlacement" }] : /* istanbul ignore next */ []));
723
738
  /** Mode efektif: `stacked` selalu `sticky`; `side` memakai mode pada `PageFilter`. */
724
- filterMode = computed(() => this.filterPlacement() === 'stacked' ? 'sticky' : (this.projectedFilter()?.mode() ?? this.page.filterMode()), /* @ts-ignore */
739
+ filterMode = computed(() => this.filterPlacement() === 'stacked'
740
+ ? 'sticky'
741
+ : (this.projectedFilter()?.mode() ?? this.page.filterMode()), /* @ts-ignore */
725
742
  ...(ngDevMode ? [{ debugName: "filterMode" }] : /* istanbul ignore next */ []));
726
743
  filterPosition = computed(() => this.projectedFilter()?.position() ?? this.page.filterPosition(), /* @ts-ignore */
727
744
  ...(ngDevMode ? [{ debugName: "filterPosition" }] : /* istanbul ignore next */ []));
@@ -856,7 +873,8 @@ class PageComponent {
856
873
  aria-label="Close overlay panel"
857
874
  data-page-overlay-backdrop
858
875
  class="absolute inset-0 z-20 bg-[hsl(var(--overlay-backdrop))]"
859
- (click)="handleBackdropClick()"></button>
876
+ (click)="handleBackdropClick()"
877
+ ></button>
860
878
  }
861
879
 
862
880
  <div [class]="shellClasses()">
@@ -882,13 +900,15 @@ class PageComponent {
882
900
  [id]="launcherNavId"
883
901
  [data]="appsNavData()"
884
902
  ariaLabel="Application navigation"
885
- [class]="appsLauncherHostClass()">
903
+ [class]="appsLauncherHostClass()"
904
+ >
886
905
  <NavigationFlyout
887
906
  [icon]="appsIcon()"
888
907
  icon-only
889
908
  trigger-variant="plain"
890
909
  [nav-appearance]="appsLauncherAppearance()"
891
- [label]="appsLabel()">
910
+ [label]="appsLabel()"
911
+ >
892
912
  @if (appsBrand(); as brand) {
893
913
  <NavigationHeader>
894
914
  <LayoutBrand [brand]="brand" />
@@ -902,13 +922,12 @@ class PageComponent {
902
922
  </NavigationFlyout>
903
923
  </Navigation>
904
924
  }
905
- `, isInline: true, dependencies: [{ kind: "component", type: NavigationContainerComponent, selector: "Navigation", inputs: ["id", "data", "ariaLabel", "compact", "collapse-tree", "class", "itemClass", "nav-group-class", "activeIds", "activeUrl", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationFlyoutComponent, selector: "NavigationFlyout", inputs: ["label", "icon", "icon-only", "icon-position", "trigger-variant", "trigger-floating", "trigger-class", "nav-position", "nav-appearance", "class"] }, { kind: "component", type: NavigationHeaderComponent, selector: "NavigationHeader", inputs: ["toggle", "class"] }, { kind: "component", type: NavigationFooterComponent, selector: "NavigationFooter", inputs: ["class"] }, { kind: "component", type: LayoutBrand, selector: "LayoutBrand", inputs: ["brand", "compact"] }, { kind: "component", type: LayoutUser, selector: "LayoutUser", inputs: ["user", "detailed", "logoutLabel", "logoutIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
925
+ `, isInline: true, dependencies: [{ kind: "component", type: NavigationContainerComponent, selector: "Navigation", inputs: ["id", "data", "ariaLabel", "compact", "collapse-tree", "class", "itemClass", "nav-group-class", "activeIds", "activeUrl", "openedIds"], outputs: ["openedIdsChange", "itemSelected"] }, { kind: "component", type: NavigationFlyoutComponent, selector: "NavigationFlyout", inputs: ["label", "icon", "icon-only", "icon-position", "trigger-variant", "trigger-floating", "trigger-class", "nav-position", "nav-appearance", "class"] }, { kind: "component", type: NavigationHeaderComponent, selector: "NavigationHeader", inputs: ["toggle", "class"] }, { kind: "component", type: NavigationFooterComponent, selector: "NavigationFooter", inputs: ["class"] }, { kind: "component", type: LayoutBrand, selector: "LayoutBrand", inputs: ["brand", "compact"] }, { kind: "component", type: LayoutUser, selector: "LayoutUser", inputs: ["user", "detailed", "logoutLabel", "logoutIcon"] }] });
906
926
  }
907
927
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageComponent, decorators: [{
908
928
  type: Component,
909
929
  args: [{
910
930
  selector: 'Page',
911
- changeDetection: ChangeDetectionStrategy.OnPush,
912
931
  providers: [PageStateService],
913
932
  imports: [
914
933
  NavigationContainerComponent,
@@ -940,7 +959,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
940
959
  aria-label="Close overlay panel"
941
960
  data-page-overlay-backdrop
942
961
  class="absolute inset-0 z-20 bg-[hsl(var(--overlay-backdrop))]"
943
- (click)="handleBackdropClick()"></button>
962
+ (click)="handleBackdropClick()"
963
+ ></button>
944
964
  }
945
965
 
946
966
  <div [class]="shellClasses()">
@@ -966,13 +986,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
966
986
  [id]="launcherNavId"
967
987
  [data]="appsNavData()"
968
988
  ariaLabel="Application navigation"
969
- [class]="appsLauncherHostClass()">
989
+ [class]="appsLauncherHostClass()"
990
+ >
970
991
  <NavigationFlyout
971
992
  [icon]="appsIcon()"
972
993
  icon-only
973
994
  trigger-variant="plain"
974
995
  [nav-appearance]="appsLauncherAppearance()"
975
- [label]="appsLabel()">
996
+ [label]="appsLabel()"
997
+ >
976
998
  @if (appsBrand(); as brand) {
977
999
  <NavigationHeader>
978
1000
  <LayoutBrand [brand]="brand" />
@@ -1016,7 +1038,8 @@ class PageFilterToggleComponent {
1016
1038
  [attr.aria-label]="ariaLabel()"
1017
1039
  [attr.aria-controls]="page.filterId()"
1018
1040
  [attr.aria-expanded]="page.filterOpen()"
1019
- (click)="handleClick()">
1041
+ (click)="handleClick()"
1042
+ >
1020
1043
  <ng-content>
1021
1044
  <svg
1022
1045
  aria-hidden="true"
@@ -1026,18 +1049,18 @@ class PageFilterToggleComponent {
1026
1049
  stroke="currentColor"
1027
1050
  stroke-width="2"
1028
1051
  stroke-linecap="round"
1029
- stroke-linejoin="round">
1052
+ stroke-linejoin="round"
1053
+ >
1030
1054
  <path d="M3 5h18l-7 8v5l-4 2v-7z" />
1031
1055
  </svg>
1032
1056
  </ng-content>
1033
1057
  </button>
1034
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1058
+ `, isInline: true });
1035
1059
  }
1036
1060
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterToggleComponent, decorators: [{
1037
1061
  type: Component,
1038
1062
  args: [{
1039
1063
  selector: 'PageFilterToggle',
1040
- changeDetection: ChangeDetectionStrategy.OnPush,
1041
1064
  host: {
1042
1065
  '[class]': 'hostClasses()',
1043
1066
  },
@@ -1048,7 +1071,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
1048
1071
  [attr.aria-label]="ariaLabel()"
1049
1072
  [attr.aria-controls]="page.filterId()"
1050
1073
  [attr.aria-expanded]="page.filterOpen()"
1051
- (click)="handleClick()">
1074
+ (click)="handleClick()"
1075
+ >
1052
1076
  <ng-content>
1053
1077
  <svg
1054
1078
  aria-hidden="true"
@@ -1058,7 +1082,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
1058
1082
  stroke="currentColor"
1059
1083
  stroke-width="2"
1060
1084
  stroke-linecap="round"
1061
- stroke-linejoin="round">
1085
+ stroke-linejoin="round"
1086
+ >
1062
1087
  <path d="M3 5h18l-7 8v5l-4 2v-7z" />
1063
1088
  </svg>
1064
1089
  </ng-content>
@@ -1089,18 +1114,18 @@ class PageSideToggleComponent {
1089
1114
  [attr.aria-label]="ariaLabel()"
1090
1115
  [attr.aria-controls]="page.sideId()"
1091
1116
  [attr.aria-expanded]="page.sideOpen()"
1092
- (click)="handleClick()">
1117
+ (click)="handleClick()"
1118
+ >
1093
1119
  <ng-content>
1094
1120
  <span aria-hidden="true" class="text-lg leading-none">☰</span>
1095
1121
  </ng-content>
1096
1122
  </button>
1097
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1123
+ `, isInline: true });
1098
1124
  }
1099
1125
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideToggleComponent, decorators: [{
1100
1126
  type: Component,
1101
1127
  args: [{
1102
1128
  selector: 'PageSideToggle',
1103
- changeDetection: ChangeDetectionStrategy.OnPush,
1104
1129
  host: {
1105
1130
  '[class]': 'hostClasses()',
1106
1131
  },
@@ -1111,7 +1136,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
1111
1136
  [attr.aria-label]="ariaLabel()"
1112
1137
  [attr.aria-controls]="page.sideId()"
1113
1138
  [attr.aria-expanded]="page.sideOpen()"
1114
- (click)="handleClick()">
1139
+ (click)="handleClick()"
1140
+ >
1115
1141
  <ng-content>
1116
1142
  <span aria-hidden="true" class="text-lg leading-none">☰</span>
1117
1143
  </ng-content>
@@ -551,7 +551,7 @@ function provideUiTheme(options = {}) {
551
551
  // request (privacy / offline / CSP friendly). Set `icons.materialSymbols: true`
552
552
  // or self-host the font and import it yourself.
553
553
  if (options.icons?.materialSymbols === true) {
554
- inject(MaterialSymbolsService).ensureLoaded();
554
+ inject(MaterialSymbolsService).load();
555
555
  }
556
556
  inject(ThemeModeService);
557
557
  inject(ThemeColorService);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ojiepermana/angular-theme",
3
- "version": "22.0.44",
3
+ "version": "22.0.46",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/edsis/angular.git"
@@ -13,15 +13,30 @@
13
13
  "@angular/common": ">=22.0.0",
14
14
  "@angular/core": ">=22.0.0",
15
15
  "@angular/router": ">=22.0.0",
16
- "@ojiepermana/angular-navigation": "^22.0.44",
17
- "@ojiepermana/angular-component": "^22.0.44",
16
+ "@ojiepermana/angular-navigation": "^22.0.46",
17
+ "@ojiepermana/angular-component": "^22.0.46",
18
18
  "rxjs": ">=7.8.0"
19
19
  },
20
+ "peerDependenciesMeta": {
21
+ "@angular/router": {
22
+ "optional": true
23
+ },
24
+ "@ojiepermana/angular-navigation": {
25
+ "optional": true
26
+ },
27
+ "rxjs": {
28
+ "optional": true
29
+ }
30
+ },
20
31
  "dependencies": {
21
32
  "tslib": "^2.8.1"
22
33
  },
23
34
  "exports": {
24
35
  "./theme.css": {
36
+ "style": "./styles/css/base/index.css",
37
+ "default": "./styles/css/base/index.css"
38
+ },
39
+ "./theme-full.css": {
25
40
  "style": "./styles/css/index.css",
26
41
  "default": "./styles/css/index.css"
27
42
  },
@@ -65,7 +80,9 @@
65
80
  "access": "public",
66
81
  "registry": "https://registry.npmjs.org/"
67
82
  },
68
- "sideEffects": false,
83
+ "sideEffects": [
84
+ "**/*.css"
85
+ ],
69
86
  "module": "fesm2022/ojiepermana-angular-theme.mjs",
70
87
  "typings": "types/ojiepermana-angular-theme.d.ts",
71
88
  "type": "module"
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Lightweight default theme stylesheet (exported as `@ojiepermana/angular-theme/theme.css`).
3
+ *
4
+ * Base only: core design tokens, light/dark, and component styles. It does NOT
5
+ * pull in the runtime theme axes (color palettes, neutral families, radius and
6
+ * space presets) so consumers ship a minimal stylesheet by default.
7
+ *
8
+ * Need the runtime axes? Either import the full sheet:
9
+ * @import '@ojiepermana/angular-theme/theme-full.css';
10
+ * or opt in to only the axes you use:
11
+ * @import '@ojiepermana/angular-theme/styles/css/color/index.css';
12
+ * @import '@ojiepermana/angular-theme/styles/css/neutral/index.css';
13
+ */
14
+ @import './package.css';
@@ -88,12 +88,20 @@ declare class LayoutNavSidebar {
88
88
  /** Appearance dari shell (`[appearance]`); menentukan ketebalan border pemisah. */
89
89
  readonly appearance: _angular_core.InputSignal<"flat" | "border-rail">;
90
90
  protected readonly isBorderRail: _angular_core.Signal<boolean>;
91
- /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
92
- protected readonly shellClass: _angular_core.Signal<"border-r-[1.5px] border-border" | "border-r border-border">;
93
- /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
94
- protected readonly headerClass: _angular_core.Signal<"border-b-[1.5px] border-border" | "">;
95
- /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
96
- protected readonly footerClass: _angular_core.Signal<"px-3 border-t-[1.5px] border-border" | "px-3">;
91
+ /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` = `--layout-rail-width` (1.21px, selaras rail layout), `flat` 1px. */
92
+ protected readonly shellClass: _angular_core.Signal<"border-r-[length:var(--layout-rail-width)] border-border" | "border-r border-border">;
93
+ /**
94
+ * Header divider: di `border-rail` border bawaan dimatikan (`border-b-0`) karena pemisahnya
95
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-top-rail` di `top-12`) supaya
96
+ * tidak dempet/dobel dengan garis itu. `flat` pakai default (border bawaan NavigationHeader).
97
+ */
98
+ protected readonly headerClass: _angular_core.Signal<"border-b-0" | "">;
99
+ /**
100
+ * Footer divider: di `border-rail` border bawaan dimatikan (`border-t-0`) karena pemisahnya
101
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-bottom-rail` di `bottom-12`);
102
+ * `px-3` tetap dipertahankan.
103
+ */
104
+ protected readonly footerClass: _angular_core.Signal<"px-3 border-t-0" | "px-3">;
97
105
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LayoutNavSidebar, never>;
98
106
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<LayoutNavSidebar, "LayoutNavSidebar", never, { "brand": { "alias": "brand"; "required": true; "isSignal": true; }; "user": { "alias": "user"; "required": true; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
99
107
  }
@@ -108,12 +116,20 @@ declare class LayoutNavDockbar {
108
116
  /** Appearance dari shell (`[appearance]`); menentukan ketebalan border pemisah. */
109
117
  readonly appearance: _angular_core.InputSignal<"flat" | "border-rail">;
110
118
  protected readonly isBorderRail: _angular_core.Signal<boolean>;
111
- /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` 1.5px, `flat` 1px. */
112
- protected readonly shellClass: _angular_core.Signal<"border-r-[1.5px] border-border" | "border-r border-border">;
113
- /** Header divider menebal jadi 1.5px di `border-rail` (selaras nav/shell); `flat` pakai default. */
114
- protected readonly headerClass: _angular_core.Signal<"border-b-[1.5px] border-border" | "">;
115
- /** Footer divider menebal jadi 1.5px di `border-rail`; `px-3` tetap dipertahankan. */
116
- protected readonly footerClass: _angular_core.Signal<"px-3 border-t-[1.5px] border-border" | "px-3">;
119
+ /** Border kanan pemisah; ketebalan ikut appearance: `border-rail` = `--layout-rail-width` (1.21px, selaras rail layout), `flat` 1px. */
120
+ protected readonly shellClass: _angular_core.Signal<"border-r-[length:var(--layout-rail-width)] border-border" | "border-r border-border">;
121
+ /**
122
+ * Header divider: di `border-rail` border bawaan dimatikan (`border-b-0`) karena pemisahnya
123
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-top-rail` di `top-12`) supaya
124
+ * tidak dempet/dobel dengan garis itu. `flat` pakai default (border bawaan NavigationHeader).
125
+ */
126
+ protected readonly headerClass: _angular_core.Signal<"border-b-0" | "">;
127
+ /**
128
+ * Footer divider: di `border-rail` border bawaan dimatikan (`border-t-0`) karena pemisahnya
129
+ * diambil-alih rail inset accent Layout (`data-layout-horizontal-bottom-rail` di `bottom-12`);
130
+ * `px-3` tetap dipertahankan.
131
+ */
132
+ protected readonly footerClass: _angular_core.Signal<"px-3 border-t-0" | "px-3">;
117
133
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LayoutNavDockbar, never>;
118
134
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<LayoutNavDockbar, "LayoutNavDockbar", never, { "brand": { "alias": "brand"; "required": true; "isSignal": true; }; "user": { "alias": "user"; "required": true; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
119
135
  }