@danjelp/ngx-app-shell 1.0.0
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/LICENSE +21 -0
- package/README.md +559 -0
- package/fesm2022/danjelp-ngx-app-shell.mjs +1944 -0
- package/fesm2022/danjelp-ngx-app-shell.mjs.map +1 -0
- package/package.json +55 -0
- package/src/lib/styles/_tokens.scss +340 -0
- package/styles/_tokens.scss +3 -0
- package/types/danjelp-ngx-app-shell.d.ts +1210 -0
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { Signal, InjectionToken, EnvironmentProviders, ElementRef, TemplateRef } from '@angular/core';
|
|
3
|
+
import * as _danjelp_ngx_app_shell from '@danjelp/ngx-app-shell';
|
|
4
|
+
import { ActivatedRoute } from '@angular/router';
|
|
5
|
+
|
|
6
|
+
/** Ordered size buckets. Order matters: `sizeRank()` relies on the index. */
|
|
7
|
+
declare const SHELL_SIZES: readonly ["xs", "sm", "md", "lg", "xl"];
|
|
8
|
+
type ShellSize = (typeof SHELL_SIZES)[number];
|
|
9
|
+
/** Minimum *container* width (px) at which each bucket starts. */
|
|
10
|
+
type ShellBreakpoints = Readonly<Record<ShellSize, number>>;
|
|
11
|
+
/**
|
|
12
|
+
* `topbar-full` – topbar spans the full width, sidebar starts below it.
|
|
13
|
+
* `sidebar-full` – sidebar spans the full height, topbar starts next to it.
|
|
14
|
+
* `stacked` – topbar *and* footer span the full width, sidebar sits between.
|
|
15
|
+
* `inset` – topbar, main and footer share one rounded card; the sidebar
|
|
16
|
+
* sits directly on the shell background.
|
|
17
|
+
*/
|
|
18
|
+
type ShellLayout = 'topbar-full' | 'sidebar-full' | 'stacked' | 'inset';
|
|
19
|
+
/**
|
|
20
|
+
* `main` = app-like, only the content area scrolls.
|
|
21
|
+
* `page` = the document scrolls — or, in the `inset` layout, the card itself
|
|
22
|
+
* scrolls with the topbar sticky at its top.
|
|
23
|
+
*/
|
|
24
|
+
type ShellScrollMode = 'main' | 'page';
|
|
25
|
+
/** What the sidebar currently *is*. Derived state, never set directly. */
|
|
26
|
+
type SidebarMode = 'expanded' | 'collapsed' | 'overlay' | 'hidden';
|
|
27
|
+
/**
|
|
28
|
+
* What the user *asked for* on a screen wide enough for a persistent sidebar.
|
|
29
|
+
* `auto` hands the decision back to the behaviour. The drawer's open/closed
|
|
30
|
+
* state is deliberately not an intent: it is transient and never persisted.
|
|
31
|
+
*/
|
|
32
|
+
type SidebarIntent = 'auto' | 'expanded' | 'collapsed' | 'hidden';
|
|
33
|
+
/**
|
|
34
|
+
* When the topbar renders its built-in sidebar toggle.
|
|
35
|
+
* `auto` = only when the sidebar cannot be reached otherwise (drawer or hidden).
|
|
36
|
+
*/
|
|
37
|
+
type SidebarToggleVisibility = 'always' | 'auto' | 'never';
|
|
38
|
+
/** Fully resolved sidebar state — the single source of truth for template + CSS. */
|
|
39
|
+
interface SidebarState {
|
|
40
|
+
readonly mode: SidebarMode;
|
|
41
|
+
/** Is the panel visible at all (open drawer, or an in-flow rail/panel)? */
|
|
42
|
+
readonly open: boolean;
|
|
43
|
+
/** Does it trap focus and need a scrim? True for the open overlay drawer. */
|
|
44
|
+
readonly modal: boolean;
|
|
45
|
+
/** Should item labels / group headings render? False on an icon rail. */
|
|
46
|
+
readonly labels: boolean;
|
|
47
|
+
/** Collapsed rail temporarily widened by hover/focus peek. */
|
|
48
|
+
readonly peeked: boolean;
|
|
49
|
+
/** Width reserved in the shell grid. `0px` when the panel floats over content. */
|
|
50
|
+
readonly trackWidth: string;
|
|
51
|
+
/** Width of the panel element itself. */
|
|
52
|
+
readonly panelWidth: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Slot names the built-in regions render. Extra names are allowed — the
|
|
56
|
+
* `(string & {})` member keeps autocompletion for the known ones while letting
|
|
57
|
+
* you invent your own and render them with `<shell-slot-outlet>`.
|
|
58
|
+
*/
|
|
59
|
+
type ShellSlotName = 'topbar-start' | 'brand' | 'primary-nav' | 'title' | 'search' | 'actions' | 'account' | 'topbar-end' | 'sidebar-header' | 'sidebar-action' | 'sidebar-search' | 'sidebar-nav' | 'sidebar-secondary' | 'sidebar-footer' | 'breadcrumb' | 'subheader-start' | 'subheader-end' | 'footer-start' | 'footer' | 'footer-end' | (string & {});
|
|
60
|
+
/** Imperative surface handed to slot templates and to `[shellSidebarToggle]`. */
|
|
61
|
+
interface ShellApi {
|
|
62
|
+
readonly size: Signal<ShellSize>;
|
|
63
|
+
readonly sidebar: Signal<SidebarState>;
|
|
64
|
+
readonly sidebarIntent: Signal<SidebarIntent>;
|
|
65
|
+
toggleSidebar(): void;
|
|
66
|
+
openSidebar(): void;
|
|
67
|
+
closeSidebar(): void;
|
|
68
|
+
setSidebarIntent(intent: SidebarIntent): void;
|
|
69
|
+
}
|
|
70
|
+
/** `let-` context available inside every `<ng-template shellSlot="…">`. */
|
|
71
|
+
interface ShellSlotContext {
|
|
72
|
+
/** `let-sidebar` / `let-x` — resolved sidebar state. */
|
|
73
|
+
readonly $implicit: SidebarState;
|
|
74
|
+
readonly sidebar: SidebarState;
|
|
75
|
+
/** Size bucket of the whole shell. */
|
|
76
|
+
readonly size: ShellSize;
|
|
77
|
+
/** Size bucket of the region rendering this slot (topbar, sidebar, footer…). */
|
|
78
|
+
readonly region: ShellSize;
|
|
79
|
+
/** True when rendered inside a ⋯ overflow menu (topbar or breadcrumb row). */
|
|
80
|
+
readonly overflow: boolean;
|
|
81
|
+
readonly shell: ShellApi;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Everything a behaviour is allowed to look at. Pure in, pure out. */
|
|
85
|
+
interface SidebarContext {
|
|
86
|
+
/** Measured width of the shell in px, or `null` before the first measurement. */
|
|
87
|
+
readonly width: number | null;
|
|
88
|
+
/** Resolved size bucket of the shell container. */
|
|
89
|
+
readonly size: ShellSize;
|
|
90
|
+
/** The user's persistent preference for wide screens. */
|
|
91
|
+
readonly intent: SidebarIntent;
|
|
92
|
+
/** Transient open state, meaningful only when the behaviour resolves a drawer. */
|
|
93
|
+
readonly drawerOpen: boolean;
|
|
94
|
+
/** Pointer/focus is dwelling on the collapsed rail. */
|
|
95
|
+
readonly peeking: boolean;
|
|
96
|
+
readonly config: ShellConfig;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The collapse policy, isolated behind one interface so an app can change how
|
|
100
|
+
* the sidebar behaves without forking the layout. Swap it with
|
|
101
|
+
* `provideAppShell(config, { behavior })` or at runtime with
|
|
102
|
+
* `ShellStore.setBehavior()`.
|
|
103
|
+
*
|
|
104
|
+
* `resolve` must be a pure function of the context: it runs inside a `computed`.
|
|
105
|
+
* Build the returned state with `drawerState`, `railState`, `panelState` and
|
|
106
|
+
* `hiddenState` so every behaviour produces consistent widths and flags.
|
|
107
|
+
*/
|
|
108
|
+
interface SidebarBehavior {
|
|
109
|
+
resolve(context: SidebarContext): SidebarState;
|
|
110
|
+
/**
|
|
111
|
+
* Intent produced by activating a toggle while the sidebar is *not* a drawer
|
|
112
|
+
* (drawers simply open/close). Default: expanded ⇄ collapsed.
|
|
113
|
+
*/
|
|
114
|
+
nextIntent?(context: SidebarContext): SidebarIntent;
|
|
115
|
+
/** Should this intent survive a reload? Default: yes. */
|
|
116
|
+
persistIntent?(intent: SidebarIntent, context: SidebarContext): boolean;
|
|
117
|
+
}
|
|
118
|
+
declare const SIDEBAR_BEHAVIOR: InjectionToken<SidebarBehavior>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Topbar look. `elevated` trades the border for a shadow; `blur` and
|
|
122
|
+
* `transparent` only show through when content scrolls under the bar
|
|
123
|
+
* (`scroll: 'page'`).
|
|
124
|
+
*/
|
|
125
|
+
type TopbarAppearance = 'solid' | 'elevated' | 'blur' | 'transparent';
|
|
126
|
+
/** `floating` insets the panel as a rounded, shadowed card inside its track. */
|
|
127
|
+
type SidebarAppearance = 'solid' | 'floating' | 'borderless';
|
|
128
|
+
type FooterAppearance = 'solid' | 'minimal';
|
|
129
|
+
/** How the active nav item is marked. `none` = accent text only. */
|
|
130
|
+
type NavIndicator = 'pill' | 'bar' | 'none';
|
|
131
|
+
type DrawerMotion = 'slide' | 'fade' | 'scale' | 'none';
|
|
132
|
+
type MenuMotion = 'fade' | 'scale' | 'slide' | 'none';
|
|
133
|
+
type ScrimMotion = 'fade' | 'none';
|
|
134
|
+
/** `auto` = follow the page (see `ShellSubheaderConfig.enabled`); booleans force it. */
|
|
135
|
+
type SubheaderVisibility = 'auto' | boolean;
|
|
136
|
+
/** Breadcrumb row look. `transparent` blends into the page; `elevated` trades the rule for a shadow. */
|
|
137
|
+
type SubheaderAppearance = 'solid' | 'transparent' | 'elevated';
|
|
138
|
+
/** Glyph between breadcrumb levels. Always decorative: screen readers skip it. */
|
|
139
|
+
type BreadcrumbSeparator = 'chevron' | 'slash' | 'dot';
|
|
140
|
+
interface ShellSidebarConfig {
|
|
141
|
+
/** Width of the expanded panel. Any CSS length. */
|
|
142
|
+
readonly width: string;
|
|
143
|
+
/** Width of the icon rail. */
|
|
144
|
+
readonly collapsedWidth: string;
|
|
145
|
+
/** Width of the off-canvas drawer. */
|
|
146
|
+
readonly overlayWidth: string;
|
|
147
|
+
/** Starting intent. `auto` = let the behaviour decide from available space. */
|
|
148
|
+
readonly intent: SidebarIntent;
|
|
149
|
+
/** Below this bucket the panel collapses to a rail. */
|
|
150
|
+
readonly collapseBelow: ShellSize;
|
|
151
|
+
/** Below this bucket the panel becomes a modal drawer. */
|
|
152
|
+
readonly overlayBelow: ShellSize;
|
|
153
|
+
/** Widen the rail on hover/focus instead of showing flyouts. */
|
|
154
|
+
readonly peek: boolean;
|
|
155
|
+
/** Dwell time (ms) before a hover peeks the rail open. */
|
|
156
|
+
readonly peekDelay: number;
|
|
157
|
+
/** Close the drawer after a router navigation. */
|
|
158
|
+
readonly closeOnNavigate: boolean;
|
|
159
|
+
/** Let the user drag the expanded panel's edge to resize it. */
|
|
160
|
+
readonly resizable: boolean;
|
|
161
|
+
/** Resize bounds in px. */
|
|
162
|
+
readonly minWidth: number;
|
|
163
|
+
readonly maxWidth: number;
|
|
164
|
+
/**
|
|
165
|
+
* Key that toggles the sidebar together with Ctrl (Windows/Linux) or ⌘ (macOS),
|
|
166
|
+
* e.g. `'b'` for the VS Code style Ctrl+B. `null` disables the shortcut.
|
|
167
|
+
*/
|
|
168
|
+
readonly toggleShortcut: string | null;
|
|
169
|
+
/** Remember the user's intent and custom width across reloads. */
|
|
170
|
+
readonly persist: boolean;
|
|
171
|
+
/** Prefix for persisted keys: `<storageKey>.intent`, `<storageKey>.width`. */
|
|
172
|
+
readonly storageKey: string;
|
|
173
|
+
}
|
|
174
|
+
interface ShellTopbarConfig {
|
|
175
|
+
readonly enabled: boolean;
|
|
176
|
+
readonly sidebarToggle: SidebarToggleVisibility;
|
|
177
|
+
}
|
|
178
|
+
interface ShellFooterConfig {
|
|
179
|
+
/** Render the footer even when no footer slot is filled. */
|
|
180
|
+
readonly enabled: boolean;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Look presets per part. Each preset only changes the *defaults* of that
|
|
184
|
+
* part's component tokens, so `--shell-topbar-bg` & co. always win.
|
|
185
|
+
*/
|
|
186
|
+
interface ShellAppearanceConfig {
|
|
187
|
+
readonly topbar: TopbarAppearance;
|
|
188
|
+
readonly sidebar: SidebarAppearance;
|
|
189
|
+
readonly footer: FooterAppearance;
|
|
190
|
+
readonly navIndicator: NavIndicator;
|
|
191
|
+
readonly subheader: SubheaderAppearance;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Which animations run and how. Timing per part lives in CSS
|
|
195
|
+
* (`--shell-drawer-duration`, `--shell-collapse-easing` …); this chooses the
|
|
196
|
+
* *kind*. `prefers-reduced-motion` is always respected on top.
|
|
197
|
+
*/
|
|
198
|
+
interface ShellMotionConfig {
|
|
199
|
+
/** Master switch. `false` makes every shell transition instant. */
|
|
200
|
+
readonly enabled: boolean;
|
|
201
|
+
/** Speed multiplier for every duration: 2 = twice as fast, 0.5 = half speed. */
|
|
202
|
+
readonly speed: number;
|
|
203
|
+
readonly drawer: DrawerMotion;
|
|
204
|
+
readonly overflowMenu: MenuMotion;
|
|
205
|
+
readonly scrim: ScrimMotion;
|
|
206
|
+
/** Animate rail ⇄ panel (grid track, panel width, peek). */
|
|
207
|
+
readonly collapse: boolean;
|
|
208
|
+
/** Animate nav sections opening and closing. */
|
|
209
|
+
readonly navExpand: boolean;
|
|
210
|
+
}
|
|
211
|
+
/** The breadcrumb row between the topbar and `main`. */
|
|
212
|
+
interface ShellSubheaderConfig {
|
|
213
|
+
/**
|
|
214
|
+
* `'auto'`: shown when the trail is at least two levels deep or a subheader
|
|
215
|
+
* slot is filled; a route's `data: { subheader }` overrides. Booleans force it.
|
|
216
|
+
*/
|
|
217
|
+
readonly enabled: SubheaderVisibility;
|
|
218
|
+
/** With `scroll: 'page'`, stick under the topbar instead of scrolling away. */
|
|
219
|
+
readonly sticky: boolean;
|
|
220
|
+
/** Slot names the row may push into its own ⋯ menu, in that order. */
|
|
221
|
+
readonly overflowSlots: readonly string[];
|
|
222
|
+
}
|
|
223
|
+
/** How `ShellBreadcrumbStore` builds the trail and `<shell-breadcrumb>` shows it. */
|
|
224
|
+
interface ShellBreadcrumbsConfig {
|
|
225
|
+
/** Prepend a home crumb (unless the trail already starts there). */
|
|
226
|
+
readonly includeHome: boolean;
|
|
227
|
+
readonly homeLabel: string;
|
|
228
|
+
readonly homeUrl: string;
|
|
229
|
+
/** Show the current page as the last item (NN/g); `false` ends at the parent (GOV.UK). */
|
|
230
|
+
readonly includeCurrent: boolean;
|
|
231
|
+
/** Beyond this many items the middle levels fold into "…". Minimum 2. */
|
|
232
|
+
readonly maxItems: number;
|
|
233
|
+
/** Label levels without `data.breadcrumb` by their route `title`. */
|
|
234
|
+
readonly useRouteTitle: boolean;
|
|
235
|
+
/** Below this width (px, of the trail itself): first › … › parent › current. */
|
|
236
|
+
readonly compactBelow: number;
|
|
237
|
+
/** Below this width (px): a single "‹ Parent" link. */
|
|
238
|
+
readonly backBelow: number;
|
|
239
|
+
/** A pending label only shows its skeleton after this many ms. */
|
|
240
|
+
readonly skeletonDelay: number;
|
|
241
|
+
readonly separator: BreadcrumbSeparator;
|
|
242
|
+
}
|
|
243
|
+
interface ShellLabels {
|
|
244
|
+
readonly skipToContent: string;
|
|
245
|
+
readonly openNavigation: string;
|
|
246
|
+
readonly closeNavigation: string;
|
|
247
|
+
readonly expandSidebar: string;
|
|
248
|
+
readonly collapseSidebar: string;
|
|
249
|
+
readonly resizeSidebar: string;
|
|
250
|
+
readonly moreActions: string;
|
|
251
|
+
readonly primaryNavigation: string;
|
|
252
|
+
readonly breadcrumb: string;
|
|
253
|
+
readonly showPath: string;
|
|
254
|
+
readonly back: string;
|
|
255
|
+
readonly loading: string;
|
|
256
|
+
}
|
|
257
|
+
interface ShellConfig {
|
|
258
|
+
readonly layout: ShellLayout;
|
|
259
|
+
readonly scroll: ShellScrollMode;
|
|
260
|
+
/**
|
|
261
|
+
* With `scroll: 'main'` the router cannot restore scroll (the window never
|
|
262
|
+
* scrolls), so the shell scrolls `main` back to the top after navigation.
|
|
263
|
+
*/
|
|
264
|
+
readonly scrollTopOnNavigate: boolean;
|
|
265
|
+
/** Bucket assumed before the first measurement (and on the server). */
|
|
266
|
+
readonly initialSize: ShellSize;
|
|
267
|
+
readonly breakpoints: ShellBreakpoints;
|
|
268
|
+
readonly sidebar: ShellSidebarConfig;
|
|
269
|
+
readonly topbar: ShellTopbarConfig;
|
|
270
|
+
readonly footer: ShellFooterConfig;
|
|
271
|
+
readonly appearance: ShellAppearanceConfig;
|
|
272
|
+
readonly motion: ShellMotionConfig;
|
|
273
|
+
readonly subheader: ShellSubheaderConfig;
|
|
274
|
+
readonly breadcrumbs: ShellBreadcrumbsConfig;
|
|
275
|
+
/** Slot names the topbar may push into its overflow menu, in that order. */
|
|
276
|
+
readonly overflowSlots: readonly string[];
|
|
277
|
+
readonly labels: ShellLabels;
|
|
278
|
+
}
|
|
279
|
+
declare const DEFAULT_SHELL_CONFIG: ShellConfig;
|
|
280
|
+
/** Configuration as provided by the app (`provideAppShell`). */
|
|
281
|
+
declare const SHELL_CONFIG: InjectionToken<ShellConfig>;
|
|
282
|
+
/**
|
|
283
|
+
* Two levels deep is all the config tree needs — which keeps the merge honest
|
|
284
|
+
* and the type readable. Arrays and primitives are replaced wholesale; nested
|
|
285
|
+
* option objects are merged key by key.
|
|
286
|
+
*/
|
|
287
|
+
type ShellConfigInput = {
|
|
288
|
+
readonly [K in keyof ShellConfig]?: ShellConfig[K] extends readonly unknown[] ? ShellConfig[K] : ShellConfig[K] extends object ? Partial<ShellConfig[K]> : ShellConfig[K];
|
|
289
|
+
};
|
|
290
|
+
/** Merge `input` over `base` (the defaults unless given). Never mutates. */
|
|
291
|
+
declare function mergeShellConfig(input?: ShellConfigInput, base?: ShellConfig): ShellConfig;
|
|
292
|
+
/**
|
|
293
|
+
* Register the shell once, in `app.config.ts`:
|
|
294
|
+
*
|
|
295
|
+
* ```ts
|
|
296
|
+
* provideAppShell({ layout: 'sidebar-full', sidebar: { collapseBelow: 'xl' } })
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* Pass `behavior` to replace the collapse policy wholesale. Both can also be
|
|
300
|
+
* changed at runtime through `ShellStore.configure()` / `setBehavior()`.
|
|
301
|
+
*/
|
|
302
|
+
declare function provideAppShell(config?: ShellConfigInput, options?: {
|
|
303
|
+
readonly behavior?: SidebarBehavior;
|
|
304
|
+
}): EnvironmentProviders;
|
|
305
|
+
|
|
306
|
+
/** Minimal persistence port so the shell never touches `localStorage` directly. */
|
|
307
|
+
interface ShellStateStorage {
|
|
308
|
+
read(key: string): string | null;
|
|
309
|
+
write(key: string, value: string): void;
|
|
310
|
+
}
|
|
311
|
+
declare function localShellStateStorage(): ShellStateStorage;
|
|
312
|
+
declare const SHELL_STATE_STORAGE: InjectionToken<ShellStateStorage>;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Stable element ids. The shell assumes one instance per application, which
|
|
316
|
+
* lets `aria-controls` work from a toggle rendered anywhere in the tree.
|
|
317
|
+
*/
|
|
318
|
+
declare const SHELL_SIDEBAR_ID = "shell-sidebar";
|
|
319
|
+
declare const SHELL_MAIN_ID = "shell-main";
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Single source of truth for the shell.
|
|
323
|
+
*
|
|
324
|
+
* Root-provided on purpose: any component in the app — including lazily loaded
|
|
325
|
+
* pages and content projected into `<app-shell>` — can inject it and drive the
|
|
326
|
+
* sidebar without input/output plumbing. One shell per application.
|
|
327
|
+
*
|
|
328
|
+
* Everything is derived from four inputs: the measured shell width, the user's
|
|
329
|
+
* intent, the transient drawer/peek flags, and the (runtime-changeable)
|
|
330
|
+
* config + behaviour.
|
|
331
|
+
*/
|
|
332
|
+
declare class ShellStore implements ShellApi {
|
|
333
|
+
private readonly storage;
|
|
334
|
+
private readonly baseConfig;
|
|
335
|
+
private readonly behaviorState;
|
|
336
|
+
/** Measured shell width; `null` until the first ResizeObserver callback. */
|
|
337
|
+
private readonly width;
|
|
338
|
+
private readonly intent;
|
|
339
|
+
/** User-dragged panel width in px; `null` = use the configured width. */
|
|
340
|
+
private readonly customWidth;
|
|
341
|
+
private readonly peekRequested;
|
|
342
|
+
private readonly resizingState;
|
|
343
|
+
private peekTimer;
|
|
344
|
+
/** Size bucket of the shell container — not of the viewport. */
|
|
345
|
+
readonly size: Signal<ShellSize>;
|
|
346
|
+
/**
|
|
347
|
+
* Drawer open state. Transient by design: it is never persisted, and any
|
|
348
|
+
* change of size bucket closes it, so rotating a phone or resizing a window
|
|
349
|
+
* never leaves a modal drawer stranded open.
|
|
350
|
+
*/
|
|
351
|
+
private readonly drawerOpen;
|
|
352
|
+
/** Effective configuration: app config, runtime patches and the user's width. */
|
|
353
|
+
readonly config: Signal<ShellConfig>;
|
|
354
|
+
readonly behavior: Signal<SidebarBehavior>;
|
|
355
|
+
readonly shellWidth: Signal<number | null>;
|
|
356
|
+
readonly sidebarIntent: Signal<SidebarIntent>;
|
|
357
|
+
readonly sidebarWidth: Signal<number | null>;
|
|
358
|
+
readonly resizing: Signal<boolean>;
|
|
359
|
+
readonly sidebar: Signal<SidebarState>;
|
|
360
|
+
/** Patch the configuration at runtime, e.g. from a settings screen. */
|
|
361
|
+
configure(patch: ShellConfigInput): void;
|
|
362
|
+
/** Swap the collapse policy at runtime. */
|
|
363
|
+
setBehavior(behavior: SidebarBehavior): void;
|
|
364
|
+
/** Called by `<app-shell>` with its measured width. */
|
|
365
|
+
setWidth(width: number | null): void;
|
|
366
|
+
setSidebarIntent(intent: SidebarIntent): void;
|
|
367
|
+
/**
|
|
368
|
+
* Overlay drawers toggle open/closed; persistent panels ask the behaviour
|
|
369
|
+
* for the next intent (by default: expanded ⇄ collapsed).
|
|
370
|
+
*/
|
|
371
|
+
toggleSidebar(): void;
|
|
372
|
+
openSidebar(): void;
|
|
373
|
+
closeSidebar(): void;
|
|
374
|
+
/**
|
|
375
|
+
* Set the expanded panel width in px (clamped to `minWidth`/`maxWidth`), or
|
|
376
|
+
* `null` to return to the configured width. Pass `{ persist: false }` for
|
|
377
|
+
* intermediate values, e.g. while a drag is still in progress.
|
|
378
|
+
*/
|
|
379
|
+
setSidebarWidth(width: number | null, options?: {
|
|
380
|
+
readonly persist?: boolean;
|
|
381
|
+
}): void;
|
|
382
|
+
/** Suspends layout transitions while a resize drag is in progress. */
|
|
383
|
+
setResizing(resizing: boolean): void;
|
|
384
|
+
/** Hover/focus peek on the collapsed rail, debounced by `sidebar.peekDelay`. */
|
|
385
|
+
requestPeek(peeking: boolean): void;
|
|
386
|
+
private context;
|
|
387
|
+
private persistIntent;
|
|
388
|
+
private write;
|
|
389
|
+
private read;
|
|
390
|
+
private restoreIntent;
|
|
391
|
+
private restoreWidth;
|
|
392
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellStore, never>;
|
|
393
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<ShellStore>;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Building blocks for `SidebarBehavior.resolve`. Each returns a complete,
|
|
398
|
+
* internally consistent state, so a custom behaviour only decides *which* mode
|
|
399
|
+
* applies — never how wide the track is or whether a scrim is needed.
|
|
400
|
+
*/
|
|
401
|
+
/** Off-canvas modal drawer. Reserves no grid track. */
|
|
402
|
+
declare function drawerState(open: boolean, sidebar: ShellSidebarConfig): SidebarState;
|
|
403
|
+
/** Icon rail. While peeking the panel floats wider but the track stays put. */
|
|
404
|
+
declare function railState(peeking: boolean, sidebar: ShellSidebarConfig): SidebarState;
|
|
405
|
+
/** Full in-flow panel with labels. */
|
|
406
|
+
declare function panelState(sidebar: ShellSidebarConfig): SidebarState;
|
|
407
|
+
/** Not rendered, zero-width track. */
|
|
408
|
+
declare function hiddenState(): SidebarState;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Default policy.
|
|
412
|
+
*
|
|
413
|
+
* size < overlayBelow → modal drawer, closed until opened
|
|
414
|
+
* size < collapseBelow → icon rail, widened while peeking
|
|
415
|
+
* otherwise → full panel
|
|
416
|
+
*
|
|
417
|
+
* An explicit intent (`expanded`, `collapsed`, `hidden`) wins over the
|
|
418
|
+
* automatic choice, except in drawer territory where there is no room for a
|
|
419
|
+
* persistent panel. Set the intent back to `auto` to follow breakpoints again.
|
|
420
|
+
*/
|
|
421
|
+
declare const responsiveSidebarBehavior: SidebarBehavior;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The sidebar never collapses on its own. Whatever the user last chose is what
|
|
425
|
+
* they get at every desktop size (`auto` means expanded); only drawer
|
|
426
|
+
* territory overrides it.
|
|
427
|
+
*
|
|
428
|
+
* ```ts
|
|
429
|
+
* provideAppShell({}, { behavior: manualSidebarBehavior });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
declare const manualSidebarBehavior: SidebarBehavior;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Content-first policy: the sidebar is always an off-canvas drawer, at every
|
|
436
|
+
* size. Suits editors, media players and dashboards that want the full width.
|
|
437
|
+
*/
|
|
438
|
+
declare const drawerSidebarBehavior: SidebarBehavior;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Container-first breakpoints. Deliberately *not* device widths: they describe
|
|
442
|
+
* the space a region has, which is what layout decisions should react to.
|
|
443
|
+
*/
|
|
444
|
+
declare const DEFAULT_BREAKPOINTS: ShellBreakpoints;
|
|
445
|
+
declare function sizeRank(size: ShellSize): number;
|
|
446
|
+
/** Largest bucket whose minimum width is still <= `width`. */
|
|
447
|
+
declare function resolveSize(width: number, breakpoints: ShellBreakpoints): ShellSize;
|
|
448
|
+
/** `atLeast('md', 'sm')` → true */
|
|
449
|
+
declare function atLeast(size: ShellSize, min: ShellSize): boolean;
|
|
450
|
+
/** `atMost('md', 'lg')` → true */
|
|
451
|
+
declare function atMost(size: ShellSize, max: ShellSize): boolean;
|
|
452
|
+
/** Strictly narrower than `bound` — used for "collapse below md" style rules. */
|
|
453
|
+
declare function isBelow(size: ShellSize, bound: ShellSize): boolean;
|
|
454
|
+
|
|
455
|
+
type WidthTarget = ElementRef<HTMLElement> | Signal<ElementRef<HTMLElement> | undefined>;
|
|
456
|
+
/**
|
|
457
|
+
* Tracks the inline size of an element as a signal.
|
|
458
|
+
*
|
|
459
|
+
* Accepts a plain `ElementRef` (host element) or a `viewChild` signal, in which
|
|
460
|
+
* case the observer follows the element as it appears, changes or is removed.
|
|
461
|
+
*
|
|
462
|
+
* Must be called from an injection context. SSR safe: with no `ResizeObserver`
|
|
463
|
+
* the signal simply stays `null`, which callers read as "not measured yet" and
|
|
464
|
+
* fall back to `config.initialSize` — so the server renders the desktop layout
|
|
465
|
+
* instead of flashing the mobile one.
|
|
466
|
+
*/
|
|
467
|
+
declare function observeWidth(target: WidthTarget): Signal<number | null>;
|
|
468
|
+
|
|
469
|
+
interface MeasuredRegion {
|
|
470
|
+
/** Measured inline size in px, `null` until measured. */
|
|
471
|
+
readonly width: Signal<number | null>;
|
|
472
|
+
/** Bucket for that width under the shell's current breakpoints. */
|
|
473
|
+
readonly size: Signal<ShellSize>;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Measure an element and bucket its width with the shell's breakpoints.
|
|
477
|
+
* Before the first measurement (and on the server) the bucket is
|
|
478
|
+
* `config.initialSize`. Call from an injection context.
|
|
479
|
+
*/
|
|
480
|
+
declare function measureRegion(target: WidthTarget): MeasuredRegion;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The size bucket of the nearest enclosing region (topbar, sidebar, drawer,
|
|
484
|
+
* main). Regions provide this so slot outlets and consumer templates react to
|
|
485
|
+
* *their own* available space rather than to the viewport.
|
|
486
|
+
*/
|
|
487
|
+
interface ShellRegion {
|
|
488
|
+
readonly regionName: string;
|
|
489
|
+
readonly regionSize: Signal<ShellSize>;
|
|
490
|
+
readonly regionWidth: Signal<number | null>;
|
|
491
|
+
}
|
|
492
|
+
declare const SHELL_REGION: InjectionToken<ShellRegion>;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Turns any element into a measured container.
|
|
496
|
+
*
|
|
497
|
+
* ```html
|
|
498
|
+
* <div shellContainerSize #box="shellContainerSize">
|
|
499
|
+
* @if (box.atLeast('md')) { <app-filters /> }
|
|
500
|
+
* <span>{{ box.size() }}</span>
|
|
501
|
+
* </div>
|
|
502
|
+
* ```
|
|
503
|
+
*
|
|
504
|
+
* Also mirrors the bucket onto the host as `data-shell-size` and the raw width
|
|
505
|
+
* as `--shell-container-width`, and makes the host a CSS query container, so
|
|
506
|
+
* plain CSS can react without a media query:
|
|
507
|
+
*
|
|
508
|
+
* ```scss
|
|
509
|
+
* [data-shell-size='xs'] .toolbar__label { display: none; }
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* Provides itself as the enclosing `SHELL_REGION`, so nested slot outlets pick
|
|
513
|
+
* the nearest measured ancestor.
|
|
514
|
+
*/
|
|
515
|
+
declare class ContainerSizeDirective implements ShellRegion {
|
|
516
|
+
/** Optional name, purely for debugging: `shellContainerSize="toolbar"`. */
|
|
517
|
+
readonly label: _angular_core.InputSignal<string>;
|
|
518
|
+
private readonly region;
|
|
519
|
+
readonly regionWidth: Signal<number | null>;
|
|
520
|
+
readonly size: Signal<ShellSize>;
|
|
521
|
+
readonly regionSize: Signal<ShellSize>;
|
|
522
|
+
protected readonly cssWidth: Signal<string | null>;
|
|
523
|
+
get regionName(): string;
|
|
524
|
+
atLeast(min: ShellSize): boolean;
|
|
525
|
+
atMost(max: ShellSize): boolean;
|
|
526
|
+
below(bound: ShellSize): boolean;
|
|
527
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ContainerSizeDirective, never>;
|
|
528
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ContainerSizeDirective, "[shellContainerSize]", ["shellContainerSize"], { "label": { "alias": "shellContainerSize"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Declares a piece of shell chrome.
|
|
533
|
+
*
|
|
534
|
+
* ```html
|
|
535
|
+
* <ng-template shellSlot="search" slotMinSize="md" slotOverflow>
|
|
536
|
+
* <app-global-search />
|
|
537
|
+
* </ng-template>
|
|
538
|
+
* ```
|
|
539
|
+
*
|
|
540
|
+
* Templates register themselves with a root registry, so a slot can be
|
|
541
|
+
* contributed from anywhere — the app root, a routed page, a feature
|
|
542
|
+
* component — and it does not need to be a DOM child of `<app-shell>`.
|
|
543
|
+
* That is also what allows the same template to be rendered in the topbar on a
|
|
544
|
+
* wide screen and inside the overflow menu on a narrow one.
|
|
545
|
+
*/
|
|
546
|
+
declare class ShellSlotDirective {
|
|
547
|
+
/** Target slot. Unknown names are fine; render them with `<shell-slot-outlet>`. */
|
|
548
|
+
readonly name: _angular_core.InputSignal<ShellSlotName>;
|
|
549
|
+
/** Ascending render order within the slot. Contribute several templates freely. */
|
|
550
|
+
readonly order: _angular_core.InputSignalWithTransform<number, unknown>;
|
|
551
|
+
/** Render only when the *region* has at least this much room. */
|
|
552
|
+
readonly minSize: _angular_core.InputSignal<"xs" | "sm" | "md" | "lg" | "xl">;
|
|
553
|
+
/** Render only up to this region size — for narrow-only affordances. */
|
|
554
|
+
readonly maxSize: _angular_core.InputSignal<"xs" | "sm" | "md" | "lg" | "xl">;
|
|
555
|
+
/**
|
|
556
|
+
* When the region is too narrow, move the content into the overflow menu
|
|
557
|
+
* instead of dropping it. Off by default: dropping is the right answer for
|
|
558
|
+
* decorative chrome, moving is the right answer for actions. Only slots in
|
|
559
|
+
* `config.overflowSlots` have an overflow menu.
|
|
560
|
+
*/
|
|
561
|
+
readonly overflow: _angular_core.InputSignalWithTransform<boolean, boolean | "">;
|
|
562
|
+
readonly template: TemplateRef<ShellSlotContext>;
|
|
563
|
+
private readonly registry;
|
|
564
|
+
constructor();
|
|
565
|
+
/** Type guard for `let-` context inference in the template. */
|
|
566
|
+
static ngTemplateContextGuard(_directive: ShellSlotDirective, _context: unknown): _context is ShellSlotContext;
|
|
567
|
+
/**
|
|
568
|
+
* `slotOverflow` on a slot the topbar never scans is silently ignored at
|
|
569
|
+
* runtime; say so during development. Inputs are only readable once bound,
|
|
570
|
+
* hence the effect. Config is read untracked so runtime `configure()` calls
|
|
571
|
+
* do not repeat the warning.
|
|
572
|
+
*/
|
|
573
|
+
private warnOnUnsupportedOverflow;
|
|
574
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellSlotDirective, never>;
|
|
575
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ShellSlotDirective, "ng-template[shellSlot]", never, { "name": { "alias": "shellSlot"; "required": true; "isSignal": true; }; "order": { "alias": "slotOrder"; "required": false; "isSignal": true; }; "minSize": { "alias": "slotMinSize"; "required": false; "isSignal": true; }; "maxSize": { "alias": "slotMaxSize"; "required": false; "isSignal": true; }; "overflow": { "alias": "slotOverflow"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** Reactive index of every registered slot template, keyed by name. */
|
|
579
|
+
declare class ShellSlotRegistry {
|
|
580
|
+
private readonly slots;
|
|
581
|
+
private readonly byName;
|
|
582
|
+
register(slot: ShellSlotDirective): void;
|
|
583
|
+
unregister(slot: ShellSlotDirective): void;
|
|
584
|
+
/** All templates for a slot, ordered. Memoised per name. */
|
|
585
|
+
all(name: ShellSlotName): Signal<readonly ShellSlotDirective[]>;
|
|
586
|
+
/** Templates that fit the given region size. */
|
|
587
|
+
visible(name: ShellSlotName, size: ShellSize): readonly ShellSlotDirective[];
|
|
588
|
+
/** Templates that do not fit but asked to be kept via the overflow menu. */
|
|
589
|
+
overflowing(name: ShellSlotName, size: ShellSize): readonly ShellSlotDirective[];
|
|
590
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellSlotRegistry, never>;
|
|
591
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<ShellSlotRegistry>;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Renders the templates registered for a slot.
|
|
596
|
+
*
|
|
597
|
+
* Size filtering uses the nearest measured region, falling back to the shell
|
|
598
|
+
* itself — which is why a topbar squeezed by a wide sidebar collapses its
|
|
599
|
+
* contents even though the window never changed.
|
|
600
|
+
*/
|
|
601
|
+
declare class ShellSlotOutletComponent {
|
|
602
|
+
readonly name: _angular_core.InputSignal<ShellSlotName>;
|
|
603
|
+
/** `visible` renders what fits; `overflow` renders what did not fit. */
|
|
604
|
+
readonly select: _angular_core.InputSignal<"visible" | "overflow">;
|
|
605
|
+
/** Overrides the measured region size. Mostly for tests. */
|
|
606
|
+
readonly size: _angular_core.InputSignal<"xs" | "sm" | "md" | "lg" | "xl" | null>;
|
|
607
|
+
private readonly registry;
|
|
608
|
+
private readonly store;
|
|
609
|
+
private readonly region;
|
|
610
|
+
private readonly effectiveSize;
|
|
611
|
+
readonly slots: Signal<readonly ShellSlotDirective[]>;
|
|
612
|
+
readonly context: Signal<ShellSlotContext>;
|
|
613
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellSlotOutletComponent, never>;
|
|
614
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellSlotOutletComponent, "shell-slot-outlet", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "select": { "alias": "select"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* `true` when at least one template is registered for a slot — lets a region
|
|
619
|
+
* skip its wrapper markup (and its padding) instead of rendering an empty box.
|
|
620
|
+
* Call from an injection context.
|
|
621
|
+
*/
|
|
622
|
+
declare function hasSlot(...names: readonly ShellSlotName[]): Signal<boolean>;
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The navigational shell: sidebar + topbar + breadcrumb row + main + footer.
|
|
626
|
+
*
|
|
627
|
+
* Layout is a CSS grid whose tracks come from custom properties, so a host app
|
|
628
|
+
* restyles it entirely from CSS. The only value TypeScript writes is the
|
|
629
|
+
* current sidebar track width, which is derived state, not layout maths.
|
|
630
|
+
*/
|
|
631
|
+
declare class AppShellComponent implements ShellRegion {
|
|
632
|
+
private readonly host;
|
|
633
|
+
private readonly document;
|
|
634
|
+
private readonly router;
|
|
635
|
+
private readonly breadcrumbs;
|
|
636
|
+
private readonly main;
|
|
637
|
+
private readonly frame;
|
|
638
|
+
protected readonly store: ShellStore;
|
|
639
|
+
protected readonly config: Signal<_danjelp_ngx_app_shell.ShellConfig>;
|
|
640
|
+
protected readonly mainId = "shell-main";
|
|
641
|
+
protected readonly sidebarId = "shell-sidebar";
|
|
642
|
+
/** Per-instance overrides. Leave unset to follow the (runtime) config. */
|
|
643
|
+
readonly layout: _angular_core.InputSignal<ShellLayout | undefined>;
|
|
644
|
+
readonly scroll: _angular_core.InputSignal<ShellScrollMode | undefined>;
|
|
645
|
+
readonly topbar: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
|
|
646
|
+
readonly subheader: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
|
|
647
|
+
readonly footer: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
|
|
648
|
+
readonly regionName = "shell";
|
|
649
|
+
readonly regionWidth: Signal<number | null>;
|
|
650
|
+
readonly regionSize: Signal<ShellSize>;
|
|
651
|
+
protected readonly size: Signal<"xs" | "sm" | "md" | "lg" | "xl">;
|
|
652
|
+
protected readonly sidebar: Signal<SidebarState>;
|
|
653
|
+
private readonly hasSubheaderSlots;
|
|
654
|
+
private readonly hasFooterSlots;
|
|
655
|
+
protected readonly resolvedLayout: Signal<ShellLayout>;
|
|
656
|
+
protected readonly resolvedScroll: Signal<ShellScrollMode>;
|
|
657
|
+
/** Topbar, main and footer share one rounded card; the sidebar sits on the shell. */
|
|
658
|
+
protected readonly inset: Signal<boolean>;
|
|
659
|
+
protected readonly showTopbar: Signal<boolean>;
|
|
660
|
+
protected readonly showFooter: Signal<boolean>;
|
|
661
|
+
/**
|
|
662
|
+
* The breadcrumb row. In `'auto'` it follows the page: a route's
|
|
663
|
+
* `data.subheader` wins; otherwise it shows when the trail is at least two
|
|
664
|
+
* levels deep (a flat trail is noise) or when a page fills a subheader slot.
|
|
665
|
+
* Decided per navigation, so it never appears or vanishes within a page.
|
|
666
|
+
*/
|
|
667
|
+
protected readonly showSubheader: Signal<boolean>;
|
|
668
|
+
/**
|
|
669
|
+
* Factor every shell duration is multiplied by (see `duration()` in the
|
|
670
|
+
* token file): 0 turns motion off, 1 / speed scales it.
|
|
671
|
+
*/
|
|
672
|
+
protected readonly motionScale: Signal<string>;
|
|
673
|
+
constructor();
|
|
674
|
+
/**
|
|
675
|
+
* The window does not scroll in these modes, so the router cannot restore
|
|
676
|
+
* scroll: `main` scrolls in `scroll: 'main'`, the card in `inset` + `page`.
|
|
677
|
+
*/
|
|
678
|
+
private resetScroll;
|
|
679
|
+
/**
|
|
680
|
+
* A plain `href="#id"` would resolve against `<base href>` and trigger a
|
|
681
|
+
* router navigation, so the jump is done by moving focus instead.
|
|
682
|
+
*/
|
|
683
|
+
protected skipToMain(event: Event): void;
|
|
684
|
+
/** Optional Ctrl/⌘ + key toggle, ignored while typing. */
|
|
685
|
+
protected onDocumentKeydown(event: KeyboardEvent): void;
|
|
686
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AppShellComponent, never>;
|
|
687
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<AppShellComponent, "app-shell", ["appShell"], { "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "scroll": { "alias": "scroll"; "required": false; "isSignal": true; }; "topbar": { "alias": "topbar"; "required": false; "isSignal": true; }; "subheader": { "alias": "subheader"; "required": false; "isSignal": true; }; "footer": { "alias": "footer"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Topbar with priority-based slot collapsing.
|
|
692
|
+
*
|
|
693
|
+
* The bar measures **itself**, not the window. That matters: expanding the
|
|
694
|
+
* sidebar from a rail to a full panel takes ~200px away from the topbar without
|
|
695
|
+
* the window ever resizing, and the bar has to respond to that the same way it
|
|
696
|
+
* responds to a phone.
|
|
697
|
+
*
|
|
698
|
+
* Slot order, start to end:
|
|
699
|
+
*
|
|
700
|
+
* [toggle] [topbar-start] [brand] [primary-nav] [title] ····· [search] [actions] [⋯] [account] [topbar-end]
|
|
701
|
+
*
|
|
702
|
+
* Each slot template declares the room it needs (`slotMinSize` / `slotMaxSize`)
|
|
703
|
+
* and what happens when it does not get it (`slotOverflow` → move into the ⋯
|
|
704
|
+
* menu, otherwise drop).
|
|
705
|
+
*/
|
|
706
|
+
declare class ShellTopbarComponent implements ShellRegion {
|
|
707
|
+
private readonly registry;
|
|
708
|
+
private readonly region;
|
|
709
|
+
protected readonly store: ShellStore;
|
|
710
|
+
protected readonly config: Signal<_danjelp_ngx_app_shell.ShellConfig>;
|
|
711
|
+
readonly regionName = "topbar";
|
|
712
|
+
readonly regionWidth: Signal<number | null>;
|
|
713
|
+
readonly regionSize: Signal<ShellSize>;
|
|
714
|
+
protected readonly showToggle: Signal<boolean>;
|
|
715
|
+
protected readonly overflowSlots: Signal<readonly ShellSlotName[]>;
|
|
716
|
+
/** The ⋯ menu exists only while something is in it. */
|
|
717
|
+
protected readonly hasOverflow: Signal<boolean>;
|
|
718
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellTopbarComponent, never>;
|
|
719
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellTopbarComponent, "shell-topbar", never, {}, {}, never, never, true, never>;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Sidebar with four modes — `expanded`, `collapsed` (icon rail), `overlay`
|
|
724
|
+
* (modal drawer) and `hidden` — chosen by the injected `SidebarBehavior`
|
|
725
|
+
* rather than by this component. All this class does is render the resolved
|
|
726
|
+
* state and handle the mechanics each mode needs: peek timing, focus trapping,
|
|
727
|
+
* Escape, and drag-to-resize.
|
|
728
|
+
*
|
|
729
|
+
* Slot order, top to bottom:
|
|
730
|
+
*
|
|
731
|
+
* sidebar-header brand, workspace or account switcher
|
|
732
|
+
* sidebar-action one primary action ("New", "Compose")
|
|
733
|
+
* sidebar-search quick find / command palette launcher
|
|
734
|
+
* sidebar-nav the scrolling navigation (grows)
|
|
735
|
+
* sidebar-secondary settings, help, upgrade prompts
|
|
736
|
+
* sidebar-footer user card, storage meter — shares a row with the collapse control
|
|
737
|
+
*/
|
|
738
|
+
declare class ShellSidebarComponent implements ShellRegion {
|
|
739
|
+
private readonly host;
|
|
740
|
+
private readonly panel;
|
|
741
|
+
protected readonly store: ShellStore;
|
|
742
|
+
protected readonly config: Signal<_danjelp_ngx_app_shell.ShellConfig>;
|
|
743
|
+
/** Render the built-in expand/collapse control in the footer row. */
|
|
744
|
+
readonly collapseControl: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
745
|
+
readonly regionName = "sidebar";
|
|
746
|
+
/**
|
|
747
|
+
* Measured on the *panel*, not the host: during a peek the panel is wider
|
|
748
|
+
* than the grid track it lives in. Exposed as `--shell-container-width`.
|
|
749
|
+
*/
|
|
750
|
+
readonly regionWidth: Signal<number | null>;
|
|
751
|
+
/**
|
|
752
|
+
* The sidebar reports a *semantic* bucket rather than a measured one. Its
|
|
753
|
+
* panel is a few hundred pixels wide in every mode, so raw widths would put
|
|
754
|
+
* it permanently in `xs` and make `slotMinSize` useless. The contract is:
|
|
755
|
+
*
|
|
756
|
+
* rail (icons only) → `xs`
|
|
757
|
+
* panel / drawer (labels on) → `md`
|
|
758
|
+
*
|
|
759
|
+
* so `slotMinSize="sm"` on a sidebar slot reads as "only when labels show".
|
|
760
|
+
*/
|
|
761
|
+
readonly regionSize: Signal<ShellSize>;
|
|
762
|
+
protected readonly state: Signal<SidebarState>;
|
|
763
|
+
protected readonly hasHeader: Signal<boolean>;
|
|
764
|
+
protected readonly hasSecondary: Signal<boolean>;
|
|
765
|
+
private readonly hasFooterSlot;
|
|
766
|
+
/** The collapse control only makes sense for persistent panels. */
|
|
767
|
+
protected readonly showCollapse: Signal<boolean>;
|
|
768
|
+
protected readonly showFooter: Signal<boolean>;
|
|
769
|
+
protected readonly containerWidth: Signal<string | null>;
|
|
770
|
+
/** Peek only makes sense on a rail, and only when configured. */
|
|
771
|
+
private readonly peekable;
|
|
772
|
+
protected readonly resizable: Signal<boolean>;
|
|
773
|
+
/** Current panel width in px for `aria-valuenow` and keyboard steps. */
|
|
774
|
+
protected readonly resizeValue: Signal<number>;
|
|
775
|
+
constructor();
|
|
776
|
+
protected onPointerEnter(): void;
|
|
777
|
+
protected onPointerLeave(): void;
|
|
778
|
+
/** Keyboard users get the same peek: focus entering the rail widens it. */
|
|
779
|
+
protected onFocusIn(): void;
|
|
780
|
+
protected onFocusOut(event: FocusEvent): void;
|
|
781
|
+
protected onEscape(): void;
|
|
782
|
+
protected onResizeStart(event: PointerEvent): void;
|
|
783
|
+
protected onResizeMove(event: PointerEvent): void;
|
|
784
|
+
protected onResizeEnd(event: PointerEvent): void;
|
|
785
|
+
protected onResizeKey(event: KeyboardEvent): void;
|
|
786
|
+
/** Double-click the handle to return to the configured width. */
|
|
787
|
+
protected onResizeReset(): void;
|
|
788
|
+
private isRtl;
|
|
789
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellSidebarComponent, never>;
|
|
790
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellSidebarComponent, "shell-sidebar", never, { "collapseControl": { "alias": "collapseControl"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Footer strip. Three slots so the common "legal start, meta end" pattern
|
|
795
|
+
* needs no markup of its own, and so a status bar can occupy the middle.
|
|
796
|
+
* Measures itself, because its width depends on the layout and the sidebar.
|
|
797
|
+
*/
|
|
798
|
+
declare class ShellFooterComponent implements ShellRegion {
|
|
799
|
+
private readonly region;
|
|
800
|
+
protected readonly store: ShellStore;
|
|
801
|
+
readonly regionName = "footer";
|
|
802
|
+
readonly regionWidth: Signal<number | null>;
|
|
803
|
+
readonly regionSize: Signal<ShellSize>;
|
|
804
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellFooterComponent, never>;
|
|
805
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellFooterComponent, "shell-footer", never, {}, {}, never, never, true, never>;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* The breadcrumb row, between the topbar and `main`.
|
|
810
|
+
*
|
|
811
|
+
* [breadcrumb | built-in trail] [subheader-start] ········ [subheader-end] [⋯]
|
|
812
|
+
*
|
|
813
|
+
* A measured region like the topbar: `subheader-end` templates use
|
|
814
|
+
* `slotMinSize` / `slotOverflow` against the row's own width and move into
|
|
815
|
+
* the row's ⋯ menu when they don't fit. Its minimum height is reserved, so
|
|
816
|
+
* labels and actions that arrive late never shift the page.
|
|
817
|
+
*/
|
|
818
|
+
declare class ShellSubheaderComponent implements ShellRegion {
|
|
819
|
+
private readonly region;
|
|
820
|
+
private readonly registry;
|
|
821
|
+
protected readonly config: Signal<_danjelp_ngx_app_shell.ShellConfig>;
|
|
822
|
+
readonly regionName = "subheader";
|
|
823
|
+
readonly regionWidth: Signal<number | null>;
|
|
824
|
+
readonly regionSize: Signal<ShellSize>;
|
|
825
|
+
/** An app-provided trail replaces the built-in one. */
|
|
826
|
+
protected readonly customTrail: Signal<boolean>;
|
|
827
|
+
protected readonly overflowSlots: Signal<readonly ShellSlotName[]>;
|
|
828
|
+
protected readonly hasOverflow: Signal<boolean>;
|
|
829
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellSubheaderComponent, never>;
|
|
830
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellSubheaderComponent, "shell-subheader", never, {}, {}, never, never, true, never>;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Route `data` key for a level's label: a string (static, or produced by a
|
|
835
|
+
* resolver under `resolve: { breadcrumb: … }`), or `false` to skip the level.
|
|
836
|
+
*/
|
|
837
|
+
declare const BREADCRUMB_DATA_KEY = "breadcrumb";
|
|
838
|
+
/** Route `data` key that forces the breadcrumb row on (`true`) or off (`false`) for a page. */
|
|
839
|
+
declare const SUBHEADER_DATA_KEY = "subheader";
|
|
840
|
+
/** One level of the trail. */
|
|
841
|
+
interface ShellCrumb {
|
|
842
|
+
/** `null` while the label is still loading: the crumb renders as a placeholder. */
|
|
843
|
+
readonly label: string | null;
|
|
844
|
+
readonly url: string;
|
|
845
|
+
/** The page the user is on: rendered as text with `aria-current="page"`, not as a link. */
|
|
846
|
+
readonly current: boolean;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* How the trail is laid out:
|
|
850
|
+
* `full` — every level (the middle folds into "…" beyond `maxItems`);
|
|
851
|
+
* `compact` — first › … › parent › current;
|
|
852
|
+
* `back` — a single "‹ Parent" link.
|
|
853
|
+
*/
|
|
854
|
+
type ShellBreadcrumbDisplay = 'full' | 'compact' | 'back';
|
|
855
|
+
/** Context of a custom `[itemTemplate]` on `<shell-breadcrumb>`. */
|
|
856
|
+
interface ShellCrumbContext {
|
|
857
|
+
readonly $implicit: ShellCrumb;
|
|
858
|
+
readonly crumb: ShellCrumb;
|
|
859
|
+
}
|
|
860
|
+
type ShellCrumbTemplate = TemplateRef<ShellCrumbContext>;
|
|
861
|
+
|
|
862
|
+
interface TrailView {
|
|
863
|
+
readonly head: readonly ShellCrumb[];
|
|
864
|
+
/** Levels folded into the "…" menu. */
|
|
865
|
+
readonly hidden: readonly ShellCrumb[];
|
|
866
|
+
readonly tail: readonly ShellCrumb[];
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Breadcrumb trail, following the WAI-ARIA breadcrumb pattern:
|
|
870
|
+
* `<nav aria-label>` › `<ol>` › links, the current page as text with
|
|
871
|
+
* `aria-current="page"`, and CSS separators that screen readers skip.
|
|
872
|
+
*
|
|
873
|
+
* Renders the router-derived trail from `ShellBreadcrumbStore`, or `[items]`.
|
|
874
|
+
* It measures its own width and shortens itself instead of wrapping:
|
|
875
|
+
* full → middle levels folded into "…" → first › … › parent › current →
|
|
876
|
+
* a single "‹ Parent" back link.
|
|
877
|
+
*/
|
|
878
|
+
declare class ShellBreadcrumbComponent {
|
|
879
|
+
/** A manual trail. Leave unset to follow the router. */
|
|
880
|
+
readonly items: _angular_core.InputSignal<readonly ShellCrumb[] | null>;
|
|
881
|
+
/** Overrides `breadcrumbs.maxItems` for this instance. */
|
|
882
|
+
readonly maxItems: _angular_core.InputSignal<number | null>;
|
|
883
|
+
/** Force a layout instead of choosing one from the available width. */
|
|
884
|
+
readonly display: _angular_core.InputSignal<"auto" | ShellBreadcrumbDisplay>;
|
|
885
|
+
/** Custom crumb content; the link and `aria-current` stay the library's. */
|
|
886
|
+
readonly itemTemplate: _angular_core.InputSignal<ShellCrumbTemplate | null>;
|
|
887
|
+
private readonly store;
|
|
888
|
+
private readonly shell;
|
|
889
|
+
private readonly width;
|
|
890
|
+
private readonly options;
|
|
891
|
+
protected readonly separator: _angular_core.Signal<_danjelp_ngx_app_shell.BreadcrumbSeparator>;
|
|
892
|
+
protected readonly labels: _angular_core.Signal<_danjelp_ngx_app_shell.ShellLabels>;
|
|
893
|
+
protected readonly crumbs: _angular_core.Signal<readonly ShellCrumb[]>;
|
|
894
|
+
protected readonly mode: _angular_core.Signal<ShellBreadcrumbDisplay>;
|
|
895
|
+
protected readonly view: _angular_core.Signal<TrailView>;
|
|
896
|
+
/** Target of the "‹ Parent" link: the level just above the current page. */
|
|
897
|
+
protected readonly parent: _angular_core.Signal<ShellCrumb | null>;
|
|
898
|
+
protected readonly pending: _angular_core.Signal<boolean>;
|
|
899
|
+
/** Fast labels never flash a skeleton: it only shows after `skeletonDelay`. */
|
|
900
|
+
private readonly delayElapsed;
|
|
901
|
+
protected readonly skeletonShown: _angular_core.Signal<boolean>;
|
|
902
|
+
constructor();
|
|
903
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellBreadcrumbComponent, never>;
|
|
904
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellBreadcrumbComponent, "shell-breadcrumb", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "maxItems": { "alias": "maxItems"; "required": false; "isSignal": true; }; "display": { "alias": "display"; "required": false; "isSignal": true; }; "itemTemplate": { "alias": "itemTemplate"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** A label, or a signal of one that is `undefined` while it loads. */
|
|
908
|
+
type ShellBreadcrumbLabel = string | Signal<string | undefined>;
|
|
909
|
+
/**
|
|
910
|
+
* The breadcrumb trail, derived from the router.
|
|
911
|
+
*
|
|
912
|
+
* On every `NavigationEnd` it walks the active (primary) route tree. Each
|
|
913
|
+
* route that consumes URL segments is one level, labelled by — in order — a
|
|
914
|
+
* page override, the route's own `data.breadcrumb` (static or resolved), or
|
|
915
|
+
* its own `title`. Only what a route declares *itself* counts: Angular copies
|
|
916
|
+
* `data` from componentless and empty-path parents into children, and a child
|
|
917
|
+
* must not borrow its parent's label.
|
|
918
|
+
*/
|
|
919
|
+
declare class ShellBreadcrumbStore {
|
|
920
|
+
private readonly router;
|
|
921
|
+
private readonly shell;
|
|
922
|
+
private readonly levels;
|
|
923
|
+
private readonly routeSubheader;
|
|
924
|
+
private readonly overrides;
|
|
925
|
+
/** Labelled levels of the current route, before the home/current options. */
|
|
926
|
+
private readonly trail;
|
|
927
|
+
/** The trail to render, with `breadcrumbs.includeHome` / `includeCurrent` applied. */
|
|
928
|
+
readonly crumbs: Signal<readonly ShellCrumb[]>;
|
|
929
|
+
/** Labelled levels in the current route; the home crumb is not counted. */
|
|
930
|
+
readonly depth: Signal<number>;
|
|
931
|
+
/** `data.subheader` of the deepest route that declares it, if any. */
|
|
932
|
+
readonly subheaderOverride: Signal<boolean | undefined>;
|
|
933
|
+
constructor();
|
|
934
|
+
/**
|
|
935
|
+
* Label one level from code — for names only the page knows, e.g. a record
|
|
936
|
+
* loaded without a resolver. `route` picks the level ending at that route's
|
|
937
|
+
* URL; `null` labels the current level. While a signal label is `undefined`
|
|
938
|
+
* the crumb renders as a placeholder. Returns a function that removes the
|
|
939
|
+
* label again; prefer `contributeBreadcrumbLabel()`, which does so for you.
|
|
940
|
+
*/
|
|
941
|
+
setLabel(label: ShellBreadcrumbLabel, route?: ActivatedRoute | null): () => void;
|
|
942
|
+
/** The latest override for a level, `undefined` while pending, or `NO_OVERRIDE`. */
|
|
943
|
+
private overrideFor;
|
|
944
|
+
private refresh;
|
|
945
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellBreadcrumbStore, never>;
|
|
946
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<ShellBreadcrumbStore>;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Label the calling page's own level of the trail, for as long as the page
|
|
950
|
+
* lives. Call from a routed component's injection context:
|
|
951
|
+
*
|
|
952
|
+
* ```ts
|
|
953
|
+
* export class ProjectPage {
|
|
954
|
+
* readonly name = signal<string | undefined>(undefined); // set when loaded
|
|
955
|
+
* constructor() { contributeBreadcrumbLabel(this.name); }
|
|
956
|
+
* }
|
|
957
|
+
* ```
|
|
958
|
+
*
|
|
959
|
+
* While the signal is `undefined` the crumb is a placeholder; the breadcrumb
|
|
960
|
+
* row has reserved its height, so nothing shifts when the name arrives.
|
|
961
|
+
*/
|
|
962
|
+
declare function contributeBreadcrumbLabel(label: ShellBreadcrumbLabel): void;
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Drop-in toggle. Works anywhere in the app — header, page, command palette —
|
|
966
|
+
* because it talks to the root store rather than to a parent component.
|
|
967
|
+
*
|
|
968
|
+
* ```html
|
|
969
|
+
* <button shellSidebarToggle class="icon-button">☰</button>
|
|
970
|
+
* ```
|
|
971
|
+
*
|
|
972
|
+
* Sets `aria-expanded`, `aria-controls` and an accessible label that tracks the
|
|
973
|
+
* current mode, so the same button reads correctly as a hamburger on mobile and
|
|
974
|
+
* as a collapse control on desktop.
|
|
975
|
+
*/
|
|
976
|
+
declare class SidebarToggleDirective {
|
|
977
|
+
protected readonly store: ShellStore;
|
|
978
|
+
protected readonly sidebarId = "shell-sidebar";
|
|
979
|
+
/** True when the sidebar is showing its full, labelled panel. */
|
|
980
|
+
readonly expanded: _angular_core.Signal<boolean>;
|
|
981
|
+
readonly label: _angular_core.Signal<string>;
|
|
982
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarToggleDirective, never>;
|
|
983
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SidebarToggleDirective, "button[shellSidebarToggle]", ["shellSidebarToggle"], {}, {}, never, never, true, never>;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* One navigation entry. Data-driven on purpose: the same array can render in
|
|
988
|
+
* the sidebar, in the mobile drawer, in a command palette or in a breadcrumb
|
|
989
|
+
* without being duplicated as markup.
|
|
990
|
+
*/
|
|
991
|
+
interface ShellNavItem {
|
|
992
|
+
readonly id?: string;
|
|
993
|
+
readonly label: string;
|
|
994
|
+
/**
|
|
995
|
+
* Icon key handed to the icon template. Without an icon, a rail shows the
|
|
996
|
+
* label's first letter instead.
|
|
997
|
+
*/
|
|
998
|
+
readonly icon?: string;
|
|
999
|
+
/** Router target. Takes precedence over `href`. Prefer absolute paths. */
|
|
1000
|
+
readonly route?: string | readonly unknown[];
|
|
1001
|
+
/** External or non-router target. */
|
|
1002
|
+
readonly href?: string;
|
|
1003
|
+
readonly target?: '_self' | '_blank';
|
|
1004
|
+
/** Match the route exactly instead of by prefix. */
|
|
1005
|
+
readonly exact?: boolean;
|
|
1006
|
+
/** Short count or status text shown at the trailing edge. */
|
|
1007
|
+
readonly badge?: string | number;
|
|
1008
|
+
readonly disabled?: boolean;
|
|
1009
|
+
/** Renders as an expandable section instead of a link. */
|
|
1010
|
+
readonly children?: readonly ShellNavItem[];
|
|
1011
|
+
/** Start expanded on first render. */
|
|
1012
|
+
readonly expanded?: boolean;
|
|
1013
|
+
/** Anything your own item template needs. */
|
|
1014
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
1015
|
+
}
|
|
1016
|
+
/** A labelled cluster of items — the standard way to group sidebar navigation. */
|
|
1017
|
+
interface ShellNavGroup {
|
|
1018
|
+
/** Required for `ShellNavRegistry` contributions to find the group. */
|
|
1019
|
+
readonly id?: string;
|
|
1020
|
+
/** Omit for an unlabelled first cluster (the usual "Home / Inbox" block). */
|
|
1021
|
+
readonly label?: string;
|
|
1022
|
+
readonly items: readonly ShellNavItem[];
|
|
1023
|
+
readonly collapsible?: boolean;
|
|
1024
|
+
readonly collapsed?: boolean;
|
|
1025
|
+
}
|
|
1026
|
+
/** Context of a custom `[itemTemplate]` on `<shell-nav>`. */
|
|
1027
|
+
interface ShellNavItemContext {
|
|
1028
|
+
readonly $implicit: ShellNavItem;
|
|
1029
|
+
readonly item: ShellNavItem;
|
|
1030
|
+
/** Nesting level, 0 for top-level items. */
|
|
1031
|
+
readonly depth: number;
|
|
1032
|
+
/** False on an icon rail: render an icon only and keep the label for screen readers. */
|
|
1033
|
+
readonly labels: boolean;
|
|
1034
|
+
/** For sections: whether the children are showing. */
|
|
1035
|
+
readonly expanded: boolean;
|
|
1036
|
+
}
|
|
1037
|
+
type ShellNavItemTemplate = TemplateRef<ShellNavItemContext>;
|
|
1038
|
+
interface ShellNavIconContext {
|
|
1039
|
+
readonly $implicit: string;
|
|
1040
|
+
readonly icon: string;
|
|
1041
|
+
readonly item: ShellNavItem;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Renders grouped navigation. Data in, markup out — so the same array feeds
|
|
1046
|
+
* the sidebar, the mobile drawer and anything else that needs the nav tree.
|
|
1047
|
+
*
|
|
1048
|
+
* ```html
|
|
1049
|
+
* <ng-template shellSlot="sidebar-nav">
|
|
1050
|
+
* <shell-nav [groups]="navigation" [iconTemplate]="icon" />
|
|
1051
|
+
* </ng-template>
|
|
1052
|
+
* ```
|
|
1053
|
+
*
|
|
1054
|
+
* `labels` follows the sidebar by default, so a rail needs no configuration.
|
|
1055
|
+
* Items contributed through `ShellNavRegistry` are merged into the groups with
|
|
1056
|
+
* a matching `id`; groups that end up empty are not rendered.
|
|
1057
|
+
*/
|
|
1058
|
+
declare class ShellNavComponent {
|
|
1059
|
+
/** Groups, or a flat item list which is wrapped in one unlabelled group. */
|
|
1060
|
+
readonly groups: _angular_core.InputSignal<readonly ShellNavGroup[]>;
|
|
1061
|
+
readonly items: _angular_core.InputSignal<readonly ShellNavItem[]>;
|
|
1062
|
+
/** Override the sidebar's label state — useful when reusing `shell-nav`. */
|
|
1063
|
+
readonly labels: _angular_core.InputSignal<boolean | null>;
|
|
1064
|
+
/** Hide group headings even when labels show (a denser, Slack-like list). */
|
|
1065
|
+
readonly headings: _angular_core.InputSignalWithTransform<boolean, unknown>;
|
|
1066
|
+
/** Renders `ShellNavItem.icon` keys. Without it, the key is shown as text. */
|
|
1067
|
+
readonly iconTemplate: _angular_core.InputSignal<TemplateRef<ShellNavIconContext> | null>;
|
|
1068
|
+
/**
|
|
1069
|
+
* Replaces the *content* of every row (icon, label, badge). The row element
|
|
1070
|
+
* itself — link, section button, active state, children — stays the
|
|
1071
|
+
* library's, so routing and accessibility keep working.
|
|
1072
|
+
*/
|
|
1073
|
+
readonly itemTemplate: _angular_core.InputSignal<ShellNavItemTemplate | null>;
|
|
1074
|
+
protected readonly store: ShellStore;
|
|
1075
|
+
private readonly registry;
|
|
1076
|
+
/** Per-group overrides on top of the data's own `collapsed` flag. */
|
|
1077
|
+
private readonly groupOverrides;
|
|
1078
|
+
protected readonly showLabels: _angular_core.Signal<boolean>;
|
|
1079
|
+
protected readonly resolvedGroups: _angular_core.Signal<readonly ShellNavGroup[]>;
|
|
1080
|
+
protected groupKey(group: ShellNavGroup, index: number): string;
|
|
1081
|
+
protected isCollapsed(group: ShellNavGroup, index: number): boolean;
|
|
1082
|
+
protected toggleGroup(group: ShellNavGroup, index: number): void;
|
|
1083
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellNavComponent, never>;
|
|
1084
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellNavComponent, "shell-nav", never, { "groups": { "alias": "groups"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "labels": { "alias": "labels"; "required": false; "isSignal": true; }; "headings": { "alias": "headings"; "required": false; "isSignal": true; }; "iconTemplate": { "alias": "iconTemplate"; "required": false; "isSignal": true; }; "itemTemplate": { "alias": "itemTemplate"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* A single navigation entry: a link, or an expandable section when it has
|
|
1089
|
+
* children.
|
|
1090
|
+
*
|
|
1091
|
+
* Renders `<a>` for anything navigable and `<button>` for a pure section
|
|
1092
|
+
* header. Those are different semantics for assistive tech, and only one of
|
|
1093
|
+
* them belongs in the tab order as a link. A section whose subtree contains the
|
|
1094
|
+
* active route opens itself and is marked, so deep links never land in a
|
|
1095
|
+
* collapsed tree.
|
|
1096
|
+
*/
|
|
1097
|
+
declare class ShellNavItemComponent {
|
|
1098
|
+
readonly item: _angular_core.InputSignal<ShellNavItem>;
|
|
1099
|
+
readonly depth: _angular_core.InputSignal<number>;
|
|
1100
|
+
/** False on an icon rail: labels and badges give way to icons and tooltips. */
|
|
1101
|
+
readonly labels: _angular_core.InputSignal<boolean>;
|
|
1102
|
+
readonly iconTemplate: _angular_core.InputSignal<TemplateRef<ShellNavIconContext> | null>;
|
|
1103
|
+
/** Replaces the row content; see `ShellNavComponent.itemTemplate`. */
|
|
1104
|
+
readonly itemTemplate: _angular_core.InputSignal<ShellNavItemTemplate | null>;
|
|
1105
|
+
private readonly router;
|
|
1106
|
+
/** Current URL, so "contains the active route" re-evaluates on navigation. */
|
|
1107
|
+
private readonly url;
|
|
1108
|
+
protected readonly children: Signal<readonly ShellNavItem[]>;
|
|
1109
|
+
protected readonly hasChildren: Signal<boolean>;
|
|
1110
|
+
protected readonly isLink: Signal<boolean>;
|
|
1111
|
+
protected readonly childActive: Signal<boolean>;
|
|
1112
|
+
/**
|
|
1113
|
+
* User-toggleable, but re-derives when the data or the active route changes —
|
|
1114
|
+
* so a server-driven `expanded` flag or a deep link still wins, without
|
|
1115
|
+
* clobbering a click the moment anything else re-renders.
|
|
1116
|
+
*/
|
|
1117
|
+
private readonly expandedState;
|
|
1118
|
+
protected readonly expanded: Signal<boolean>;
|
|
1119
|
+
/** Tooltip only when the label is not rendered — otherwise it is noise. */
|
|
1120
|
+
protected readonly title: Signal<string | null>;
|
|
1121
|
+
/** Stand-in for a missing icon on a rail, so the row is never blank. */
|
|
1122
|
+
protected readonly initial: Signal<string>;
|
|
1123
|
+
protected readonly itemContext: Signal<ShellNavItemContext>;
|
|
1124
|
+
protected toggle(): void;
|
|
1125
|
+
/** `readonly unknown[]` is friendlier as an API than RouterLink's `any[]`. */
|
|
1126
|
+
protected routerTarget(item: ShellNavItem): string | unknown[];
|
|
1127
|
+
private containsActive;
|
|
1128
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellNavItemComponent, never>;
|
|
1129
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ShellNavItemComponent, "shell-nav-item", never, { "item": { "alias": "item"; "required": true; "isSignal": true; }; "depth": { "alias": "depth"; "required": false; "isSignal": true; }; "labels": { "alias": "labels"; "required": false; "isSignal": true; }; "iconTemplate": { "alias": "iconTemplate"; "required": false; "isSignal": true; }; "itemTemplate": { "alias": "itemTemplate"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/** Static items, or a signal of items (e.g. with live badge counts). */
|
|
1133
|
+
type ShellNavItemsSource = readonly ShellNavItem[] | Signal<readonly ShellNavItem[]>;
|
|
1134
|
+
interface ShellNavContributionOptions {
|
|
1135
|
+
/**
|
|
1136
|
+
* Ascending position among contributions to the same group. Contributions
|
|
1137
|
+
* always follow the group's own items; ties keep registration order.
|
|
1138
|
+
*/
|
|
1139
|
+
readonly order?: number;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Lets code other than the owner of the nav array add entries to it — a
|
|
1143
|
+
* feature library adding "Roadmap" to the app's "Workspace" group, or a
|
|
1144
|
+
* section adding context links while it is open.
|
|
1145
|
+
*
|
|
1146
|
+
* Contributions target a group by `ShellNavGroup.id` and are merged into every
|
|
1147
|
+
* `<shell-nav>` that renders a group with that id. The app keeps control of
|
|
1148
|
+
* structure and order: it declares the groups (an empty group is fine — empty
|
|
1149
|
+
* groups are not rendered), features only fill them.
|
|
1150
|
+
*/
|
|
1151
|
+
declare class ShellNavRegistry {
|
|
1152
|
+
private readonly contributions;
|
|
1153
|
+
private sequence;
|
|
1154
|
+
/**
|
|
1155
|
+
* Add items to the group with this id. Returns a function that removes them
|
|
1156
|
+
* again. Prefer `contributeNavItems()` / `provideShellNavItems()`, which
|
|
1157
|
+
* remove them automatically.
|
|
1158
|
+
*/
|
|
1159
|
+
contribute(groupId: string, items: ShellNavItemsSource, options?: ShellNavContributionOptions): () => void;
|
|
1160
|
+
/** Items contributed to a group, in order. Reactive when read in a signal context. */
|
|
1161
|
+
itemsFor(groupId: string): readonly ShellNavItem[];
|
|
1162
|
+
/**
|
|
1163
|
+
* `groups` with contributed items appended to the groups whose `id` matches.
|
|
1164
|
+
* Returns the input untouched when there is nothing to add, so object
|
|
1165
|
+
* identity stays stable for the common case. Reactive.
|
|
1166
|
+
*/
|
|
1167
|
+
merge(groups: readonly ShellNavGroup[]): readonly ShellNavGroup[];
|
|
1168
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ShellNavRegistry, never>;
|
|
1169
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<ShellNavRegistry>;
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Contribute nav items for the lifetime of the calling component, directive or
|
|
1173
|
+
* service. Call from an injection context.
|
|
1174
|
+
*
|
|
1175
|
+
* ```ts
|
|
1176
|
+
* export class ProjectPage {
|
|
1177
|
+
* constructor() {
|
|
1178
|
+
* contributeNavItems('workspace', [{ label: 'Project settings', route: '/settings' }]);
|
|
1179
|
+
* }
|
|
1180
|
+
* }
|
|
1181
|
+
* ```
|
|
1182
|
+
*/
|
|
1183
|
+
declare function contributeNavItems(groupId: string, items: ShellNavItemsSource, options?: ShellNavContributionOptions): void;
|
|
1184
|
+
/**
|
|
1185
|
+
* Contribute nav items for the lifetime of an environment injector. In the app
|
|
1186
|
+
* config the entries exist from startup — the usual way for a feature to own
|
|
1187
|
+
* its link:
|
|
1188
|
+
*
|
|
1189
|
+
* ```ts
|
|
1190
|
+
* providers: [provideShellNavItems('workspace', [{ label: 'Roadmap', route: '/roadmap' }])]
|
|
1191
|
+
* ```
|
|
1192
|
+
*
|
|
1193
|
+
* In a lazy route's `providers` they only appear once that route has loaded,
|
|
1194
|
+
* so do not use that to add the link *to* the lazy route itself.
|
|
1195
|
+
*/
|
|
1196
|
+
declare function provideShellNavItems(groupId: string, items: ShellNavItemsSource, options?: ShellNavContributionOptions): EnvironmentProviders;
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Add to a component's `imports` to get the whole shell vocabulary:
|
|
1200
|
+
*
|
|
1201
|
+
* ```ts
|
|
1202
|
+
* @Component({ imports: [APP_SHELL], … })
|
|
1203
|
+
* ```
|
|
1204
|
+
*
|
|
1205
|
+
* Tree-shaking still applies per component, so unused pieces are dropped.
|
|
1206
|
+
*/
|
|
1207
|
+
declare const APP_SHELL: readonly [typeof AppShellComponent, typeof ShellSlotDirective, typeof ShellSlotOutletComponent, typeof ShellNavComponent, typeof ShellNavItemComponent, typeof ShellBreadcrumbComponent, typeof SidebarToggleDirective, typeof ContainerSizeDirective];
|
|
1208
|
+
|
|
1209
|
+
export { APP_SHELL, AppShellComponent, BREADCRUMB_DATA_KEY, ContainerSizeDirective, DEFAULT_BREAKPOINTS, DEFAULT_SHELL_CONFIG, SHELL_CONFIG, SHELL_MAIN_ID, SHELL_REGION, SHELL_SIDEBAR_ID, SHELL_SIZES, SHELL_STATE_STORAGE, SIDEBAR_BEHAVIOR, SUBHEADER_DATA_KEY, ShellBreadcrumbComponent, ShellBreadcrumbStore, ShellFooterComponent, ShellNavComponent, ShellNavItemComponent, ShellNavRegistry, ShellSidebarComponent, ShellSlotDirective, ShellSlotOutletComponent, ShellSlotRegistry, ShellStore, ShellSubheaderComponent, ShellTopbarComponent, SidebarToggleDirective, atLeast, atMost, contributeBreadcrumbLabel, contributeNavItems, drawerSidebarBehavior, drawerState, hasSlot, hiddenState, isBelow, localShellStateStorage, manualSidebarBehavior, measureRegion, mergeShellConfig, observeWidth, panelState, provideAppShell, provideShellNavItems, railState, resolveSize, responsiveSidebarBehavior, sizeRank };
|
|
1210
|
+
export type { BreadcrumbSeparator, DrawerMotion, FooterAppearance, MeasuredRegion, MenuMotion, NavIndicator, ScrimMotion, ShellApi, ShellAppearanceConfig, ShellBreadcrumbDisplay, ShellBreadcrumbLabel, ShellBreadcrumbsConfig, ShellBreakpoints, ShellConfig, ShellConfigInput, ShellCrumb, ShellCrumbContext, ShellCrumbTemplate, ShellFooterConfig, ShellLabels, ShellLayout, ShellMotionConfig, ShellNavContributionOptions, ShellNavGroup, ShellNavIconContext, ShellNavItem, ShellNavItemContext, ShellNavItemTemplate, ShellNavItemsSource, ShellRegion, ShellScrollMode, ShellSidebarConfig, ShellSize, ShellSlotContext, ShellSlotName, ShellStateStorage, ShellSubheaderConfig, ShellTopbarConfig, SidebarAppearance, SidebarBehavior, SidebarContext, SidebarIntent, SidebarMode, SidebarState, SidebarToggleVisibility, SubheaderAppearance, SubheaderVisibility, TopbarAppearance, WidthTarget };
|