@ojiepermana/angular-theme 22.0.43 → 22.0.45
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 +28 -13
- package/fesm2022/ojiepermana-angular-theme-layout-services.mjs +6 -6
- package/fesm2022/ojiepermana-angular-theme-layout-wrapper.mjs +59 -39
- package/fesm2022/ojiepermana-angular-theme-layout.mjs +41 -42
- package/fesm2022/ojiepermana-angular-theme-page.mjs +550 -59
- package/fesm2022/ojiepermana-angular-theme-styles.mjs +16 -16
- package/package.json +21 -4
- package/page/README.md +157 -11
- package/styles/css/base/index.css +14 -0
- package/types/ojiepermana-angular-theme-layout-wrapper.d.ts +28 -12
- package/types/ojiepermana-angular-theme-page.d.ts +188 -43
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject,
|
|
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';
|
|
@@ -7,6 +7,45 @@ import { LayoutService } from '@ojiepermana/angular-theme/layout';
|
|
|
7
7
|
import { LayoutIdentityService, LayoutBrand, LayoutUser } from '@ojiepermana/angular-theme/layout/wrapper';
|
|
8
8
|
import { DOCUMENT } from '@angular/common';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Manajemen fokus untuk panel mengambang (drawer/overlay): saat terbuka, fokus dipindahkan ke
|
|
12
|
+
* panel; saat tertutup, fokus dikembalikan ke elemen pemicu (mis. tombol toggle). Memenuhi
|
|
13
|
+
* WCAG 2.4.3 (Focus Order). Hanya aktif untuk mode interaktif, aman saat SSR (tanpa `defaultView`),
|
|
14
|
+
* dan tidak mencuri fokus pada render awal.
|
|
15
|
+
*
|
|
16
|
+
* Harus dipanggil di dalam injection context (constructor) karena membuat `effect`.
|
|
17
|
+
*/
|
|
18
|
+
function setupOverlayFocusManagement(config) {
|
|
19
|
+
let initialized = false;
|
|
20
|
+
let wasOpen = false;
|
|
21
|
+
let returnFocus = null;
|
|
22
|
+
effect(() => {
|
|
23
|
+
const interactive = config.isInteractive();
|
|
24
|
+
const open = interactive && config.isOpen();
|
|
25
|
+
const view = config.document?.defaultView ?? null;
|
|
26
|
+
// Lewati render pertama agar tidak mencuri fokus saat panel sudah controlled-open sejak awal.
|
|
27
|
+
if (!initialized) {
|
|
28
|
+
initialized = true;
|
|
29
|
+
wasOpen = open;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (view) {
|
|
33
|
+
if (open && !wasOpen) {
|
|
34
|
+
returnFocus = config.document?.activeElement ?? null;
|
|
35
|
+
view.requestAnimationFrame(() => config.host.focus());
|
|
36
|
+
}
|
|
37
|
+
else if (!open && wasOpen) {
|
|
38
|
+
const target = returnFocus;
|
|
39
|
+
returnFocus = null;
|
|
40
|
+
if (target && typeof target.focus === 'function') {
|
|
41
|
+
view.requestAnimationFrame(() => target.focus());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
wasOpen = open;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
10
49
|
const PAGE_VARIANTS = ['stacked', 'side'];
|
|
11
50
|
const PAGE_SIDE_POSITIONS = ['left', 'right'];
|
|
12
51
|
const PAGE_SIDE_MODES = ['sticky', 'drawer', 'overlay'];
|
|
@@ -14,6 +53,8 @@ const PAGE_SCROLL_VALUES = ['content', 'page'];
|
|
|
14
53
|
const PAGE_HEIGHT_VALUES = ['auto', 'fix'];
|
|
15
54
|
/** Visual appearance shared with the layout/navigation axes — unifies borders. */
|
|
16
55
|
const PAGE_APPEARANCES = ['flat', 'border-rail'];
|
|
56
|
+
/** Penempatan `PageFilter`: `stacked` = bar antara header & content; `side` = kolom di samping content. */
|
|
57
|
+
const PAGE_FILTER_PLACEMENTS = ['stacked', 'side'];
|
|
17
58
|
const PAGE_DEFAULT_VARIANT = 'stacked';
|
|
18
59
|
const PAGE_DEFAULT_SIDE_POSITION = 'left';
|
|
19
60
|
const PAGE_DEFAULT_SIDE_MODE = 'sticky';
|
|
@@ -21,6 +62,14 @@ const PAGE_DEFAULT_SCROLL = 'content';
|
|
|
21
62
|
const PAGE_DEFAULT_HEIGHT = 'auto';
|
|
22
63
|
const PAGE_DEFAULT_APPEARANCE = 'flat';
|
|
23
64
|
const PAGE_DEFAULT_SIDE_WIDTH = '16rem';
|
|
65
|
+
/** Placement default `PageFilter`. */
|
|
66
|
+
const PAGE_DEFAULT_FILTER_PLACEMENT = 'stacked';
|
|
67
|
+
/** Mode default `PageFilter` saat `placement="side"`; `stacked` selalu berperilaku `sticky`. */
|
|
68
|
+
const PAGE_DEFAULT_FILTER_MODE = 'sticky';
|
|
69
|
+
/** Posisi default kolom `PageFilter` saat `placement="side"`. */
|
|
70
|
+
const PAGE_DEFAULT_FILTER_POSITION = 'left';
|
|
71
|
+
/** Lebar default kolom `PageFilter` saat `placement="side"` (sticky/drawer/overlay). */
|
|
72
|
+
const PAGE_DEFAULT_FILTER_WIDTH = '18rem';
|
|
24
73
|
function isUiPageVariant(value) {
|
|
25
74
|
return value !== null && PAGE_VARIANTS.includes(value);
|
|
26
75
|
}
|
|
@@ -39,6 +88,9 @@ function isUiPageHeight(value) {
|
|
|
39
88
|
function isUiPageAppearance(value) {
|
|
40
89
|
return value !== null && PAGE_APPEARANCES.includes(value);
|
|
41
90
|
}
|
|
91
|
+
function isUiPageFilterPlacement(value) {
|
|
92
|
+
return value !== null && PAGE_FILTER_PLACEMENTS.includes(value);
|
|
93
|
+
}
|
|
42
94
|
|
|
43
95
|
class PageStateService {
|
|
44
96
|
variantState = signal(PAGE_DEFAULT_VARIANT, /* @ts-ignore */
|
|
@@ -68,6 +120,25 @@ class PageStateService {
|
|
|
68
120
|
/** Aktif saat apps-launcher mengambang di atas `PageHeader`; header memesan ruang kanan agar isinya tidak tertimpa. */
|
|
69
121
|
appsLauncherReserveState = signal(false, /* @ts-ignore */
|
|
70
122
|
...(ngDevMode ? [{ debugName: "appsLauncherReserveState" }] : /* istanbul ignore next */ []));
|
|
123
|
+
// Filter state — paralel dengan side state agar `PageFilter` punya open/close sendiri yang independen.
|
|
124
|
+
filterPlacementState = signal(PAGE_DEFAULT_FILTER_PLACEMENT, /* @ts-ignore */
|
|
125
|
+
...(ngDevMode ? [{ debugName: "filterPlacementState" }] : /* istanbul ignore next */ []));
|
|
126
|
+
filterModeState = signal(PAGE_DEFAULT_FILTER_MODE, /* @ts-ignore */
|
|
127
|
+
...(ngDevMode ? [{ debugName: "filterModeState" }] : /* istanbul ignore next */ []));
|
|
128
|
+
filterPositionState = signal(PAGE_DEFAULT_FILTER_POSITION, /* @ts-ignore */
|
|
129
|
+
...(ngDevMode ? [{ debugName: "filterPositionState" }] : /* istanbul ignore next */ []));
|
|
130
|
+
filterWidthState = signal(PAGE_DEFAULT_FILTER_WIDTH, /* @ts-ignore */
|
|
131
|
+
...(ngDevMode ? [{ debugName: "filterWidthState" }] : /* istanbul ignore next */ []));
|
|
132
|
+
filterIdState = signal(null, /* @ts-ignore */
|
|
133
|
+
...(ngDevMode ? [{ debugName: "filterIdState" }] : /* istanbul ignore next */ []));
|
|
134
|
+
internalFilterOpenState = signal(false, /* @ts-ignore */
|
|
135
|
+
...(ngDevMode ? [{ debugName: "internalFilterOpenState" }] : /* istanbul ignore next */ []));
|
|
136
|
+
controlledFilterOpenState = signal(null, /* @ts-ignore */
|
|
137
|
+
...(ngDevMode ? [{ debugName: "controlledFilterOpenState" }] : /* istanbul ignore next */ []));
|
|
138
|
+
filterOpenRequestState = signal(null, /* @ts-ignore */
|
|
139
|
+
...(ngDevMode ? [{ debugName: "filterOpenRequestState" }] : /* istanbul ignore next */ []));
|
|
140
|
+
filterOpenRequestVersionState = signal(0, /* @ts-ignore */
|
|
141
|
+
...(ngDevMode ? [{ debugName: "filterOpenRequestVersionState" }] : /* istanbul ignore next */ []));
|
|
71
142
|
variant = this.variantState.asReadonly();
|
|
72
143
|
height = this.heightState.asReadonly();
|
|
73
144
|
scroll = this.scrollState.asReadonly();
|
|
@@ -85,6 +156,19 @@ class PageStateService {
|
|
|
85
156
|
...(ngDevMode ? [{ debugName: "isSideInteractive" }] : /* istanbul ignore next */ []));
|
|
86
157
|
isSideVisible = computed(() => this.sideMode() === 'sticky' || this.sideOpen(), /* @ts-ignore */
|
|
87
158
|
...(ngDevMode ? [{ debugName: "isSideVisible" }] : /* istanbul ignore next */ []));
|
|
159
|
+
filterPlacement = this.filterPlacementState.asReadonly();
|
|
160
|
+
filterMode = this.filterModeState.asReadonly();
|
|
161
|
+
filterPosition = this.filterPositionState.asReadonly();
|
|
162
|
+
filterWidth = this.filterWidthState.asReadonly();
|
|
163
|
+
filterId = this.filterIdState.asReadonly();
|
|
164
|
+
filterOpenRequest = this.filterOpenRequestState.asReadonly();
|
|
165
|
+
filterOpenRequestVersion = this.filterOpenRequestVersionState.asReadonly();
|
|
166
|
+
filterOpen = computed(() => this.controlledFilterOpenState() ?? this.internalFilterOpenState(), /* @ts-ignore */
|
|
167
|
+
...(ngDevMode ? [{ debugName: "filterOpen" }] : /* istanbul ignore next */ []));
|
|
168
|
+
isFilterInteractive = computed(() => this.filterMode() === 'drawer' || this.filterMode() === 'overlay', /* @ts-ignore */
|
|
169
|
+
...(ngDevMode ? [{ debugName: "isFilterInteractive" }] : /* istanbul ignore next */ []));
|
|
170
|
+
isFilterVisible = computed(() => this.filterMode() === 'sticky' || this.filterOpen(), /* @ts-ignore */
|
|
171
|
+
...(ngDevMode ? [{ debugName: "isFilterVisible" }] : /* istanbul ignore next */ []));
|
|
88
172
|
registerRoot(config) {
|
|
89
173
|
this.variantState.set(config.variant);
|
|
90
174
|
this.heightState.set(config.height);
|
|
@@ -129,13 +213,218 @@ class PageStateService {
|
|
|
129
213
|
this.sideOpenRequestVersionState.update((version) => version + 1);
|
|
130
214
|
return open;
|
|
131
215
|
}
|
|
132
|
-
|
|
133
|
-
|
|
216
|
+
registerFilter(config) {
|
|
217
|
+
this.filterPlacementState.set(config.placement);
|
|
218
|
+
this.filterModeState.set(config.mode);
|
|
219
|
+
this.filterPositionState.set(config.position);
|
|
220
|
+
this.filterWidthState.set(config.width);
|
|
221
|
+
this.filterIdState.set(config.id);
|
|
222
|
+
}
|
|
223
|
+
setControlledFilterOpen(open) {
|
|
224
|
+
this.controlledFilterOpenState.set(open);
|
|
225
|
+
if (open !== null) {
|
|
226
|
+
this.internalFilterOpenState.set(open);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setFilterOpen(open) {
|
|
230
|
+
this.internalFilterOpenState.set(open);
|
|
231
|
+
}
|
|
232
|
+
openFilter() {
|
|
233
|
+
return this.requestFilterOpenChange(true);
|
|
234
|
+
}
|
|
235
|
+
closeFilter() {
|
|
236
|
+
return this.requestFilterOpenChange(false);
|
|
237
|
+
}
|
|
238
|
+
toggleFilter() {
|
|
239
|
+
return this.requestFilterOpenChange(!this.filterOpen());
|
|
240
|
+
}
|
|
241
|
+
requestFilterOpenChange(open) {
|
|
242
|
+
if (this.controlledFilterOpenState() === null) {
|
|
243
|
+
this.internalFilterOpenState.set(open);
|
|
244
|
+
}
|
|
245
|
+
this.filterOpenRequestState.set(open);
|
|
246
|
+
this.filterOpenRequestVersionState.update((version) => version + 1);
|
|
247
|
+
return open;
|
|
248
|
+
}
|
|
249
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
250
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageStateService });
|
|
134
251
|
}
|
|
135
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
252
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageStateService, decorators: [{
|
|
136
253
|
type: Injectable
|
|
137
254
|
}] });
|
|
138
255
|
|
|
256
|
+
let nextPageFilterId = 0;
|
|
257
|
+
/**
|
|
258
|
+
* `PageFilter` — slot filter di antara `PageHeader` dan `PageContent` (`placement="stacked"`)
|
|
259
|
+
* atau di samping `PageContent` (`placement="side"`). Pada `stacked`, filter mendorong content
|
|
260
|
+
* ke bawah dan content menyesuaikan tinggi sisa. Pada `side`, filter mendorong content ke samping
|
|
261
|
+
* dengan mode `sticky | drawer | overlay` (mengikuti pola `PageSide`).
|
|
262
|
+
*
|
|
263
|
+
* `placement="stacked"` selalu berperilaku `sticky` — `drawer`/`overlay` hanya berlaku untuk `side`.
|
|
264
|
+
*/
|
|
265
|
+
class PageFilterComponent {
|
|
266
|
+
document = inject(DOCUMENT, { optional: true });
|
|
267
|
+
host = inject((ElementRef));
|
|
268
|
+
page = inject(PageStateService);
|
|
269
|
+
resolvedId = `page-filter-${++nextPageFilterId}`;
|
|
270
|
+
placement = input(PAGE_DEFAULT_FILTER_PLACEMENT, /* @ts-ignore */
|
|
271
|
+
...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
272
|
+
mode = input(PAGE_DEFAULT_FILTER_MODE, /* @ts-ignore */
|
|
273
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
274
|
+
position = input(null, /* @ts-ignore */
|
|
275
|
+
...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
276
|
+
width = input(null, /* @ts-ignore */
|
|
277
|
+
...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
278
|
+
closeOnEsc = input(true, /* @ts-ignore */
|
|
279
|
+
...(ngDevMode ? [{ debugName: "closeOnEsc" }] : /* istanbul ignore next */ []));
|
|
280
|
+
/**
|
|
281
|
+
* Khusus `placement="stacked"`: jadikan bar dapat dibuka/tutup lewat `PageFilterToggle`
|
|
282
|
+
* (default tertutup). Saat tertutup, bar disembunyikan dan content mengisi ruangnya kembali.
|
|
283
|
+
* Diabaikan untuk `placement="side"` (gunakan `mode="drawer"`/`overlay"` untuk side yang bisa ditutup).
|
|
284
|
+
*/
|
|
285
|
+
collapsible = input(false, { ...(ngDevMode ? { debugName: "collapsible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
286
|
+
/** Nama aksesibilitas untuk panel saat mode `drawer`/`overlay` (dipasang sebagai `aria-label` dialog). */
|
|
287
|
+
ariaLabel = input('Filters', /* @ts-ignore */
|
|
288
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
289
|
+
class = input('', /* @ts-ignore */
|
|
290
|
+
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
291
|
+
resolvedPlacement = computed(() => this.placement(), /* @ts-ignore */
|
|
292
|
+
...(ngDevMode ? [{ debugName: "resolvedPlacement" }] : /* istanbul ignore next */ []));
|
|
293
|
+
/** `stacked` adalah bar horizontal — tidak punya drawer/overlay, jadi mode efektifnya selalu `sticky`. */
|
|
294
|
+
effectiveMode = computed(() => this.resolvedPlacement() === 'stacked' ? 'sticky' : this.mode(), /* @ts-ignore */
|
|
295
|
+
...(ngDevMode ? [{ debugName: "effectiveMode" }] : /* istanbul ignore next */ []));
|
|
296
|
+
resolvedPosition = computed(() => this.position() ?? this.page.filterPosition(), /* @ts-ignore */
|
|
297
|
+
...(ngDevMode ? [{ debugName: "resolvedPosition" }] : /* istanbul ignore next */ []));
|
|
298
|
+
resolvedWidth = computed(() => this.width() ?? this.page.filterWidth() ?? PAGE_DEFAULT_FILTER_WIDTH, /* @ts-ignore */
|
|
299
|
+
...(ngDevMode ? [{ debugName: "resolvedWidth" }] : /* istanbul ignore next */ []));
|
|
300
|
+
resolvedScroll = computed(() => this.page.scroll() ?? PAGE_DEFAULT_SCROLL, /* @ts-ignore */
|
|
301
|
+
...(ngDevMode ? [{ debugName: "resolvedScroll" }] : /* istanbul ignore next */ []));
|
|
302
|
+
isStacked = computed(() => this.resolvedPlacement() === 'stacked', /* @ts-ignore */
|
|
303
|
+
...(ngDevMode ? [{ debugName: "isStacked" }] : /* istanbul ignore next */ []));
|
|
304
|
+
/** Bar stacked yang dapat ditutup dan sedang tertutup → disembunyikan dari layout. */
|
|
305
|
+
isCollapsedStacked = computed(() => this.isStacked() && this.collapsible() && !this.page.filterOpen(), /* @ts-ignore */
|
|
306
|
+
...(ngDevMode ? [{ debugName: "isCollapsedStacked" }] : /* istanbul ignore next */ []));
|
|
307
|
+
isSticky = computed(() => this.effectiveMode() === 'sticky', /* @ts-ignore */
|
|
308
|
+
...(ngDevMode ? [{ debugName: "isSticky" }] : /* istanbul ignore next */ []));
|
|
309
|
+
isDrawer = computed(() => this.effectiveMode() === 'drawer', /* @ts-ignore */
|
|
310
|
+
...(ngDevMode ? [{ debugName: "isDrawer" }] : /* istanbul ignore next */ []));
|
|
311
|
+
isOverlay = computed(() => this.effectiveMode() === 'overlay', /* @ts-ignore */
|
|
312
|
+
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
313
|
+
/** Mode mengambang (drawer/overlay) — bersifat dialog dan butuh manajemen fokus + `inert` saat tertutup. */
|
|
314
|
+
isInteractive = computed(() => this.isDrawer() || this.isOverlay(), /* @ts-ignore */
|
|
315
|
+
...(ngDevMode ? [{ debugName: "isInteractive" }] : /* istanbul ignore next */ []));
|
|
316
|
+
/** Drawer/overlay yang tertutup disembunyikan dari pohon aksesibilitas. */
|
|
317
|
+
ariaHidden = computed(() => !this.isSticky() && !this.page.filterOpen() ? 'true' : null, /* @ts-ignore */
|
|
318
|
+
...(ngDevMode ? [{ debugName: "ariaHidden" }] : /* istanbul ignore next */ []));
|
|
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 */
|
|
321
|
+
...(ngDevMode ? [{ debugName: "inertWhenClosed" }] : /* istanbul ignore next */ []));
|
|
322
|
+
dialogRole = computed(() => (this.isInteractive() ? 'dialog' : null), /* @ts-ignore */
|
|
323
|
+
...(ngDevMode ? [{ debugName: "dialogRole" }] : /* istanbul ignore next */ []));
|
|
324
|
+
ariaModal = computed(() => (this.isOverlay() ? 'true' : null), /* @ts-ignore */
|
|
325
|
+
...(ngDevMode ? [{ debugName: "ariaModal" }] : /* istanbul ignore next */ []));
|
|
326
|
+
dialogLabel = computed(() => (this.isInteractive() ? this.ariaLabel() : null), /* @ts-ignore */
|
|
327
|
+
...(ngDevMode ? [{ debugName: "dialogLabel" }] : /* istanbul ignore next */ []));
|
|
328
|
+
dialogTabindex = computed(() => (this.isInteractive() ? '-1' : null), /* @ts-ignore */
|
|
329
|
+
...(ngDevMode ? [{ debugName: "dialogTabindex" }] : /* istanbul ignore next */ []));
|
|
330
|
+
classes = computed(() => {
|
|
331
|
+
const position = this.resolvedPosition();
|
|
332
|
+
const scroll = this.resolvedScroll();
|
|
333
|
+
const open = this.page.filterOpen();
|
|
334
|
+
// STACKED: bar selebar content yang mendorong content ke bawah (shrink-0 = tinggi mengikuti isi).
|
|
335
|
+
if (this.isStacked()) {
|
|
336
|
+
return cn('block shrink-0 border-b border-border bg-background',
|
|
337
|
+
// Saat seluruh page yang scroll, filter dipin ke atas area scroll agar tetap terjangkau.
|
|
338
|
+
scroll === 'page' && 'sticky top-0 z-10', this.class(),
|
|
339
|
+
// Collapsible & tertutup → `hidden` (menang atas `block`); content mengisi ruangnya kembali.
|
|
340
|
+
this.isCollapsedStacked() && 'hidden');
|
|
341
|
+
}
|
|
342
|
+
// SIDE: kolom di samping content.
|
|
343
|
+
return cn('block min-h-0 border-border bg-background', scroll === 'content' && 'h-full overflow-auto', scroll === 'page' && 'overflow-visible',
|
|
344
|
+
// sticky → mendorong content (push); urutan kolom di-handle oleh `order-last` saat di kanan.
|
|
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
|
+
// drawer → meluncur dari tepi content region, mengambang di atas content.
|
|
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() &&
|
|
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'),
|
|
353
|
+
// overlay → seperti drawer, tapi di atas drawer (z lebih tinggi) dengan backdrop.
|
|
354
|
+
this.isOverlay() &&
|
|
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());
|
|
360
|
+
}, /* @ts-ignore */
|
|
361
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
362
|
+
constructor() {
|
|
363
|
+
setupOverlayFocusManagement({
|
|
364
|
+
document: this.document,
|
|
365
|
+
host: this.host.nativeElement,
|
|
366
|
+
isOpen: this.page.filterOpen,
|
|
367
|
+
isInteractive: this.isInteractive,
|
|
368
|
+
});
|
|
369
|
+
effect(() => {
|
|
370
|
+
this.page.registerFilter({
|
|
371
|
+
placement: this.resolvedPlacement(),
|
|
372
|
+
mode: this.effectiveMode(),
|
|
373
|
+
position: this.resolvedPosition(),
|
|
374
|
+
width: this.resolvedWidth(),
|
|
375
|
+
id: this.resolvedId,
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
effect((onCleanup) => {
|
|
379
|
+
if (!this.closeOnEsc() || this.isSticky()) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const defaultView = this.document?.defaultView;
|
|
383
|
+
if (!defaultView) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const handler = (event) => {
|
|
387
|
+
if (event.key !== 'Escape') {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!untracked(() => this.page.filterOpen())) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
untracked(() => {
|
|
394
|
+
this.page.closeFilter();
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
defaultView.addEventListener('keydown', handler);
|
|
398
|
+
onCleanup(() => defaultView.removeEventListener('keydown', handler));
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
403
|
+
}
|
|
404
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterComponent, decorators: [{
|
|
405
|
+
type: Component,
|
|
406
|
+
args: [{
|
|
407
|
+
selector: 'PageFilter',
|
|
408
|
+
host: {
|
|
409
|
+
'[class]': 'classes()',
|
|
410
|
+
'[attr.id]': 'resolvedId',
|
|
411
|
+
'[attr.data-page-slot]': '"filter"',
|
|
412
|
+
'[attr.data-page-filter-placement]': 'resolvedPlacement()',
|
|
413
|
+
'[attr.data-page-filter-mode]': 'effectiveMode()',
|
|
414
|
+
'[attr.data-page-filter-open]': 'page.filterOpen()',
|
|
415
|
+
'[attr.data-page-position]': 'resolvedPosition()',
|
|
416
|
+
'[attr.aria-hidden]': 'ariaHidden()',
|
|
417
|
+
'[attr.inert]': 'inertWhenClosed()',
|
|
418
|
+
'[attr.role]': 'dialogRole()',
|
|
419
|
+
'[attr.aria-modal]': 'ariaModal()',
|
|
420
|
+
'[attr.aria-label]': 'dialogLabel()',
|
|
421
|
+
'[attr.tabindex]': 'dialogTabindex()',
|
|
422
|
+
'[style.--page-filter-width]': 'resolvedWidth()',
|
|
423
|
+
},
|
|
424
|
+
template: `<ng-content />`,
|
|
425
|
+
}]
|
|
426
|
+
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closeOnEsc: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEsc", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
|
|
427
|
+
|
|
139
428
|
function buildPageBodyClasses(scroll, customClass) {
|
|
140
429
|
return cn('block min-w-0', scroll === 'content' && 'h-full min-h-0 overflow-auto', scroll === 'page' && 'overflow-visible', customClass);
|
|
141
430
|
}
|
|
@@ -157,14 +446,13 @@ class PageHeaderComponent {
|
|
|
157
446
|
// appearance unifies the border with the layout/nav: border-rail = 1.5px.
|
|
158
447
|
this.isBorderRail() ? 'border-b-[1.5px]' : 'border-b', this.class(), this.resolvedHeight() === 'fix' && 'h-12 overflow-hidden'), /* @ts-ignore */
|
|
159
448
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
160
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
161
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
449
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
162
451
|
}
|
|
163
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
452
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageHeaderComponent, decorators: [{
|
|
164
453
|
type: Component,
|
|
165
454
|
args: [{
|
|
166
455
|
selector: 'PageHeader',
|
|
167
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
168
456
|
host: {
|
|
169
457
|
'[class]': 'classes()',
|
|
170
458
|
// Inline style menang atas utility responsif konsumen (mis. `md:px-6`), jadi ruang
|
|
@@ -183,14 +471,13 @@ class PageContentComponent {
|
|
|
183
471
|
...(ngDevMode ? [{ debugName: "resolvedScroll" }] : /* istanbul ignore next */ []));
|
|
184
472
|
classes = computed(() => buildPageBodyClasses(this.resolvedScroll(), this.class()), /* @ts-ignore */
|
|
185
473
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
186
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
187
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
474
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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()" } }, ngImport: i0, template: `<ng-content />`, isInline: true });
|
|
188
476
|
}
|
|
189
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
477
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageContentComponent, decorators: [{
|
|
190
478
|
type: Component,
|
|
191
479
|
args: [{
|
|
192
480
|
selector: 'PageContent',
|
|
193
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
194
481
|
host: {
|
|
195
482
|
'[class]': 'classes()',
|
|
196
483
|
'data-page-slot': 'content',
|
|
@@ -206,14 +493,13 @@ class PageDashboardComponent {
|
|
|
206
493
|
...(ngDevMode ? [{ debugName: "resolvedScroll" }] : /* istanbul ignore next */ []));
|
|
207
494
|
classes = computed(() => buildPageBodyClasses(this.resolvedScroll(), this.class()), /* @ts-ignore */
|
|
208
495
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
209
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
210
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
496
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageDashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
497
|
+
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 });
|
|
211
498
|
}
|
|
212
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
499
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageDashboardComponent, decorators: [{
|
|
213
500
|
type: Component,
|
|
214
501
|
args: [{
|
|
215
502
|
selector: 'PageDashboard',
|
|
216
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
217
503
|
host: {
|
|
218
504
|
'[class]': 'classes()',
|
|
219
505
|
'data-page-slot': 'dashboard',
|
|
@@ -233,14 +519,13 @@ class PageFooterComponent {
|
|
|
233
519
|
// appearance unifies the border with the layout/nav: border-rail = 1.5px.
|
|
234
520
|
this.isBorderRail() ? 'border-t-[1.5px]' : 'border-t', this.class(), this.resolvedHeight() === 'fix' && 'h-12 overflow-hidden'), /* @ts-ignore */
|
|
235
521
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
236
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
237
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
522
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
523
|
+
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 });
|
|
238
524
|
}
|
|
239
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
525
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFooterComponent, decorators: [{
|
|
240
526
|
type: Component,
|
|
241
527
|
args: [{
|
|
242
528
|
selector: 'PageFooter',
|
|
243
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
244
529
|
host: {
|
|
245
530
|
'[class]': 'classes()',
|
|
246
531
|
'data-page-slot': 'footer',
|
|
@@ -252,6 +537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
252
537
|
let nextPageSideId = 0;
|
|
253
538
|
class PageSideComponent {
|
|
254
539
|
document = inject(DOCUMENT, { optional: true });
|
|
540
|
+
host = inject((ElementRef));
|
|
255
541
|
page = inject(PageStateService);
|
|
256
542
|
resolvedId = `page-side-${++nextPageSideId}`;
|
|
257
543
|
mode = input('sticky', /* @ts-ignore */
|
|
@@ -262,6 +548,9 @@ class PageSideComponent {
|
|
|
262
548
|
...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
263
549
|
closeOnEsc = input(true, /* @ts-ignore */
|
|
264
550
|
...(ngDevMode ? [{ debugName: "closeOnEsc" }] : /* istanbul ignore next */ []));
|
|
551
|
+
/** Nama aksesibilitas untuk panel saat mode `drawer`/`overlay` (dipasang sebagai `aria-label` dialog). */
|
|
552
|
+
ariaLabel = input('Side panel', /* @ts-ignore */
|
|
553
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
265
554
|
class = input('', /* @ts-ignore */
|
|
266
555
|
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
267
556
|
resolvedMode = computed(() => this.mode() ?? this.page.sideMode(), /* @ts-ignore */
|
|
@@ -276,19 +565,49 @@ class PageSideComponent {
|
|
|
276
565
|
...(ngDevMode ? [{ debugName: "isDrawer" }] : /* istanbul ignore next */ []));
|
|
277
566
|
isOverlay = computed(() => this.resolvedMode() === 'overlay', /* @ts-ignore */
|
|
278
567
|
...(ngDevMode ? [{ debugName: "isOverlay" }] : /* istanbul ignore next */ []));
|
|
568
|
+
/** Mode mengambang (drawer/overlay) — bersifat dialog dan butuh manajemen fokus + `inert` saat tertutup. */
|
|
569
|
+
isInteractive = computed(() => this.isDrawer() || this.isOverlay(), /* @ts-ignore */
|
|
570
|
+
...(ngDevMode ? [{ debugName: "isInteractive" }] : /* istanbul ignore next */ []));
|
|
279
571
|
resolvedScroll = computed(() => this.page.scroll() ?? PAGE_DEFAULT_SCROLL, /* @ts-ignore */
|
|
280
572
|
...(ngDevMode ? [{ debugName: "resolvedScroll" }] : /* istanbul ignore next */ []));
|
|
281
|
-
ariaHidden = computed(() =>
|
|
573
|
+
ariaHidden = computed(() => !this.isSticky() && !this.page.sideOpen() ? 'true' : null, /* @ts-ignore */
|
|
282
574
|
...(ngDevMode ? [{ debugName: "ariaHidden" }] : /* istanbul ignore next */ []));
|
|
575
|
+
/** Saat panel mengambang tertutup, `inert` mengeluarkan isinya dari tab order & pohon aksesibilitas (AXE). */
|
|
576
|
+
inertWhenClosed = computed(() => this.isInteractive() && !this.page.sideOpen() ? '' : null, /* @ts-ignore */
|
|
577
|
+
...(ngDevMode ? [{ debugName: "inertWhenClosed" }] : /* istanbul ignore next */ []));
|
|
578
|
+
dialogRole = computed(() => (this.isInteractive() ? 'dialog' : null), /* @ts-ignore */
|
|
579
|
+
...(ngDevMode ? [{ debugName: "dialogRole" }] : /* istanbul ignore next */ []));
|
|
580
|
+
ariaModal = computed(() => (this.isOverlay() ? 'true' : null), /* @ts-ignore */
|
|
581
|
+
...(ngDevMode ? [{ debugName: "ariaModal" }] : /* istanbul ignore next */ []));
|
|
582
|
+
dialogLabel = computed(() => (this.isInteractive() ? this.ariaLabel() : null), /* @ts-ignore */
|
|
583
|
+
...(ngDevMode ? [{ debugName: "dialogLabel" }] : /* istanbul ignore next */ []));
|
|
584
|
+
dialogTabindex = computed(() => (this.isInteractive() ? '-1' : null), /* @ts-ignore */
|
|
585
|
+
...(ngDevMode ? [{ debugName: "dialogTabindex" }] : /* istanbul ignore next */ []));
|
|
283
586
|
classes = computed(() => {
|
|
284
587
|
const position = this.resolvedPosition();
|
|
285
588
|
const sideOpen = this.page.sideOpen();
|
|
286
|
-
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)]',
|
|
287
|
-
|
|
288
|
-
|
|
589
|
+
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)]',
|
|
590
|
+
// Urutan DOM body = rail-lalu-content; `left` tak perlu reorder, `right` mendorong rail ke kolom kedua.
|
|
591
|
+
this.isSticky() && position === 'left' && 'border-r', this.isSticky() && position === 'right' && 'order-last border-l', this.isDrawer() &&
|
|
592
|
+
'absolute inset-y-0 z-20 w-[var(--page-side-width)] border shadow-sm transition-transform duration-200 ease-out', this.isDrawer() &&
|
|
593
|
+
position === 'left' &&
|
|
594
|
+
(sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isDrawer() &&
|
|
595
|
+
position === 'right' &&
|
|
596
|
+
(sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.isOverlay() &&
|
|
597
|
+
'absolute inset-y-0 z-30 w-[var(--page-side-width)] border shadow-md transition-transform duration-200 ease-out', this.isOverlay() &&
|
|
598
|
+
position === 'left' &&
|
|
599
|
+
(sideOpen ? 'left-0 translate-x-0' : 'left-0 -translate-x-full'), this.isOverlay() &&
|
|
600
|
+
position === 'right' &&
|
|
601
|
+
(sideOpen ? 'right-0 translate-x-0' : 'right-0 translate-x-full'), this.class());
|
|
289
602
|
}, /* @ts-ignore */
|
|
290
603
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
291
604
|
constructor() {
|
|
605
|
+
setupOverlayFocusManagement({
|
|
606
|
+
document: this.document,
|
|
607
|
+
host: this.host.nativeElement,
|
|
608
|
+
isOpen: this.page.sideOpen,
|
|
609
|
+
isInteractive: this.isInteractive,
|
|
610
|
+
});
|
|
292
611
|
effect(() => {
|
|
293
612
|
this.page.registerSide({
|
|
294
613
|
mode: this.resolvedMode(),
|
|
@@ -320,14 +639,13 @@ class PageSideComponent {
|
|
|
320
639
|
onCleanup(() => defaultView.removeEventListener('keydown', handler));
|
|
321
640
|
});
|
|
322
641
|
}
|
|
323
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
324
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
642
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
643
|
+
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 });
|
|
325
644
|
}
|
|
326
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
645
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideComponent, decorators: [{
|
|
327
646
|
type: Component,
|
|
328
647
|
args: [{
|
|
329
648
|
selector: 'PageSide',
|
|
330
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
331
649
|
host: {
|
|
332
650
|
'[class]': 'classes()',
|
|
333
651
|
'[attr.id]': 'resolvedId',
|
|
@@ -336,11 +654,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
336
654
|
'[attr.data-page-side-open]': 'page.sideOpen()',
|
|
337
655
|
'[attr.data-page-position]': 'resolvedPosition()',
|
|
338
656
|
'[attr.aria-hidden]': 'ariaHidden()',
|
|
657
|
+
'[attr.inert]': 'inertWhenClosed()',
|
|
658
|
+
'[attr.role]': 'dialogRole()',
|
|
659
|
+
'[attr.aria-modal]': 'ariaModal()',
|
|
660
|
+
'[attr.aria-label]': 'dialogLabel()',
|
|
661
|
+
'[attr.tabindex]': 'dialogTabindex()',
|
|
339
662
|
'[style.--page-side-width]': 'resolvedWidth()',
|
|
340
663
|
},
|
|
341
664
|
template: `<ng-content />`,
|
|
342
665
|
}]
|
|
343
|
-
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closeOnEsc: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEsc", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
|
|
666
|
+
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closeOnEsc: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEsc", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
|
|
344
667
|
|
|
345
668
|
/** Penghitung instance untuk id unik tiap apps-launcher (hindari bentrok `claimId` saat beberapa `Page` hidup). */
|
|
346
669
|
let pageAppsLauncherInstanceId = 0;
|
|
@@ -353,6 +676,8 @@ class PageComponent {
|
|
|
353
676
|
...(ngDevMode ? [{ debugName: "projectedSide" }] : /* istanbul ignore next */ []));
|
|
354
677
|
projectedHeader = contentChild(PageHeaderComponent, /* @ts-ignore */
|
|
355
678
|
...(ngDevMode ? [{ debugName: "projectedHeader" }] : /* istanbul ignore next */ []));
|
|
679
|
+
projectedFilter = contentChild(PageFilterComponent, /* @ts-ignore */
|
|
680
|
+
...(ngDevMode ? [{ debugName: "projectedFilter" }] : /* istanbul ignore next */ []));
|
|
356
681
|
variant = input(PAGE_DEFAULT_VARIANT, /* @ts-ignore */
|
|
357
682
|
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
358
683
|
height = input(PAGE_DEFAULT_HEIGHT, /* @ts-ignore */
|
|
@@ -370,9 +695,13 @@ class PageComponent {
|
|
|
370
695
|
...(ngDevMode ? [{ debugName: "sideOpen" }] : /* istanbul ignore next */ []));
|
|
371
696
|
sideWidth = input(PAGE_DEFAULT_SIDE_WIDTH, /* @ts-ignore */
|
|
372
697
|
...(ngDevMode ? [{ debugName: "sideWidth" }] : /* istanbul ignore next */ []));
|
|
698
|
+
/** Controlled state untuk `PageFilter` drawer/overlay. `null` = uncontrolled (dikelola toggle/backdrop/Esc). */
|
|
699
|
+
filterOpen = input(null, /* @ts-ignore */
|
|
700
|
+
...(ngDevMode ? [{ debugName: "filterOpen" }] : /* istanbul ignore next */ []));
|
|
373
701
|
class = input('', /* @ts-ignore */
|
|
374
702
|
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
375
703
|
sideOpenChange = output();
|
|
704
|
+
filterOpenChange = output();
|
|
376
705
|
/** Saat layout `empty`, munculkan tombol apps (flyout main navigation) di pojok kanan-atas. Set `false` untuk menonaktifkan. */
|
|
377
706
|
appsLauncher = input(true, { ...(ngDevMode ? { debugName: "appsLauncher" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
378
707
|
/** Id navigasi yang disurface oleh apps-launcher (default `main`). */
|
|
@@ -394,7 +723,29 @@ class PageComponent {
|
|
|
394
723
|
...(ngDevMode ? [{ debugName: "isLeftSide" }] : /* istanbul ignore next */ []));
|
|
395
724
|
isRightSide = computed(() => this.resolvedPosition() === 'right', /* @ts-ignore */
|
|
396
725
|
...(ngDevMode ? [{ debugName: "isRightSide" }] : /* istanbul ignore next */ []));
|
|
397
|
-
|
|
726
|
+
// Filter — resolusi dibaca langsung dari `PageFilter` yang diprojeksikan (reactive content query).
|
|
727
|
+
// Resolusi filter dibaca dari `PageFilter` yang diprojeksikan, lalu fall back ke page-state
|
|
728
|
+
// (diisi `registerFilter`), lalu default — pola seragam agar Page & PageFilter selalu sepakat.
|
|
729
|
+
hasFilter = computed(() => this.projectedFilter() !== undefined, /* @ts-ignore */
|
|
730
|
+
...(ngDevMode ? [{ debugName: "hasFilter" }] : /* istanbul ignore next */ []));
|
|
731
|
+
filterPlacement = computed(() => this.projectedFilter()?.placement() ??
|
|
732
|
+
this.page.filterPlacement() ??
|
|
733
|
+
PAGE_DEFAULT_FILTER_PLACEMENT, /* @ts-ignore */
|
|
734
|
+
...(ngDevMode ? [{ debugName: "filterPlacement" }] : /* istanbul ignore next */ []));
|
|
735
|
+
/** Mode efektif: `stacked` selalu `sticky`; `side` memakai mode pada `PageFilter`. */
|
|
736
|
+
filterMode = computed(() => this.filterPlacement() === 'stacked'
|
|
737
|
+
? 'sticky'
|
|
738
|
+
: (this.projectedFilter()?.mode() ?? this.page.filterMode()), /* @ts-ignore */
|
|
739
|
+
...(ngDevMode ? [{ debugName: "filterMode" }] : /* istanbul ignore next */ []));
|
|
740
|
+
filterPosition = computed(() => this.projectedFilter()?.position() ?? this.page.filterPosition(), /* @ts-ignore */
|
|
741
|
+
...(ngDevMode ? [{ debugName: "filterPosition" }] : /* istanbul ignore next */ []));
|
|
742
|
+
filterWidthVar = computed(() => this.projectedFilter()?.width() ?? this.page.filterWidth() ?? PAGE_DEFAULT_FILTER_WIDTH, /* @ts-ignore */
|
|
743
|
+
...(ngDevMode ? [{ debugName: "filterWidthVar" }] : /* istanbul ignore next */ []));
|
|
744
|
+
showsSideOverlayBackdrop = computed(() => this.variant() === 'side' && this.resolvedSideMode() === 'overlay' && this.page.sideOpen(), /* @ts-ignore */
|
|
745
|
+
...(ngDevMode ? [{ debugName: "showsSideOverlayBackdrop" }] : /* istanbul ignore next */ []));
|
|
746
|
+
showsFilterOverlayBackdrop = computed(() => this.hasFilter() && this.filterMode() === 'overlay' && this.page.filterOpen(), /* @ts-ignore */
|
|
747
|
+
...(ngDevMode ? [{ debugName: "showsFilterOverlayBackdrop" }] : /* istanbul ignore next */ []));
|
|
748
|
+
showsOverlayBackdrop = computed(() => this.showsSideOverlayBackdrop() || this.showsFilterOverlayBackdrop(), /* @ts-ignore */
|
|
398
749
|
...(ngDevMode ? [{ debugName: "showsOverlayBackdrop" }] : /* istanbul ignore next */ []));
|
|
399
750
|
/** Signal data untuk id yang dipilih; di-recompute hanya saat `appsNavId` berubah. */
|
|
400
751
|
appsNavSource = computed(() => this.navigation?.data(this.appsNavId()) ?? null, /* @ts-ignore */
|
|
@@ -437,6 +788,35 @@ class PageComponent {
|
|
|
437
788
|
return cn('relative min-w-0', this.scroll() === 'content' && 'min-h-0');
|
|
438
789
|
}, /* @ts-ignore */
|
|
439
790
|
...(ngDevMode ? [{ debugName: "bodyClasses" }] : /* istanbul ignore next */ []));
|
|
791
|
+
/**
|
|
792
|
+
* Wrapper di sekitar `PageFilter` + `PageContent`. Tanpa filter, memakai `display: contents`
|
|
793
|
+
* agar transparan (perilaku body lama dipertahankan persis). Dengan filter, menjadi grid/flex
|
|
794
|
+
* yang menata filter (baris untuk `stacked`, kolom untuk `side`); drawer/overlay menjadikannya
|
|
795
|
+
* `relative` sebagai positioning context untuk panel yang mengambang.
|
|
796
|
+
*/
|
|
797
|
+
contentRegionClasses = computed(() => {
|
|
798
|
+
if (!this.hasFilter()) {
|
|
799
|
+
return 'contents';
|
|
800
|
+
}
|
|
801
|
+
const scroll = this.scroll();
|
|
802
|
+
const mode = this.filterMode();
|
|
803
|
+
// drawer/overlay (side only): content mengisi penuh, filter mengambang di atasnya.
|
|
804
|
+
if (mode !== 'sticky') {
|
|
805
|
+
return cn('relative min-w-0', scroll === 'content' && 'min-h-0 h-full');
|
|
806
|
+
}
|
|
807
|
+
if (this.filterPlacement() === 'stacked') {
|
|
808
|
+
// content scroll: filter (auto) di atas, content (1fr) mengisi sisa tinggi & scroll sendiri.
|
|
809
|
+
// page scroll: aliran block biasa; filter dipin via `sticky top-0` pada elemennya.
|
|
810
|
+
return scroll === 'content'
|
|
811
|
+
? 'grid min-w-0 min-h-0 h-full grid-rows-[auto_minmax(0,1fr)]'
|
|
812
|
+
: 'block min-w-0';
|
|
813
|
+
}
|
|
814
|
+
// side + sticky: filter sebagai kolom; posisi kanan dibalik via `order-last` pada elemen filter.
|
|
815
|
+
return cn('grid min-w-0', scroll === 'content' && 'min-h-0 h-full', this.filterPosition() === 'left'
|
|
816
|
+
? 'grid-cols-[var(--page-filter-width)_minmax(0,1fr)]'
|
|
817
|
+
: 'grid-cols-[minmax(0,1fr)_var(--page-filter-width)]');
|
|
818
|
+
}, /* @ts-ignore */
|
|
819
|
+
...(ngDevMode ? [{ debugName: "contentRegionClasses" }] : /* istanbul ignore next */ []));
|
|
440
820
|
constructor() {
|
|
441
821
|
effect(() => {
|
|
442
822
|
this.page.registerRoot({
|
|
@@ -452,6 +832,9 @@ class PageComponent {
|
|
|
452
832
|
effect(() => {
|
|
453
833
|
this.page.setControlledSideOpen(this.sideOpen());
|
|
454
834
|
});
|
|
835
|
+
effect(() => {
|
|
836
|
+
this.page.setControlledFilterOpen(this.filterOpen());
|
|
837
|
+
});
|
|
455
838
|
// Header memesan ruang kanan hanya saat tombol apps benar-benar mengambang di atas header.
|
|
456
839
|
effect(() => {
|
|
457
840
|
this.page.setAppsLauncherReserve(this.showsAppsLauncher() && this.projectedHeader() !== undefined);
|
|
@@ -463,31 +846,47 @@ class PageComponent {
|
|
|
463
846
|
}
|
|
464
847
|
this.sideOpenChange.emit(this.page.sideOpenRequest() ?? false);
|
|
465
848
|
});
|
|
849
|
+
effect(() => {
|
|
850
|
+
const requestVersion = this.page.filterOpenRequestVersion();
|
|
851
|
+
if (requestVersion === 0) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
this.filterOpenChange.emit(this.page.filterOpenRequest() ?? false);
|
|
855
|
+
});
|
|
466
856
|
}
|
|
467
857
|
handleBackdropClick() {
|
|
468
|
-
this.
|
|
858
|
+
if (this.showsSideOverlayBackdrop()) {
|
|
859
|
+
this.page.closeSide();
|
|
860
|
+
}
|
|
861
|
+
if (this.showsFilterOverlayBackdrop()) {
|
|
862
|
+
this.page.closeFilter();
|
|
863
|
+
}
|
|
469
864
|
}
|
|
470
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
471
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.
|
|
865
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
866
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: PageComponent, isStandalone: true, selector: "Page", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, scroll: { classPropertyName: "scroll", publicName: "scroll", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, sideMode: { classPropertyName: "sideMode", publicName: "sideMode", isSignal: true, isRequired: false, transformFunction: null }, sideOpen: { classPropertyName: "sideOpen", publicName: "sideOpen", isSignal: true, isRequired: false, transformFunction: null }, sideWidth: { classPropertyName: "sideWidth", publicName: "sideWidth", isSignal: true, isRequired: false, transformFunction: null }, filterOpen: { classPropertyName: "filterOpen", publicName: "filterOpen", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, appsLauncher: { classPropertyName: "appsLauncher", publicName: "appsLauncher", isSignal: true, isRequired: false, transformFunction: null }, appsNavId: { classPropertyName: "appsNavId", publicName: "appsNavId", isSignal: true, isRequired: false, transformFunction: null }, appsIcon: { classPropertyName: "appsIcon", publicName: "appsIcon", isSignal: true, isRequired: false, transformFunction: null }, appsLabel: { classPropertyName: "appsLabel", publicName: "appsLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sideOpenChange: "sideOpenChange", filterOpenChange: "filterOpenChange" }, host: { properties: { "class": "hostClasses()", "attr.data-page-variant": "variant()", "attr.data-page-height": "height()", "attr.data-page-scroll": "scroll()", "attr.data-page-appearance": "appearance()", "attr.data-page-position": "resolvedPosition()", "attr.data-page-side-mode": "resolvedSideMode()", "attr.data-page-side-open": "page.sideOpen()", "attr.data-page-filter-placement": "hasFilter() ? filterPlacement() : null", "attr.data-page-filter-mode": "hasFilter() ? filterMode() : null", "attr.data-page-filter-open": "hasFilter() ? page.filterOpen() : null", "style.--page-side-width": "sideWidth()", "style.--page-filter-width": "filterWidthVar()" } }, providers: [PageStateService], queries: [{ propertyName: "projectedSide", first: true, predicate: PageSideComponent, descendants: true, isSignal: true }, { propertyName: "projectedHeader", first: true, predicate: PageHeaderComponent, descendants: true, isSignal: true }, { propertyName: "projectedFilter", first: true, predicate: PageFilterComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
472
867
|
@if (showsOverlayBackdrop()) {
|
|
473
868
|
<button
|
|
474
869
|
type="button"
|
|
475
|
-
aria-label="Close
|
|
870
|
+
aria-label="Close overlay panel"
|
|
476
871
|
data-page-overlay-backdrop
|
|
477
872
|
class="absolute inset-0 z-20 bg-[hsl(var(--overlay-backdrop))]"
|
|
478
|
-
(click)="handleBackdropClick()"
|
|
873
|
+
(click)="handleBackdropClick()"
|
|
874
|
+
></button>
|
|
479
875
|
}
|
|
480
876
|
|
|
481
877
|
<div [class]="shellClasses()">
|
|
482
878
|
<ng-content select="PageHeader" />
|
|
483
|
-
<ng-content select="PageSideToggle" />
|
|
879
|
+
<ng-content select="PageSideToggle, PageFilterToggle" />
|
|
484
880
|
|
|
485
|
-
<div [class]="bodyClasses()">
|
|
881
|
+
<div [class]="bodyClasses()" data-page-body>
|
|
486
882
|
@if (variant() === 'side') {
|
|
487
883
|
<ng-content select="PageSide" />
|
|
488
884
|
}
|
|
489
885
|
|
|
490
|
-
<
|
|
886
|
+
<div [class]="contentRegionClasses()" data-page-content-region>
|
|
887
|
+
<ng-content select="PageFilter" />
|
|
888
|
+
<ng-content select="PageContent, PageDashboard" />
|
|
889
|
+
</div>
|
|
491
890
|
</div>
|
|
492
891
|
|
|
493
892
|
<ng-content select="PageFooter" />
|
|
@@ -498,13 +897,15 @@ class PageComponent {
|
|
|
498
897
|
[id]="launcherNavId"
|
|
499
898
|
[data]="appsNavData()"
|
|
500
899
|
ariaLabel="Application navigation"
|
|
501
|
-
[class]="appsLauncherHostClass()"
|
|
900
|
+
[class]="appsLauncherHostClass()"
|
|
901
|
+
>
|
|
502
902
|
<NavigationFlyout
|
|
503
903
|
[icon]="appsIcon()"
|
|
504
904
|
icon-only
|
|
505
905
|
trigger-variant="plain"
|
|
506
906
|
[nav-appearance]="appsLauncherAppearance()"
|
|
507
|
-
[label]="appsLabel()"
|
|
907
|
+
[label]="appsLabel()"
|
|
908
|
+
>
|
|
508
909
|
@if (appsBrand(); as brand) {
|
|
509
910
|
<NavigationHeader>
|
|
510
911
|
<LayoutBrand [brand]="brand" />
|
|
@@ -518,13 +919,12 @@ class PageComponent {
|
|
|
518
919
|
</NavigationFlyout>
|
|
519
920
|
</Navigation>
|
|
520
921
|
}
|
|
521
|
-
`, 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"] }]
|
|
922
|
+
`, 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"] }] });
|
|
522
923
|
}
|
|
523
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
924
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageComponent, decorators: [{
|
|
524
925
|
type: Component,
|
|
525
926
|
args: [{
|
|
526
927
|
selector: 'Page',
|
|
527
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
528
928
|
providers: [PageStateService],
|
|
529
929
|
imports: [
|
|
530
930
|
NavigationContainerComponent,
|
|
@@ -543,28 +943,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
543
943
|
'[attr.data-page-position]': 'resolvedPosition()',
|
|
544
944
|
'[attr.data-page-side-mode]': 'resolvedSideMode()',
|
|
545
945
|
'[attr.data-page-side-open]': 'page.sideOpen()',
|
|
946
|
+
'[attr.data-page-filter-placement]': 'hasFilter() ? filterPlacement() : null',
|
|
947
|
+
'[attr.data-page-filter-mode]': 'hasFilter() ? filterMode() : null',
|
|
948
|
+
'[attr.data-page-filter-open]': 'hasFilter() ? page.filterOpen() : null',
|
|
546
949
|
'[style.--page-side-width]': 'sideWidth()',
|
|
950
|
+
'[style.--page-filter-width]': 'filterWidthVar()',
|
|
547
951
|
},
|
|
548
952
|
template: `
|
|
549
953
|
@if (showsOverlayBackdrop()) {
|
|
550
954
|
<button
|
|
551
955
|
type="button"
|
|
552
|
-
aria-label="Close
|
|
956
|
+
aria-label="Close overlay panel"
|
|
553
957
|
data-page-overlay-backdrop
|
|
554
958
|
class="absolute inset-0 z-20 bg-[hsl(var(--overlay-backdrop))]"
|
|
555
|
-
(click)="handleBackdropClick()"
|
|
959
|
+
(click)="handleBackdropClick()"
|
|
960
|
+
></button>
|
|
556
961
|
}
|
|
557
962
|
|
|
558
963
|
<div [class]="shellClasses()">
|
|
559
964
|
<ng-content select="PageHeader" />
|
|
560
|
-
<ng-content select="PageSideToggle" />
|
|
965
|
+
<ng-content select="PageSideToggle, PageFilterToggle" />
|
|
561
966
|
|
|
562
|
-
<div [class]="bodyClasses()">
|
|
967
|
+
<div [class]="bodyClasses()" data-page-body>
|
|
563
968
|
@if (variant() === 'side') {
|
|
564
969
|
<ng-content select="PageSide" />
|
|
565
970
|
}
|
|
566
971
|
|
|
567
|
-
<
|
|
972
|
+
<div [class]="contentRegionClasses()" data-page-content-region>
|
|
973
|
+
<ng-content select="PageFilter" />
|
|
974
|
+
<ng-content select="PageContent, PageDashboard" />
|
|
975
|
+
</div>
|
|
568
976
|
</div>
|
|
569
977
|
|
|
570
978
|
<ng-content select="PageFooter" />
|
|
@@ -575,13 +983,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
575
983
|
[id]="launcherNavId"
|
|
576
984
|
[data]="appsNavData()"
|
|
577
985
|
ariaLabel="Application navigation"
|
|
578
|
-
[class]="appsLauncherHostClass()"
|
|
986
|
+
[class]="appsLauncherHostClass()"
|
|
987
|
+
>
|
|
579
988
|
<NavigationFlyout
|
|
580
989
|
[icon]="appsIcon()"
|
|
581
990
|
icon-only
|
|
582
991
|
trigger-variant="plain"
|
|
583
992
|
[nav-appearance]="appsLauncherAppearance()"
|
|
584
|
-
[label]="appsLabel()"
|
|
993
|
+
[label]="appsLabel()"
|
|
994
|
+
>
|
|
585
995
|
@if (appsBrand(); as brand) {
|
|
586
996
|
<NavigationHeader>
|
|
587
997
|
<LayoutBrand [brand]="brand" />
|
|
@@ -597,7 +1007,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
597
1007
|
}
|
|
598
1008
|
`,
|
|
599
1009
|
}]
|
|
600
|
-
}], ctorParameters: () => [], propDecorators: { projectedSide: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PageSideComponent), { isSignal: true }] }], projectedHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PageHeaderComponent), { isSignal: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], scroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "scroll", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], sideMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideMode", required: false }] }], sideOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOpen", required: false }] }], sideWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideWidth", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], sideOpenChange: [{ type: i0.Output, args: ["sideOpenChange"] }], appsLauncher: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsLauncher", required: false }] }], appsNavId: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsNavId", required: false }] }], appsIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsIcon", required: false }] }], appsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsLabel", required: false }] }] } });
|
|
1010
|
+
}], ctorParameters: () => [], propDecorators: { projectedSide: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PageSideComponent), { isSignal: true }] }], projectedHeader: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PageHeaderComponent), { isSignal: true }] }], projectedFilter: [{ type: i0.ContentChild, args: [i0.forwardRef(() => PageFilterComponent), { isSignal: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], scroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "scroll", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], sideMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideMode", required: false }] }], sideOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOpen", required: false }] }], sideWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideWidth", required: false }] }], filterOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterOpen", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], sideOpenChange: [{ type: i0.Output, args: ["sideOpenChange"] }], filterOpenChange: [{ type: i0.Output, args: ["filterOpenChange"] }], appsLauncher: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsLauncher", required: false }] }], appsNavId: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsNavId", required: false }] }], appsIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsIcon", required: false }] }], appsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "appsLabel", required: false }] }] } });
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* `PageFilterToggle` — tombol untuk membuka/menutup `PageFilter` dalam mode `drawer`/`overlay`.
|
|
1014
|
+
* Menerima projected content untuk label/ikon kustom; fallback memakai ikon funnel bawaan.
|
|
1015
|
+
*/
|
|
1016
|
+
class PageFilterToggleComponent {
|
|
1017
|
+
page = inject(PageStateService);
|
|
1018
|
+
ariaLabel = input('Toggle page filter', /* @ts-ignore */
|
|
1019
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
1020
|
+
class = input('', /* @ts-ignore */
|
|
1021
|
+
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
1022
|
+
toggled = output();
|
|
1023
|
+
hostClasses = computed(() => cn('inline-flex shrink-0', this.class()), /* @ts-ignore */
|
|
1024
|
+
...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
1025
|
+
buttonClasses = computed(() => cn('inline-flex h-9 min-w-9 items-center justify-center rounded-md border border-border bg-background px-3 text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2'), /* @ts-ignore */
|
|
1026
|
+
...(ngDevMode ? [{ debugName: "buttonClasses" }] : /* istanbul ignore next */ []));
|
|
1027
|
+
handleClick() {
|
|
1028
|
+
this.toggled.emit(this.page.toggleFilter());
|
|
1029
|
+
}
|
|
1030
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1031
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageFilterToggleComponent, isStandalone: true, selector: "PageFilterToggle", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
|
|
1032
|
+
<button
|
|
1033
|
+
type="button"
|
|
1034
|
+
[class]="buttonClasses()"
|
|
1035
|
+
[attr.aria-label]="ariaLabel()"
|
|
1036
|
+
[attr.aria-controls]="page.filterId()"
|
|
1037
|
+
[attr.aria-expanded]="page.filterOpen()"
|
|
1038
|
+
(click)="handleClick()"
|
|
1039
|
+
>
|
|
1040
|
+
<ng-content>
|
|
1041
|
+
<svg
|
|
1042
|
+
aria-hidden="true"
|
|
1043
|
+
viewBox="0 0 24 24"
|
|
1044
|
+
class="h-4 w-4"
|
|
1045
|
+
fill="none"
|
|
1046
|
+
stroke="currentColor"
|
|
1047
|
+
stroke-width="2"
|
|
1048
|
+
stroke-linecap="round"
|
|
1049
|
+
stroke-linejoin="round"
|
|
1050
|
+
>
|
|
1051
|
+
<path d="M3 5h18l-7 8v5l-4 2v-7z" />
|
|
1052
|
+
</svg>
|
|
1053
|
+
</ng-content>
|
|
1054
|
+
</button>
|
|
1055
|
+
`, isInline: true });
|
|
1056
|
+
}
|
|
1057
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageFilterToggleComponent, decorators: [{
|
|
1058
|
+
type: Component,
|
|
1059
|
+
args: [{
|
|
1060
|
+
selector: 'PageFilterToggle',
|
|
1061
|
+
host: {
|
|
1062
|
+
'[class]': 'hostClasses()',
|
|
1063
|
+
},
|
|
1064
|
+
template: `
|
|
1065
|
+
<button
|
|
1066
|
+
type="button"
|
|
1067
|
+
[class]="buttonClasses()"
|
|
1068
|
+
[attr.aria-label]="ariaLabel()"
|
|
1069
|
+
[attr.aria-controls]="page.filterId()"
|
|
1070
|
+
[attr.aria-expanded]="page.filterOpen()"
|
|
1071
|
+
(click)="handleClick()"
|
|
1072
|
+
>
|
|
1073
|
+
<ng-content>
|
|
1074
|
+
<svg
|
|
1075
|
+
aria-hidden="true"
|
|
1076
|
+
viewBox="0 0 24 24"
|
|
1077
|
+
class="h-4 w-4"
|
|
1078
|
+
fill="none"
|
|
1079
|
+
stroke="currentColor"
|
|
1080
|
+
stroke-width="2"
|
|
1081
|
+
stroke-linecap="round"
|
|
1082
|
+
stroke-linejoin="round"
|
|
1083
|
+
>
|
|
1084
|
+
<path d="M3 5h18l-7 8v5l-4 2v-7z" />
|
|
1085
|
+
</svg>
|
|
1086
|
+
</ng-content>
|
|
1087
|
+
</button>
|
|
1088
|
+
`,
|
|
1089
|
+
}]
|
|
1090
|
+
}], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], toggled: [{ type: i0.Output, args: ["toggled"] }] } });
|
|
601
1091
|
|
|
602
1092
|
class PageSideToggleComponent {
|
|
603
1093
|
page = inject(PageStateService);
|
|
@@ -613,26 +1103,26 @@ class PageSideToggleComponent {
|
|
|
613
1103
|
handleClick() {
|
|
614
1104
|
this.toggled.emit(this.page.toggleSide());
|
|
615
1105
|
}
|
|
616
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.
|
|
617
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.
|
|
1106
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1107
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.4", type: PageSideToggleComponent, isStandalone: true, selector: "PageSideToggle", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
|
|
618
1108
|
<button
|
|
619
1109
|
type="button"
|
|
620
1110
|
[class]="buttonClasses()"
|
|
621
1111
|
[attr.aria-label]="ariaLabel()"
|
|
622
1112
|
[attr.aria-controls]="page.sideId()"
|
|
623
1113
|
[attr.aria-expanded]="page.sideOpen()"
|
|
624
|
-
(click)="handleClick()"
|
|
1114
|
+
(click)="handleClick()"
|
|
1115
|
+
>
|
|
625
1116
|
<ng-content>
|
|
626
1117
|
<span aria-hidden="true" class="text-lg leading-none">☰</span>
|
|
627
1118
|
</ng-content>
|
|
628
1119
|
</button>
|
|
629
|
-
`, isInline: true
|
|
1120
|
+
`, isInline: true });
|
|
630
1121
|
}
|
|
631
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.
|
|
1122
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PageSideToggleComponent, decorators: [{
|
|
632
1123
|
type: Component,
|
|
633
1124
|
args: [{
|
|
634
1125
|
selector: 'PageSideToggle',
|
|
635
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
636
1126
|
host: {
|
|
637
1127
|
'[class]': 'hostClasses()',
|
|
638
1128
|
},
|
|
@@ -643,7 +1133,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
643
1133
|
[attr.aria-label]="ariaLabel()"
|
|
644
1134
|
[attr.aria-controls]="page.sideId()"
|
|
645
1135
|
[attr.aria-expanded]="page.sideOpen()"
|
|
646
|
-
(click)="handleClick()"
|
|
1136
|
+
(click)="handleClick()"
|
|
1137
|
+
>
|
|
647
1138
|
<ng-content>
|
|
648
1139
|
<span aria-hidden="true" class="text-lg leading-none">☰</span>
|
|
649
1140
|
</ng-content>
|
|
@@ -656,4 +1147,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.3", ngImpor
|
|
|
656
1147
|
* Generated bundle index. Do not edit.
|
|
657
1148
|
*/
|
|
658
1149
|
|
|
659
|
-
export { PAGE_APPEARANCES, PAGE_DEFAULT_APPEARANCE, PAGE_DEFAULT_HEIGHT, PAGE_DEFAULT_SCROLL, PAGE_DEFAULT_SIDE_MODE, PAGE_DEFAULT_SIDE_POSITION, PAGE_DEFAULT_SIDE_WIDTH, PAGE_DEFAULT_VARIANT, PAGE_HEIGHT_VALUES, PAGE_SCROLL_VALUES, PAGE_SIDE_MODES, PAGE_SIDE_POSITIONS, PAGE_VARIANTS, PageComponent, PageContentComponent, PageDashboardComponent, PageFooterComponent, PageHeaderComponent, PageSideComponent, PageSideToggleComponent, isUiPageAppearance, isUiPageHeight, isUiPageScroll, isUiPageSideMode, isUiPageSidePosition, isUiPageVariant };
|
|
1150
|
+
export { PAGE_APPEARANCES, PAGE_DEFAULT_APPEARANCE, PAGE_DEFAULT_FILTER_MODE, PAGE_DEFAULT_FILTER_PLACEMENT, PAGE_DEFAULT_FILTER_POSITION, PAGE_DEFAULT_FILTER_WIDTH, PAGE_DEFAULT_HEIGHT, PAGE_DEFAULT_SCROLL, PAGE_DEFAULT_SIDE_MODE, PAGE_DEFAULT_SIDE_POSITION, PAGE_DEFAULT_SIDE_WIDTH, PAGE_DEFAULT_VARIANT, PAGE_FILTER_PLACEMENTS, PAGE_HEIGHT_VALUES, PAGE_SCROLL_VALUES, PAGE_SIDE_MODES, PAGE_SIDE_POSITIONS, PAGE_VARIANTS, PageComponent, PageContentComponent, PageDashboardComponent, PageFilterComponent, PageFilterToggleComponent, PageFooterComponent, PageHeaderComponent, PageSideComponent, PageSideToggleComponent, isUiPageAppearance, isUiPageFilterPlacement, isUiPageHeight, isUiPageScroll, isUiPageSideMode, isUiPageSidePosition, isUiPageVariant };
|