@nexterajs/ui 0.0.2 → 0.1.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/README.md +279 -34
- package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
- package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-core.mjs +652 -12
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-display.mjs +1142 -0
- package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs +321 -33
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +572 -10
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +2027 -136
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +1222 -188
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +103 -11
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
- package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-notify.mjs +46 -44
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
- package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +51 -26
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-shell.mjs +542 -0
- package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-tree-view.mjs +5 -2
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +42 -11
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +5 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +33 -2
- package/styles/_dropdown.scss +5 -5
- package/styles/_editor.scss +52 -17
- package/styles/_mixins.scss +1 -1
- package/styles/_scrollbar.scss +5 -5
- package/styles/_status.scss +21 -0
- package/styles/_toast.scss +14 -14
- package/styles/_tokens.scss +420 -3
- package/styles/_widget.scss +14 -3
- package/styles/index.scss +6 -4
- package/types/nexterajs-ui-charts.d.ts +446 -0
- package/types/nexterajs-ui-core.d.ts +387 -22
- package/types/nexterajs-ui-display.d.ts +720 -0
- package/types/nexterajs-ui-dropdowns.d.ts +191 -48
- package/types/nexterajs-ui-file-uploader.d.ts +236 -7
- package/types/nexterajs-ui-forms.d.ts +675 -30
- package/types/nexterajs-ui-grid.d.ts +448 -93
- package/types/nexterajs-ui-intl.d.ts +39 -4
- package/types/nexterajs-ui-navigation.d.ts +531 -0
- package/types/nexterajs-ui-notify.d.ts +4 -2
- package/types/nexterajs-ui-overlay.d.ts +703 -0
- package/types/nexterajs-ui-pivot.d.ts +56 -48
- package/types/nexterajs-ui-shell.d.ts +294 -0
- package/types/nexterajs-ui-tree-view.d.ts +6 -3
- package/types/nexterajs-ui-validation.d.ts +20 -11
- package/types/nexterajs-ui.d.ts +5 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Directive, inject, ElementRef, viewChild, input, model, booleanAttribute, output, contentChild, TemplateRef, computed, linkedSignal, ViewEncapsulation, ChangeDetectionStrategy, Component, Injector, numberAttribute, signal, DestroyRef, afterNextRender, forwardRef } from '@angular/core';
|
|
3
|
+
import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { FocusTrapFactory } from '@angular/cdk/a11y';
|
|
5
|
+
import { Router, NavigationEnd, PRIMARY_OUTLET, RouterLink } from '@angular/router';
|
|
6
|
+
import { NxeMessageService } from '@nexterajs/ui/core';
|
|
7
|
+
import { filter, map } from 'rxjs';
|
|
8
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
9
|
+
import { NxeBadgeComponent, NxeBadgeAnchorDirective } from '@nexterajs/ui/display';
|
|
10
|
+
import { NxeButtonComponent } from '@nexterajs/ui/forms';
|
|
11
|
+
|
|
12
|
+
/** Provided by `nxe-shell` to the topbar and sidenav projected into it. */
|
|
13
|
+
const NXE_SHELL = new InjectionToken('NXE_SHELL');
|
|
14
|
+
/**
|
|
15
|
+
* `<ng-template nxeSidenavIcon let-item>` inside `nxe-sidenav` draws the icon of every entry that
|
|
16
|
+
* has an `icon`, given the entry. Use it to map `icon` keys to your own SVGs:
|
|
17
|
+
*
|
|
18
|
+
* ```html
|
|
19
|
+
* <nxe-sidenav [items]="items">
|
|
20
|
+
* <ng-template nxeSidenavIcon let-item>
|
|
21
|
+
* <svg viewBox="0 0 24 24"><path [attr.d]="paths[item.icon]" /></svg>
|
|
22
|
+
* </ng-template>
|
|
23
|
+
* </nxe-sidenav>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
class NxeSidenavIconDirective {
|
|
27
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSidenavIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
28
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.5", type: NxeSidenavIconDirective, isStandalone: true, selector: "ng-template[nxeSidenavIcon]", ngImport: i0 });
|
|
29
|
+
}
|
|
30
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSidenavIconDirective, decorators: [{
|
|
31
|
+
type: Directive,
|
|
32
|
+
args: [{ selector: 'ng-template[nxeSidenavIcon]' }]
|
|
33
|
+
}] });
|
|
34
|
+
|
|
35
|
+
let nextSidenavId = 0;
|
|
36
|
+
/**
|
|
37
|
+
* `nxe-sidenav` is the application's primary navigation, drawn from a tree of {@link NxeNavItem}.
|
|
38
|
+
*
|
|
39
|
+
* ```html
|
|
40
|
+
* <nxe-sidenav [items]="items" (itemClick)="track($event)">
|
|
41
|
+
* <img nxeSidenavHeader src="logo.svg" alt="Nextera" />
|
|
42
|
+
* </nxe-sidenav>
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* **Structure and ARIA.** A `<nav>` landmark holding nested lists, in the pattern the ARIA Authoring
|
|
46
|
+
* Practices call a *disclosure navigation menu*: a link is `<a aria-current="page">` when it is the
|
|
47
|
+
* current page; a parent is a `<button aria-expanded aria-controls>` that shows or hides the list
|
|
48
|
+
* under it; a group heading labels its list (`aria-labelledby`). It is deliberately not
|
|
49
|
+
* `role="tree"`: a tree makes one node focusable and needs the full tree keyboard contract, where
|
|
50
|
+
* every link here is an ordinary tab stop and the arrow keys are a shortcut on top of Tab.
|
|
51
|
+
*
|
|
52
|
+
* **Keyboard.** Tab and Shift+Tab visit every link and parent. On a link or parent, Arrow Down/Up
|
|
53
|
+
* move to the next/previous visible entry, Home/End to the first/last. Arrow Right opens a closed
|
|
54
|
+
* parent, or moves into an open one's first child; Arrow Left closes an open parent, or moves from
|
|
55
|
+
* a child to its parent (Right and Left swap in a right-to-left document). Enter and Space activate.
|
|
56
|
+
*
|
|
57
|
+
* **Current page.** From the router: an entry is current when its URL matches (a prefix, or the
|
|
58
|
+
* whole URL with `exact`), and when several do, the most specific one alone is `aria-current`
|
|
59
|
+
* (`/payments/aging` over `/payments`). The parents of the current page open themselves on
|
|
60
|
+
* navigation; what the user opened or closed by hand holds until the next navigation into it.
|
|
61
|
+
*
|
|
62
|
+
* **Collapsed rail.** With the sidenav collapsed (through `nxe-shell` or the `collapsed` model) only
|
|
63
|
+
* the icons show, each with its label as a native `title` and as visually-hidden text, and a badge
|
|
64
|
+
* pins to the icon. There is no flyout for children (that waits for the menu): activating a parent
|
|
65
|
+
* in the rail expands the rail with that parent open.
|
|
66
|
+
*
|
|
67
|
+
* Inside an `nxe-shell` the shell owns the collapsed state and the off-canvas drawer; on its own the
|
|
68
|
+
* sidenav is a plain docked column.
|
|
69
|
+
*/
|
|
70
|
+
class NxeSidenavComponent {
|
|
71
|
+
router = inject(Router);
|
|
72
|
+
messages = inject(NxeMessageService);
|
|
73
|
+
shell = inject(NXE_SHELL, { optional: true });
|
|
74
|
+
/** The sidenav's own element; `nxe-shell` traps focus in it while the drawer is open. */
|
|
75
|
+
element = inject(ElementRef).nativeElement;
|
|
76
|
+
navElement = viewChild.required('nav', /* @ts-ignore */
|
|
77
|
+
...(ngDevMode ? [{ debugName: "navElement" }] : /* istanbul ignore next */ []));
|
|
78
|
+
/** DOM id, which the topbar's toggle button names in `aria-controls`. */
|
|
79
|
+
id = `nxe-sidenav-${nextSidenavId++}`;
|
|
80
|
+
/** The navigation tree. */
|
|
81
|
+
items = input([], /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
83
|
+
/** Accessible name of the `<nav>` landmark; "Main navigation" in the active language by default. */
|
|
84
|
+
label = input('', /* @ts-ignore */
|
|
85
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
86
|
+
/**
|
|
87
|
+
* Collapsed to the icon rail. Inside an `nxe-shell` the shell's `collapsed` wins and this is
|
|
88
|
+
* left alone; the sidenav writes the user's choice through the shell instead.
|
|
89
|
+
*/
|
|
90
|
+
collapsed = model(false, /* @ts-ignore */
|
|
91
|
+
...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
92
|
+
/** Show the collapse/expand button at the foot of a docked sidenav. Default true. */
|
|
93
|
+
collapsible = input(true, { ...(ngDevMode ? { debugName: "collapsible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
94
|
+
/** An entry was activated: a link followed, a plain button pressed. Parents do not emit. */
|
|
95
|
+
itemClick = output();
|
|
96
|
+
/** Draws the icon of every entry that has an `icon`. */
|
|
97
|
+
iconTemplate = contentChild(NxeSidenavIconDirective, { ...(ngDevMode ? { debugName: "iconTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
|
|
98
|
+
navLabel = computed(() => this.label() || this.messages.t('shell.mainNavigation'), /* @ts-ignore */
|
|
99
|
+
...(ngDevMode ? [{ debugName: "navLabel" }] : /* istanbul ignore next */ []));
|
|
100
|
+
rail = computed(() => (this.shell ? this.shell.rail() : this.collapsed()), /* @ts-ignore */
|
|
101
|
+
...(ngDevMode ? [{ debugName: "rail" }] : /* istanbul ignore next */ []));
|
|
102
|
+
drawer = computed(() => !!this.shell && this.shell.mode() === 'drawer', /* @ts-ignore */
|
|
103
|
+
...(ngDevMode ? [{ debugName: "drawer" }] : /* istanbul ignore next */ []));
|
|
104
|
+
drawerOpen = computed(() => !!this.shell && this.shell.drawerShown(), /* @ts-ignore */
|
|
105
|
+
...(ngDevMode ? [{ debugName: "drawerOpen" }] : /* istanbul ignore next */ []));
|
|
106
|
+
/** Every entry by id, with its place in the tree and the URL it points at. */
|
|
107
|
+
nodes = computed(() => {
|
|
108
|
+
const nodes = new Map();
|
|
109
|
+
const walk = (items, parentId) => {
|
|
110
|
+
for (const item of items) {
|
|
111
|
+
const heading = !!item.heading;
|
|
112
|
+
nodes.set(item.id, {
|
|
113
|
+
item,
|
|
114
|
+
parentId,
|
|
115
|
+
tree: !heading && !item.disabled && !item.children?.length ? this.treeOf(item) : null,
|
|
116
|
+
});
|
|
117
|
+
walk(item.children ?? [], heading ? parentId : item.id);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
walk(this.items(), null);
|
|
121
|
+
return nodes;
|
|
122
|
+
}, /* @ts-ignore */
|
|
123
|
+
...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
|
|
124
|
+
/** The router's URL, re-read at every navigation so what is current is a signal. */
|
|
125
|
+
url = toSignal(this.router.events.pipe(filter((event) => event instanceof NavigationEnd), map(() => this.router.url)), { initialValue: this.router.url });
|
|
126
|
+
/** Id of the one entry that is the current page. */
|
|
127
|
+
currentId = computed(() => {
|
|
128
|
+
this.url();
|
|
129
|
+
let best = null;
|
|
130
|
+
let bestScore = -1;
|
|
131
|
+
for (const [id, node] of this.nodes()) {
|
|
132
|
+
if (!node.tree) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const exact = !!node.item.exact;
|
|
136
|
+
const active = this.router.isActive(node.tree, {
|
|
137
|
+
paths: exact ? 'exact' : 'subset',
|
|
138
|
+
queryParams: exact ? 'exact' : node.item.queryParams ? 'subset' : 'ignored',
|
|
139
|
+
fragment: 'ignored',
|
|
140
|
+
matrixParams: 'ignored',
|
|
141
|
+
});
|
|
142
|
+
if (!active) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const score = (node.tree.root.children[PRIMARY_OUTLET]?.segments.length ?? 0) * 100 +
|
|
146
|
+
Object.keys(node.item.queryParams ?? {}).length;
|
|
147
|
+
if (score > bestScore) {
|
|
148
|
+
best = id;
|
|
149
|
+
bestScore = score;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return best;
|
|
153
|
+
}, /* @ts-ignore */
|
|
154
|
+
...(ngDevMode ? [{ debugName: "currentId" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/** Ids of the parents above the current page. They open by themselves. */
|
|
156
|
+
currentAncestors = computed(() => {
|
|
157
|
+
const ancestors = new Set();
|
|
158
|
+
const nodes = this.nodes();
|
|
159
|
+
const current = this.currentId();
|
|
160
|
+
let id = current ? (nodes.get(current)?.parentId ?? null) : null;
|
|
161
|
+
while (id) {
|
|
162
|
+
ancestors.add(id);
|
|
163
|
+
id = nodes.get(id)?.parentId ?? null;
|
|
164
|
+
}
|
|
165
|
+
return ancestors;
|
|
166
|
+
}, { ...(ngDevMode ? { debugName: "currentAncestors" } : /* istanbul ignore next */ {}), equal: (a, b) => a.size === b.size && [...a].every((id) => b.has(id)) });
|
|
167
|
+
/**
|
|
168
|
+
* What the user opened or closed by hand. Navigating to a new page forgets the choices about
|
|
169
|
+
* its ancestors, so they open for it, and keeps the rest.
|
|
170
|
+
*/
|
|
171
|
+
manual = linkedSignal({ ...(ngDevMode ? { debugName: "manual" } : /* istanbul ignore next */ {}), source: () => ({ url: this.url(), ancestors: this.currentAncestors() }),
|
|
172
|
+
computation: ({ ancestors }, previous) => {
|
|
173
|
+
const next = new Map(previous?.value);
|
|
174
|
+
ancestors.forEach((id) => next.delete(id));
|
|
175
|
+
return next;
|
|
176
|
+
} });
|
|
177
|
+
t(key) {
|
|
178
|
+
return this.messages.t(key);
|
|
179
|
+
}
|
|
180
|
+
isCurrent(id) {
|
|
181
|
+
return this.currentId() === id;
|
|
182
|
+
}
|
|
183
|
+
/** The current page is somewhere under this parent. */
|
|
184
|
+
holdsCurrent(id) {
|
|
185
|
+
return this.currentAncestors().has(id);
|
|
186
|
+
}
|
|
187
|
+
isOpen(id) {
|
|
188
|
+
return this.manual().get(id) ?? this.currentAncestors().has(id);
|
|
189
|
+
}
|
|
190
|
+
hasChildren(item) {
|
|
191
|
+
return !item.heading && !!item.children?.length;
|
|
192
|
+
}
|
|
193
|
+
badgeValue(item) {
|
|
194
|
+
const badge = item.badge;
|
|
195
|
+
return typeof badge === 'function' ? badge() : badge;
|
|
196
|
+
}
|
|
197
|
+
badgeName(item) {
|
|
198
|
+
const value = this.badgeValue(item);
|
|
199
|
+
return item.badgeLabel && value ? `${value} ${item.badgeLabel}` : '';
|
|
200
|
+
}
|
|
201
|
+
initial(item) {
|
|
202
|
+
return item.label.trim().charAt(0).toUpperCase();
|
|
203
|
+
}
|
|
204
|
+
/** Toggle a parent; in the rail, expand the rail with the parent open instead. */
|
|
205
|
+
onParent(item) {
|
|
206
|
+
if (this.rail()) {
|
|
207
|
+
this.setCollapsed(false);
|
|
208
|
+
this.setOpen(item.id, true);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.setOpen(item.id, !this.isOpen(item.id));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
onActivate(item) {
|
|
215
|
+
this.itemClick.emit(item);
|
|
216
|
+
}
|
|
217
|
+
toggleCollapsed() {
|
|
218
|
+
this.setCollapsed(!this.rail());
|
|
219
|
+
}
|
|
220
|
+
closeDrawer() {
|
|
221
|
+
this.shell?.closeDrawer();
|
|
222
|
+
}
|
|
223
|
+
onKeydown(event, item) {
|
|
224
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const current = event.currentTarget;
|
|
228
|
+
const links = this.focusables();
|
|
229
|
+
const at = links.indexOf(current);
|
|
230
|
+
const rtl = getComputedStyle(current).direction === 'rtl';
|
|
231
|
+
const key = event.key === 'ArrowRight' ? (rtl ? 'ArrowLeft' : 'ArrowRight') : event.key === 'ArrowLeft' ? (rtl ? 'ArrowRight' : 'ArrowLeft') : event.key;
|
|
232
|
+
const parent = this.hasChildren(item) && !this.rail();
|
|
233
|
+
let target;
|
|
234
|
+
switch (key) {
|
|
235
|
+
case 'ArrowDown':
|
|
236
|
+
target = links[at + 1];
|
|
237
|
+
break;
|
|
238
|
+
case 'ArrowUp':
|
|
239
|
+
target = links[at - 1];
|
|
240
|
+
break;
|
|
241
|
+
case 'Home':
|
|
242
|
+
target = links[0];
|
|
243
|
+
break;
|
|
244
|
+
case 'End':
|
|
245
|
+
target = links[links.length - 1];
|
|
246
|
+
break;
|
|
247
|
+
case 'ArrowRight':
|
|
248
|
+
if (!parent) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!this.isOpen(item.id)) {
|
|
252
|
+
this.setOpen(item.id, true);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
target = links[at + 1];
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case 'ArrowLeft': {
|
|
259
|
+
if (parent && this.isOpen(item.id)) {
|
|
260
|
+
this.setOpen(item.id, false);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
const parentId = this.nodes().get(item.id)?.parentId;
|
|
264
|
+
target = parentId ? links.find((link) => link.dataset['nxeNav'] === parentId) : undefined;
|
|
265
|
+
if (!target) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
default:
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
target?.focus();
|
|
275
|
+
}
|
|
276
|
+
/** Links and parent buttons in document order; the ones inside a closed parent are not rendered. */
|
|
277
|
+
focusables() {
|
|
278
|
+
return Array.from(this.navElement().nativeElement.querySelectorAll('a.nxe-nav-link, button.nxe-nav-link'));
|
|
279
|
+
}
|
|
280
|
+
setOpen(id, open) {
|
|
281
|
+
this.manual.update((current) => new Map(current).set(id, open));
|
|
282
|
+
}
|
|
283
|
+
setCollapsed(collapsed) {
|
|
284
|
+
if (this.shell) {
|
|
285
|
+
this.shell.setCollapsed(collapsed);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
this.collapsed.set(collapsed);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
treeOf(item) {
|
|
292
|
+
if (item.route === undefined) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
if (typeof item.route === 'string') {
|
|
296
|
+
const tree = this.router.parseUrl(item.route);
|
|
297
|
+
tree.queryParams = { ...tree.queryParams, ...item.queryParams };
|
|
298
|
+
return tree;
|
|
299
|
+
}
|
|
300
|
+
return this.router.createUrlTree(item.route, { queryParams: item.queryParams });
|
|
301
|
+
}
|
|
302
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSidenavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
303
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeSidenavComponent, isStandalone: true, selector: "nxe-sidenav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", itemClick: "itemClick" }, host: { properties: { "id": "id", "class.nxe-sidenav-rail": "rail()", "class.nxe-sidenav-drawer": "drawer()", "class.nxe-sidenav-open": "drawerOpen()", "attr.inert": "drawer() && !drawerOpen() ? '' : null", "attr.role": "drawerOpen() ? 'dialog' : null", "attr.aria-modal": "drawerOpen() ? 'true' : null", "attr.aria-label": "drawerOpen() ? navLabel() : null" }, classAttribute: "nxe-sidenav" }, queries: [{ propertyName: "iconTemplate", first: true, predicate: NxeSidenavIconDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "navElement", first: true, predicate: ["nav"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"nxe-sidenav-header\">\n <ng-content select=\"[nxeSidenavHeader]\" />\n @if (drawer()) {\n <nxe-button class=\"nxe-sidenav-close\" stylingMode=\"text\" size=\"sm\" [attr.aria-label]=\"t('shell.closeNavigation')\" (onClick)=\"closeDrawer()\">\n <svg nxeButtonIcon viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M3.7 2.3 8 6.6l4.3-4.3 1.4 1.4L9.4 8l4.3 4.3-1.4 1.4L8 9.4l-4.3 4.3-1.4-1.4L6.6 8 2.3 3.7z\" />\n </svg>\n </nxe-button>\n }\n</div>\n\n<nav #nav class=\"nxe-sidenav-nav\" [attr.aria-label]=\"navLabel()\">\n <ul class=\"nxe-nav-list nxe-nav-root\">\n @for (item of items(); track item.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: item, depth: 0 }\" />\n }\n </ul>\n</nav>\n\n<div class=\"nxe-sidenav-footer\">\n <ng-content select=\"[nxeSidenavFooter]\" />\n @if (!drawer() && collapsible()) {\n <nxe-button\n class=\"nxe-sidenav-collapse\"\n stylingMode=\"text\"\n size=\"sm\"\n [attr.aria-label]=\"rail() ? t('shell.expandSidebar') : t('shell.collapseSidebar')\"\n [attr.title]=\"rail() ? t('shell.expandSidebar') : t('shell.collapseSidebar')\"\n (onClick)=\"toggleCollapsed()\">\n <svg nxeButtonIcon class=\"nxe-sidenav-collapse-icon\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M9.7 3.3 5 8l4.7 4.7 1.1-1.1L7.2 8l3.6-3.6zM3 3h1.5v10H3z\" />\n </svg>\n </nxe-button>\n }\n</div>\n\n<!-- One entry's icon, label and badge; shared by every kind of entry. -->\n<ng-template #body let-item>\n <span class=\"nxe-nav-lead\" nxeBadgeAnchor>\n <span class=\"nxe-nav-icon\" aria-hidden=\"true\">\n @if (item.icon && iconTemplate(); as icon) {\n <ng-container *ngTemplateOutlet=\"icon; context: { $implicit: item }\" />\n } @else if (item.icon) {\n <i class=\"nxe-icon nxe-icon-{{ item.icon }}\"></i>\n } @else {\n <span class=\"nxe-nav-glyph\">{{ initial(item) }}</span>\n }\n </span>\n @if (rail()) {\n <nxe-badge overlay [value]=\"badgeValue(item)\" [family]=\"item.badgeFamily ?? 'danger'\" [label]=\"badgeName(item)\" />\n }\n </span>\n <span class=\"nxe-nav-label\">{{ item.label }}</span>\n @if (!rail()) {\n <nxe-badge class=\"nxe-nav-badge\" [value]=\"badgeValue(item)\" [family]=\"item.badgeFamily ?? 'danger'\" [label]=\"badgeName(item)\" />\n }\n</ng-template>\n\n<ng-template #node let-item let-depth=\"depth\">\n @if (item.heading) {\n <li class=\"nxe-nav-group\">\n <div class=\"nxe-nav-heading\" [id]=\"id + '-' + item.id\" [class.nxe-nav-heading-rail]=\"rail()\">{{ item.label }}</div>\n <ul class=\"nxe-nav-list\" [attr.aria-labelledby]=\"id + '-' + item.id\">\n @for (child of item.children; track child.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: child, depth: depth }\" />\n }\n </ul>\n </li>\n } @else {\n <li\n class=\"nxe-nav-item\"\n [class.nxe-nav-current]=\"isCurrent(item.id)\"\n [class.nxe-nav-holds-current]=\"holdsCurrent(item.id)\"\n [style.--nxe-nav-depth]=\"depth\">\n @if (item.disabled) {\n <span class=\"nxe-nav-link nxe-nav-link-disabled\" aria-disabled=\"true\" [attr.title]=\"rail() ? item.label : null\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </span>\n } @else if (hasChildren(item)) {\n <button\n type=\"button\"\n class=\"nxe-nav-link nxe-nav-parent\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.aria-expanded]=\"!rail() && isOpen(item.id)\"\n [attr.aria-controls]=\"!rail() && isOpen(item.id) ? id + '-' + item.id + '-list' : null\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onParent(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n @if (!rail()) {\n <svg class=\"nxe-nav-chevron\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"m4.2 5.8 3.8 3.8 3.8-3.8 1 1L8 11.6 3.2 6.8z\" />\n </svg>\n }\n </button>\n @if (!rail() && isOpen(item.id)) {\n <ul class=\"nxe-nav-list nxe-nav-sub\" [id]=\"id + '-' + item.id + '-list'\">\n @for (child of item.children; track child.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: child, depth: depth + 1 }\" />\n }\n </ul>\n }\n } @else if (item.route !== undefined) {\n <a\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [routerLink]=\"item.route\"\n [queryParams]=\"item.queryParams\"\n [attr.aria-current]=\"isCurrent(item.id) ? 'page' : null\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.href]=\"item.href\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </button>\n }\n </li>\n }\n</ng-template>\n", styles: ["nxe-sidenav.nxe-sidenav{display:flex;flex-direction:column;flex:0 0 auto;box-sizing:border-box;width:var(--nxe-ui-shell-sidenav-width);min-height:0;overflow:hidden;border-inline-end:1px solid var(--nxe-ui-shell-sidenav-border);background-color:var(--nxe-ui-shell-sidenav-bg);color:var(--nxe-ui-shell-sidenav-fg);font-family:var(--nxe-ui-font-family);font-size:var(--nxe-ui-font-size-md);line-height:var(--nxe-ui-line-height-md);transition:width .18s ease}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail{width:var(--nxe-ui-shell-sidenav-collapsed-width)}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer{position:absolute;inset-block:0;inset-inline-start:0;z-index:30;width:min(var(--nxe-ui-shell-sidenav-width),86%);border-inline-end:0;box-shadow:var(--nxe-ui-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .2s ease,visibility 0s linear .2s}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer:dir(rtl){transform:translate(100%)}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer.nxe-sidenav-open{transform:none;visibility:visible;transition:transform .2s ease}nxe-sidenav.nxe-sidenav .nxe-sidenav-header{display:flex;align-items:center;justify-content:space-between;flex:0 0 auto;gap:var(--nxe-ui-space-2);padding:var(--nxe-ui-space-3) var(--nxe-ui-space-3) var(--nxe-ui-space-1)}nxe-sidenav.nxe-sidenav .nxe-sidenav-header:empty{display:none}nxe-sidenav.nxe-sidenav .nxe-sidenav-nav{flex:1 1 auto;min-height:0;overflow-x:hidden;overflow-y:auto;padding:var(--nxe-ui-space-2)}nxe-sidenav.nxe-sidenav .nxe-sidenav-footer{display:flex;align-items:center;justify-content:flex-end;flex:0 0 auto;gap:var(--nxe-ui-space-2);padding:var(--nxe-ui-space-2);border-block-start:1px solid var(--nxe-ui-shell-sidenav-border)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-footer{justify-content:center}nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon{transition:transform .18s ease}nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon:dir(rtl){transform:scaleX(-1)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-collapse-icon{transform:scaleX(-1)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-collapse-icon:dir(rtl){transform:none}nxe-sidenav.nxe-sidenav .nxe-nav-list{margin:0;padding:0;list-style:none}nxe-sidenav.nxe-sidenav .nxe-nav-group+.nxe-nav-group,nxe-sidenav.nxe-sidenav .nxe-nav-item+.nxe-nav-group{margin-block-start:var(--nxe-ui-space-3)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-group+.nxe-nav-group,nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-item+.nxe-nav-group{margin-block-start:var(--nxe-ui-space-2);padding-block-start:var(--nxe-ui-space-2);border-block-start:1px solid var(--nxe-ui-shell-sidenav-border)}nxe-sidenav.nxe-sidenav .nxe-nav-heading{padding:var(--nxe-ui-space-1) var(--nxe-ui-space-3);color:var(--nxe-ui-shell-sidenav-fg-muted);font-size:var(--nxe-ui-font-size-xs);font-weight:var(--nxe-ui-font-weight-semibold);line-height:var(--nxe-ui-line-height-xs);letter-spacing:.04em;text-transform:uppercase}nxe-sidenav.nxe-sidenav .nxe-nav-heading.nxe-nav-heading-rail{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}nxe-sidenav.nxe-sidenav .nxe-nav-link{display:flex;align-items:center;gap:var(--nxe-ui-space-3);box-sizing:border-box;width:100%;min-height:var(--nxe-ui-control-min-height);margin:0;padding-block:var(--nxe-ui-space-1);padding-inline:calc(var(--nxe-ui-space-3) + var(--nxe-nav-depth, 0) * var(--nxe-ui-space-4)) var(--nxe-ui-space-3);border:0;border-radius:var(--nxe-ui-radius-md);background:transparent;color:inherit;font:inherit;text-align:start;text-decoration:none;cursor:pointer}nxe-sidenav.nxe-sidenav .nxe-nav-link:hover{background-color:var(--nxe-ui-shell-sidenav-hover-bg)}nxe-sidenav.nxe-sidenav .nxe-nav-link:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:-2px}nxe-sidenav.nxe-sidenav .nxe-nav-link-disabled{opacity:var(--nxe-ui-disabled-opacity);cursor:default}nxe-sidenav.nxe-sidenav .nxe-nav-link-disabled:hover{background:transparent}nxe-sidenav.nxe-sidenav .nxe-nav-current>.nxe-nav-link{background-color:var(--nxe-ui-shell-sidenav-active-bg);color:var(--nxe-ui-shell-sidenav-active-fg);font-weight:var(--nxe-ui-font-weight-semibold)}nxe-sidenav.nxe-sidenav .nxe-nav-holds-current>.nxe-nav-link{font-weight:var(--nxe-ui-font-weight-semibold)}nxe-sidenav.nxe-sidenav .nxe-nav-lead{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:20px;height:20px}nxe-sidenav.nxe-sidenav .nxe-nav-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px}nxe-sidenav.nxe-sidenav .nxe-nav-icon>svg{width:18px;height:18px;fill:currentColor}nxe-sidenav.nxe-sidenav .nxe-nav-glyph{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--nxe-ui-radius-sm);font-size:var(--nxe-ui-font-size-xs);font-weight:var(--nxe-ui-font-weight-semibold);box-shadow:inset 0 0 0 1px currentColor}nxe-sidenav.nxe-sidenav:not(.nxe-sidenav-rail) .nxe-nav-lead:has(.nxe-nav-glyph){display:none}nxe-sidenav.nxe-sidenav .nxe-nav-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-sidenav.nxe-sidenav .nxe-nav-badge{flex:0 0 auto}nxe-sidenav.nxe-sidenav .nxe-nav-chevron{flex:0 0 auto;width:16px;height:16px;transition:transform .15s ease}nxe-sidenav.nxe-sidenav .nxe-nav-parent[aria-expanded=true] .nxe-nav-chevron{transform:rotate(180deg)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-link{justify-content:center;padding-inline:0}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-lead{position:relative}@media(prefers-reduced-motion:reduce){nxe-sidenav.nxe-sidenav,nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer,nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer.nxe-sidenav-open,nxe-sidenav.nxe-sidenav .nxe-nav-chevron,nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon{transition-duration:.01ms;transition-delay:0s}}@media(forced-colors:active){nxe-sidenav.nxe-sidenav .nxe-nav-current>.nxe-nav-link{outline:2px solid Highlight;outline-offset:-2px}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: NxeBadgeComponent, selector: "nxe-badge", inputs: ["value", "max", "dot", "showZero", "family", "overlay", "label"] }, { kind: "directive", type: NxeBadgeAnchorDirective, selector: "[nxeBadgeAnchor]" }, { kind: "component", type: NxeButtonComponent, selector: "nxe-button", inputs: ["text", "type", "stylingMode", "size", "loading", "block", "disabled", "validationGroup", "focusStateEnabled", "icon", "height", "width"], outputs: ["onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
304
|
+
}
|
|
305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSidenavComponent, decorators: [{
|
|
306
|
+
type: Component,
|
|
307
|
+
args: [{ selector: 'nxe-sidenav', imports: [NgTemplateOutlet, RouterLink, NxeBadgeComponent, NxeBadgeAnchorDirective, NxeButtonComponent], host: {
|
|
308
|
+
class: 'nxe-sidenav',
|
|
309
|
+
'[id]': 'id',
|
|
310
|
+
'[class.nxe-sidenav-rail]': 'rail()',
|
|
311
|
+
'[class.nxe-sidenav-drawer]': 'drawer()',
|
|
312
|
+
'[class.nxe-sidenav-open]': 'drawerOpen()',
|
|
313
|
+
'[attr.inert]': "drawer() && !drawerOpen() ? '' : null",
|
|
314
|
+
'[attr.role]': "drawerOpen() ? 'dialog' : null",
|
|
315
|
+
'[attr.aria-modal]': "drawerOpen() ? 'true' : null",
|
|
316
|
+
'[attr.aria-label]': 'drawerOpen() ? navLabel() : null',
|
|
317
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-sidenav-header\">\n <ng-content select=\"[nxeSidenavHeader]\" />\n @if (drawer()) {\n <nxe-button class=\"nxe-sidenav-close\" stylingMode=\"text\" size=\"sm\" [attr.aria-label]=\"t('shell.closeNavigation')\" (onClick)=\"closeDrawer()\">\n <svg nxeButtonIcon viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M3.7 2.3 8 6.6l4.3-4.3 1.4 1.4L9.4 8l4.3 4.3-1.4 1.4L8 9.4l-4.3 4.3-1.4-1.4L6.6 8 2.3 3.7z\" />\n </svg>\n </nxe-button>\n }\n</div>\n\n<nav #nav class=\"nxe-sidenav-nav\" [attr.aria-label]=\"navLabel()\">\n <ul class=\"nxe-nav-list nxe-nav-root\">\n @for (item of items(); track item.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: item, depth: 0 }\" />\n }\n </ul>\n</nav>\n\n<div class=\"nxe-sidenav-footer\">\n <ng-content select=\"[nxeSidenavFooter]\" />\n @if (!drawer() && collapsible()) {\n <nxe-button\n class=\"nxe-sidenav-collapse\"\n stylingMode=\"text\"\n size=\"sm\"\n [attr.aria-label]=\"rail() ? t('shell.expandSidebar') : t('shell.collapseSidebar')\"\n [attr.title]=\"rail() ? t('shell.expandSidebar') : t('shell.collapseSidebar')\"\n (onClick)=\"toggleCollapsed()\">\n <svg nxeButtonIcon class=\"nxe-sidenav-collapse-icon\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M9.7 3.3 5 8l4.7 4.7 1.1-1.1L7.2 8l3.6-3.6zM3 3h1.5v10H3z\" />\n </svg>\n </nxe-button>\n }\n</div>\n\n<!-- One entry's icon, label and badge; shared by every kind of entry. -->\n<ng-template #body let-item>\n <span class=\"nxe-nav-lead\" nxeBadgeAnchor>\n <span class=\"nxe-nav-icon\" aria-hidden=\"true\">\n @if (item.icon && iconTemplate(); as icon) {\n <ng-container *ngTemplateOutlet=\"icon; context: { $implicit: item }\" />\n } @else if (item.icon) {\n <i class=\"nxe-icon nxe-icon-{{ item.icon }}\"></i>\n } @else {\n <span class=\"nxe-nav-glyph\">{{ initial(item) }}</span>\n }\n </span>\n @if (rail()) {\n <nxe-badge overlay [value]=\"badgeValue(item)\" [family]=\"item.badgeFamily ?? 'danger'\" [label]=\"badgeName(item)\" />\n }\n </span>\n <span class=\"nxe-nav-label\">{{ item.label }}</span>\n @if (!rail()) {\n <nxe-badge class=\"nxe-nav-badge\" [value]=\"badgeValue(item)\" [family]=\"item.badgeFamily ?? 'danger'\" [label]=\"badgeName(item)\" />\n }\n</ng-template>\n\n<ng-template #node let-item let-depth=\"depth\">\n @if (item.heading) {\n <li class=\"nxe-nav-group\">\n <div class=\"nxe-nav-heading\" [id]=\"id + '-' + item.id\" [class.nxe-nav-heading-rail]=\"rail()\">{{ item.label }}</div>\n <ul class=\"nxe-nav-list\" [attr.aria-labelledby]=\"id + '-' + item.id\">\n @for (child of item.children; track child.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: child, depth: depth }\" />\n }\n </ul>\n </li>\n } @else {\n <li\n class=\"nxe-nav-item\"\n [class.nxe-nav-current]=\"isCurrent(item.id)\"\n [class.nxe-nav-holds-current]=\"holdsCurrent(item.id)\"\n [style.--nxe-nav-depth]=\"depth\">\n @if (item.disabled) {\n <span class=\"nxe-nav-link nxe-nav-link-disabled\" aria-disabled=\"true\" [attr.title]=\"rail() ? item.label : null\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </span>\n } @else if (hasChildren(item)) {\n <button\n type=\"button\"\n class=\"nxe-nav-link nxe-nav-parent\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.aria-expanded]=\"!rail() && isOpen(item.id)\"\n [attr.aria-controls]=\"!rail() && isOpen(item.id) ? id + '-' + item.id + '-list' : null\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onParent(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n @if (!rail()) {\n <svg class=\"nxe-nav-chevron\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"m4.2 5.8 3.8 3.8 3.8-3.8 1 1L8 11.6 3.2 6.8z\" />\n </svg>\n }\n </button>\n @if (!rail() && isOpen(item.id)) {\n <ul class=\"nxe-nav-list nxe-nav-sub\" [id]=\"id + '-' + item.id + '-list'\">\n @for (child of item.children; track child.id) {\n <ng-container *ngTemplateOutlet=\"node; context: { $implicit: child, depth: depth + 1 }\" />\n }\n </ul>\n }\n } @else if (item.route !== undefined) {\n <a\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [routerLink]=\"item.route\"\n [queryParams]=\"item.queryParams\"\n [attr.aria-current]=\"isCurrent(item.id) ? 'page' : null\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.href]=\"item.href\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"nxe-nav-link\"\n [attr.data-nxe-nav]=\"item.id\"\n [attr.title]=\"rail() ? item.label : null\"\n (click)=\"onActivate(item)\"\n (keydown)=\"onKeydown($event, item)\">\n <ng-container *ngTemplateOutlet=\"body; context: { $implicit: item }\" />\n </button>\n }\n </li>\n }\n</ng-template>\n", styles: ["nxe-sidenav.nxe-sidenav{display:flex;flex-direction:column;flex:0 0 auto;box-sizing:border-box;width:var(--nxe-ui-shell-sidenav-width);min-height:0;overflow:hidden;border-inline-end:1px solid var(--nxe-ui-shell-sidenav-border);background-color:var(--nxe-ui-shell-sidenav-bg);color:var(--nxe-ui-shell-sidenav-fg);font-family:var(--nxe-ui-font-family);font-size:var(--nxe-ui-font-size-md);line-height:var(--nxe-ui-line-height-md);transition:width .18s ease}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail{width:var(--nxe-ui-shell-sidenav-collapsed-width)}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer{position:absolute;inset-block:0;inset-inline-start:0;z-index:30;width:min(var(--nxe-ui-shell-sidenav-width),86%);border-inline-end:0;box-shadow:var(--nxe-ui-shadow-lg);transform:translate(-100%);visibility:hidden;transition:transform .2s ease,visibility 0s linear .2s}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer:dir(rtl){transform:translate(100%)}nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer.nxe-sidenav-open{transform:none;visibility:visible;transition:transform .2s ease}nxe-sidenav.nxe-sidenav .nxe-sidenav-header{display:flex;align-items:center;justify-content:space-between;flex:0 0 auto;gap:var(--nxe-ui-space-2);padding:var(--nxe-ui-space-3) var(--nxe-ui-space-3) var(--nxe-ui-space-1)}nxe-sidenav.nxe-sidenav .nxe-sidenav-header:empty{display:none}nxe-sidenav.nxe-sidenav .nxe-sidenav-nav{flex:1 1 auto;min-height:0;overflow-x:hidden;overflow-y:auto;padding:var(--nxe-ui-space-2)}nxe-sidenav.nxe-sidenav .nxe-sidenav-footer{display:flex;align-items:center;justify-content:flex-end;flex:0 0 auto;gap:var(--nxe-ui-space-2);padding:var(--nxe-ui-space-2);border-block-start:1px solid var(--nxe-ui-shell-sidenav-border)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-footer{justify-content:center}nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon{transition:transform .18s ease}nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon:dir(rtl){transform:scaleX(-1)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-collapse-icon{transform:scaleX(-1)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-sidenav-collapse-icon:dir(rtl){transform:none}nxe-sidenav.nxe-sidenav .nxe-nav-list{margin:0;padding:0;list-style:none}nxe-sidenav.nxe-sidenav .nxe-nav-group+.nxe-nav-group,nxe-sidenav.nxe-sidenav .nxe-nav-item+.nxe-nav-group{margin-block-start:var(--nxe-ui-space-3)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-group+.nxe-nav-group,nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-item+.nxe-nav-group{margin-block-start:var(--nxe-ui-space-2);padding-block-start:var(--nxe-ui-space-2);border-block-start:1px solid var(--nxe-ui-shell-sidenav-border)}nxe-sidenav.nxe-sidenav .nxe-nav-heading{padding:var(--nxe-ui-space-1) var(--nxe-ui-space-3);color:var(--nxe-ui-shell-sidenav-fg-muted);font-size:var(--nxe-ui-font-size-xs);font-weight:var(--nxe-ui-font-weight-semibold);line-height:var(--nxe-ui-line-height-xs);letter-spacing:.04em;text-transform:uppercase}nxe-sidenav.nxe-sidenav .nxe-nav-heading.nxe-nav-heading-rail{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}nxe-sidenav.nxe-sidenav .nxe-nav-link{display:flex;align-items:center;gap:var(--nxe-ui-space-3);box-sizing:border-box;width:100%;min-height:var(--nxe-ui-control-min-height);margin:0;padding-block:var(--nxe-ui-space-1);padding-inline:calc(var(--nxe-ui-space-3) + var(--nxe-nav-depth, 0) * var(--nxe-ui-space-4)) var(--nxe-ui-space-3);border:0;border-radius:var(--nxe-ui-radius-md);background:transparent;color:inherit;font:inherit;text-align:start;text-decoration:none;cursor:pointer}nxe-sidenav.nxe-sidenav .nxe-nav-link:hover{background-color:var(--nxe-ui-shell-sidenav-hover-bg)}nxe-sidenav.nxe-sidenav .nxe-nav-link:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:-2px}nxe-sidenav.nxe-sidenav .nxe-nav-link-disabled{opacity:var(--nxe-ui-disabled-opacity);cursor:default}nxe-sidenav.nxe-sidenav .nxe-nav-link-disabled:hover{background:transparent}nxe-sidenav.nxe-sidenav .nxe-nav-current>.nxe-nav-link{background-color:var(--nxe-ui-shell-sidenav-active-bg);color:var(--nxe-ui-shell-sidenav-active-fg);font-weight:var(--nxe-ui-font-weight-semibold)}nxe-sidenav.nxe-sidenav .nxe-nav-holds-current>.nxe-nav-link{font-weight:var(--nxe-ui-font-weight-semibold)}nxe-sidenav.nxe-sidenav .nxe-nav-lead{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:20px;height:20px}nxe-sidenav.nxe-sidenav .nxe-nav-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px}nxe-sidenav.nxe-sidenav .nxe-nav-icon>svg{width:18px;height:18px;fill:currentColor}nxe-sidenav.nxe-sidenav .nxe-nav-glyph{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--nxe-ui-radius-sm);font-size:var(--nxe-ui-font-size-xs);font-weight:var(--nxe-ui-font-weight-semibold);box-shadow:inset 0 0 0 1px currentColor}nxe-sidenav.nxe-sidenav:not(.nxe-sidenav-rail) .nxe-nav-lead:has(.nxe-nav-glyph){display:none}nxe-sidenav.nxe-sidenav .nxe-nav-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-sidenav.nxe-sidenav .nxe-nav-badge{flex:0 0 auto}nxe-sidenav.nxe-sidenav .nxe-nav-chevron{flex:0 0 auto;width:16px;height:16px;transition:transform .15s ease}nxe-sidenav.nxe-sidenav .nxe-nav-parent[aria-expanded=true] .nxe-nav-chevron{transform:rotate(180deg)}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-link{justify-content:center;padding-inline:0}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-label{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}nxe-sidenav.nxe-sidenav.nxe-sidenav-rail .nxe-nav-lead{position:relative}@media(prefers-reduced-motion:reduce){nxe-sidenav.nxe-sidenav,nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer,nxe-sidenav.nxe-sidenav.nxe-sidenav-drawer.nxe-sidenav-open,nxe-sidenav.nxe-sidenav .nxe-nav-chevron,nxe-sidenav.nxe-sidenav .nxe-sidenav-collapse-icon{transition-duration:.01ms;transition-delay:0s}}@media(forced-colors:active){nxe-sidenav.nxe-sidenav .nxe-nav-current>.nxe-nav-link{outline:2px solid Highlight;outline-offset:-2px}}\n"] }]
|
|
318
|
+
}], propDecorators: { navElement: [{ type: i0.ViewChild, args: ['nav', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], iconTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSidenavIconDirective), { ...{ read: TemplateRef }, isSignal: true }] }] } });
|
|
319
|
+
|
|
320
|
+
let nextShellId = 0;
|
|
321
|
+
/**
|
|
322
|
+
* `nxe-shell` is the page frame of an application: an optional banner, the topbar, the sidenav and
|
|
323
|
+
* the main content.
|
|
324
|
+
*
|
|
325
|
+
* ```html
|
|
326
|
+
* <nxe-shell [(collapsed)]="collapsed">
|
|
327
|
+
* <sb-impersonation-banner nxeShellBanner />
|
|
328
|
+
* <nxe-topbar>...</nxe-topbar>
|
|
329
|
+
* <nxe-sidenav [items]="items" />
|
|
330
|
+
* <router-outlet />
|
|
331
|
+
* </nxe-shell>
|
|
332
|
+
* ```
|
|
333
|
+
*
|
|
334
|
+
* **Slots.** `[nxeShellBanner]` (full width above the topbar, for a banner that must stay on screen),
|
|
335
|
+
* an `nxe-topbar`, an `nxe-sidenav`, and everything else, which goes in the `<main>` landmark. A
|
|
336
|
+
* "Skip to content" link, the first tab stop, jumps to that `<main>`.
|
|
337
|
+
*
|
|
338
|
+
* **Responsive.** The shell measures its own width. At or above `breakpoint` (960px by default, the
|
|
339
|
+
* design system's `md`) the sidenav is docked beside the content, expanded or, when `collapsed`, an
|
|
340
|
+
* icon rail. Below it the sidenav is an off-canvas drawer over the content with a scrim: the
|
|
341
|
+
* topbar's toggle button opens it, focus is trapped inside while it is open, Esc and a click on the
|
|
342
|
+
* scrim close it, a navigation closes it, and focus returns to the control that opened it.
|
|
343
|
+
* Widening the shell past the breakpoint closes the drawer.
|
|
344
|
+
*
|
|
345
|
+
* The shell is as tall as `--nxe-shell-height` (default `100dvh`): give it the height of the space
|
|
346
|
+
* it fills, and the content scrolls inside `<main>`.
|
|
347
|
+
*/
|
|
348
|
+
class NxeShellComponent {
|
|
349
|
+
host = inject(ElementRef).nativeElement;
|
|
350
|
+
messages = inject(NxeMessageService);
|
|
351
|
+
router = inject(Router);
|
|
352
|
+
injector = inject(Injector);
|
|
353
|
+
focusTrapFactory = inject(FocusTrapFactory);
|
|
354
|
+
/** Docked sidenav shows only icons. Two-way: `[(collapsed)]`. */
|
|
355
|
+
collapsed = model(false, /* @ts-ignore */
|
|
356
|
+
...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
357
|
+
/** The off-canvas drawer is open (only meaningful below the breakpoint). Two-way: `[(drawerOpen)]`. */
|
|
358
|
+
drawerOpen = model(false, /* @ts-ignore */
|
|
359
|
+
...(ngDevMode ? [{ debugName: "drawerOpen" }] : /* istanbul ignore next */ []));
|
|
360
|
+
/** Shell width, in CSS px, from which the sidenav is docked rather than a drawer. */
|
|
361
|
+
breakpoint = input(960, { ...(ngDevMode ? { debugName: "breakpoint" } : /* istanbul ignore next */ {}), transform: (v) => numberAttribute(v, 960) });
|
|
362
|
+
/** Skip-link target id; unique per shell. */
|
|
363
|
+
mainId = `nxe-shell-main-${nextShellId++}`;
|
|
364
|
+
sidenav = contentChild(NxeSidenavComponent, /* @ts-ignore */
|
|
365
|
+
...(ngDevMode ? [{ debugName: "sidenav" }] : /* istanbul ignore next */ []));
|
|
366
|
+
/** Own width, kept by a ResizeObserver; the window's until the first measurement. */
|
|
367
|
+
width = signal(typeof window === 'undefined' ? Number.POSITIVE_INFINITY : window.innerWidth, /* @ts-ignore */
|
|
368
|
+
...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
369
|
+
mode = computed(() => (this.width() >= this.breakpoint() ? 'docked' : 'drawer'), /* @ts-ignore */
|
|
370
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
371
|
+
rail = computed(() => this.mode() === 'docked' && this.collapsed(), /* @ts-ignore */
|
|
372
|
+
...(ngDevMode ? [{ debugName: "rail" }] : /* istanbul ignore next */ []));
|
|
373
|
+
drawerShown = computed(() => this.mode() === 'drawer' && this.drawerOpen(), /* @ts-ignore */
|
|
374
|
+
...(ngDevMode ? [{ debugName: "drawerShown" }] : /* istanbul ignore next */ []));
|
|
375
|
+
sidenavId = computed(() => this.sidenav()?.id ?? null, /* @ts-ignore */
|
|
376
|
+
...(ngDevMode ? [{ debugName: "sidenavId" }] : /* istanbul ignore next */ []));
|
|
377
|
+
trap = null;
|
|
378
|
+
returnFocusTo = null;
|
|
379
|
+
constructor() {
|
|
380
|
+
const destroyRef = inject(DestroyRef);
|
|
381
|
+
afterNextRender(() => {
|
|
382
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const observer = new ResizeObserver(([entry]) => this.width.set(entry.contentRect.width));
|
|
386
|
+
observer.observe(this.host);
|
|
387
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
388
|
+
});
|
|
389
|
+
// Widening the shell past the breakpoint is the end of the drawer; do not reopen on narrowing.
|
|
390
|
+
toObservable(this.mode)
|
|
391
|
+
.pipe(takeUntilDestroyed())
|
|
392
|
+
.subscribe((mode) => {
|
|
393
|
+
if (mode === 'docked') {
|
|
394
|
+
this.drawerOpen.set(false);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
// Going to another page closes the drawer.
|
|
398
|
+
this.router.events
|
|
399
|
+
.pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed())
|
|
400
|
+
.subscribe(() => this.drawerOpen.set(false));
|
|
401
|
+
// The trap and the focus return follow the drawer however it was opened or closed.
|
|
402
|
+
toObservable(this.drawerShown)
|
|
403
|
+
.pipe(takeUntilDestroyed())
|
|
404
|
+
.subscribe((shown) => (shown ? this.onDrawerShown() : this.onDrawerHidden()));
|
|
405
|
+
destroyRef.onDestroy(() => this.trap?.destroy());
|
|
406
|
+
}
|
|
407
|
+
setCollapsed(collapsed) {
|
|
408
|
+
this.collapsed.set(collapsed);
|
|
409
|
+
}
|
|
410
|
+
/** The hamburger: the drawer below the breakpoint, the dock's collapsed state above it. */
|
|
411
|
+
toggleNavigation() {
|
|
412
|
+
if (this.mode() === 'drawer') {
|
|
413
|
+
if (!this.drawerOpen()) {
|
|
414
|
+
this.returnFocusTo = document.activeElement;
|
|
415
|
+
}
|
|
416
|
+
this.drawerOpen.update((open) => !open);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
this.collapsed.update((collapsed) => !collapsed);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
closeDrawer() {
|
|
423
|
+
this.drawerOpen.set(false);
|
|
424
|
+
}
|
|
425
|
+
t(key) {
|
|
426
|
+
return this.messages.t(key);
|
|
427
|
+
}
|
|
428
|
+
skipToContent(event) {
|
|
429
|
+
event.preventDefault();
|
|
430
|
+
const main = this.host.querySelector(`#${this.mainId}`);
|
|
431
|
+
main?.focus();
|
|
432
|
+
main?.scrollIntoView?.({ block: 'start' });
|
|
433
|
+
}
|
|
434
|
+
onEscape(event) {
|
|
435
|
+
if (this.drawerShown() && !event.defaultPrevented) {
|
|
436
|
+
event.preventDefault();
|
|
437
|
+
this.closeDrawer();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
onDrawerShown() {
|
|
441
|
+
this.returnFocusTo ??= document.activeElement;
|
|
442
|
+
const element = this.sidenav()?.element;
|
|
443
|
+
if (!element) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
this.trap?.destroy();
|
|
447
|
+
this.trap = this.focusTrapFactory.create(element);
|
|
448
|
+
// The drawer is still `inert` until this change detection ends; focus after the render.
|
|
449
|
+
afterNextRender(() => {
|
|
450
|
+
const current = element.querySelector('[aria-current="page"]');
|
|
451
|
+
if (current) {
|
|
452
|
+
current.focus();
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
this.trap?.focusInitialElement();
|
|
456
|
+
}
|
|
457
|
+
}, { injector: this.injector });
|
|
458
|
+
}
|
|
459
|
+
onDrawerHidden() {
|
|
460
|
+
this.trap?.destroy();
|
|
461
|
+
this.trap = null;
|
|
462
|
+
const target = this.returnFocusTo;
|
|
463
|
+
this.returnFocusTo = null;
|
|
464
|
+
if (target?.isConnected) {
|
|
465
|
+
target.focus();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
469
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeShellComponent, isStandalone: true, selector: "nxe-shell", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, drawerOpen: { classPropertyName: "drawerOpen", publicName: "drawerOpen", isSignal: true, isRequired: false, transformFunction: null }, breakpoint: { classPropertyName: "breakpoint", publicName: "breakpoint", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", drawerOpen: "drawerOpenChange" }, host: { listeners: { "keydown.escape": "onEscape($event)" }, properties: { "class.nxe-shell-docked": "mode() === 'docked'", "class.nxe-shell-drawer": "mode() === 'drawer'", "class.nxe-shell-rail": "rail()", "class.nxe-shell-drawer-open": "drawerShown()" }, classAttribute: "nxe-shell" }, providers: [{ provide: NXE_SHELL, useExisting: forwardRef(() => NxeShellComponent) }], queries: [{ propertyName: "sidenav", first: true, predicate: NxeSidenavComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<a class=\"nxe-shell-skip\" [attr.href]=\"'#' + mainId\" (click)=\"skipToContent($event)\">{{ t('shell.skipToContent') }}</a>\n<div class=\"nxe-shell-banner\"><ng-content select=\"[nxeShellBanner]\" /></div>\n<div class=\"nxe-shell-topbar\"><ng-content select=\"nxe-topbar\" /></div>\n<div class=\"nxe-shell-body\">\n <ng-content select=\"nxe-sidenav\" />\n <main class=\"nxe-shell-main\" [id]=\"mainId\" tabindex=\"-1\"><ng-content /></main>\n @if (drawerShown()) {\n <div class=\"nxe-shell-scrim\" aria-hidden=\"true\" (click)=\"closeDrawer()\"></div>\n }\n</div>\n", styles: ["nxe-shell.nxe-shell{position:relative;display:flex;flex-direction:column;box-sizing:border-box;height:var(--nxe-shell-height, 100dvh);min-height:0;overflow:hidden;background-color:var(--nxe-ui-surface);color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}nxe-shell.nxe-shell .nxe-shell-banner{flex:0 0 auto}nxe-shell.nxe-shell .nxe-shell-banner:empty{display:none}nxe-shell.nxe-shell .nxe-shell-topbar{flex:0 0 auto}nxe-shell.nxe-shell .nxe-shell-body{display:flex;flex:1 1 auto;min-height:0}nxe-shell.nxe-shell .nxe-shell-main{flex:1 1 auto;min-width:0;overflow:auto}nxe-shell.nxe-shell .nxe-shell-main:focus{outline:none}nxe-shell.nxe-shell .nxe-shell-scrim{position:absolute;inset:0;z-index:20;background-color:var(--nxe-ui-shell-scrim);animation:nxe-shell-fade .2s ease}nxe-shell.nxe-shell .nxe-shell-skip{position:absolute;inset-block-start:var(--nxe-ui-space-2);inset-inline-start:var(--nxe-ui-space-2);z-index:40;padding:var(--nxe-ui-space-2) var(--nxe-ui-space-4);border-radius:var(--nxe-ui-radius-md);background-color:var(--nxe-ui-accent);color:var(--nxe-ui-accent-contrast);font-size:var(--nxe-ui-font-size-md);font-weight:var(--nxe-ui-font-weight-semibold);text-decoration:none;transform:translateY(-300%)}nxe-shell.nxe-shell .nxe-shell-skip:focus{transform:none;outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}@media(prefers-reduced-motion:reduce){nxe-shell.nxe-shell .nxe-shell-scrim{animation:none}}@keyframes nxe-shell-fade{0%{opacity:0}to{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
470
|
+
}
|
|
471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeShellComponent, decorators: [{
|
|
472
|
+
type: Component,
|
|
473
|
+
args: [{ selector: 'nxe-shell', providers: [{ provide: NXE_SHELL, useExisting: forwardRef(() => NxeShellComponent) }], host: {
|
|
474
|
+
class: 'nxe-shell',
|
|
475
|
+
'[class.nxe-shell-docked]': "mode() === 'docked'",
|
|
476
|
+
'[class.nxe-shell-drawer]': "mode() === 'drawer'",
|
|
477
|
+
'[class.nxe-shell-rail]': 'rail()',
|
|
478
|
+
'[class.nxe-shell-drawer-open]': 'drawerShown()',
|
|
479
|
+
'(keydown.escape)': 'onEscape($event)',
|
|
480
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<a class=\"nxe-shell-skip\" [attr.href]=\"'#' + mainId\" (click)=\"skipToContent($event)\">{{ t('shell.skipToContent') }}</a>\n<div class=\"nxe-shell-banner\"><ng-content select=\"[nxeShellBanner]\" /></div>\n<div class=\"nxe-shell-topbar\"><ng-content select=\"nxe-topbar\" /></div>\n<div class=\"nxe-shell-body\">\n <ng-content select=\"nxe-sidenav\" />\n <main class=\"nxe-shell-main\" [id]=\"mainId\" tabindex=\"-1\"><ng-content /></main>\n @if (drawerShown()) {\n <div class=\"nxe-shell-scrim\" aria-hidden=\"true\" (click)=\"closeDrawer()\"></div>\n }\n</div>\n", styles: ["nxe-shell.nxe-shell{position:relative;display:flex;flex-direction:column;box-sizing:border-box;height:var(--nxe-shell-height, 100dvh);min-height:0;overflow:hidden;background-color:var(--nxe-ui-surface);color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}nxe-shell.nxe-shell .nxe-shell-banner{flex:0 0 auto}nxe-shell.nxe-shell .nxe-shell-banner:empty{display:none}nxe-shell.nxe-shell .nxe-shell-topbar{flex:0 0 auto}nxe-shell.nxe-shell .nxe-shell-body{display:flex;flex:1 1 auto;min-height:0}nxe-shell.nxe-shell .nxe-shell-main{flex:1 1 auto;min-width:0;overflow:auto}nxe-shell.nxe-shell .nxe-shell-main:focus{outline:none}nxe-shell.nxe-shell .nxe-shell-scrim{position:absolute;inset:0;z-index:20;background-color:var(--nxe-ui-shell-scrim);animation:nxe-shell-fade .2s ease}nxe-shell.nxe-shell .nxe-shell-skip{position:absolute;inset-block-start:var(--nxe-ui-space-2);inset-inline-start:var(--nxe-ui-space-2);z-index:40;padding:var(--nxe-ui-space-2) var(--nxe-ui-space-4);border-radius:var(--nxe-ui-radius-md);background-color:var(--nxe-ui-accent);color:var(--nxe-ui-accent-contrast);font-size:var(--nxe-ui-font-size-md);font-weight:var(--nxe-ui-font-weight-semibold);text-decoration:none;transform:translateY(-300%)}nxe-shell.nxe-shell .nxe-shell-skip:focus{transform:none;outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}@media(prefers-reduced-motion:reduce){nxe-shell.nxe-shell .nxe-shell-scrim{animation:none}}@keyframes nxe-shell-fade{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
481
|
+
}], ctorParameters: () => [], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], drawerOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "drawerOpen", required: false }] }, { type: i0.Output, args: ["drawerOpenChange"] }], breakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "breakpoint", required: false }] }], sidenav: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeSidenavComponent), { isSignal: true }] }] } });
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* `nxe-topbar` is the bar across the top of an `nxe-shell`: a start, a centre and an end slot.
|
|
485
|
+
*
|
|
486
|
+
* ```html
|
|
487
|
+
* <nxe-topbar>
|
|
488
|
+
* <img nxeTopbarStart src="logo.svg" alt="Nextera" />
|
|
489
|
+
* <sb-search nxeTopbarCenter />
|
|
490
|
+
* <ng-container nxeTopbarEnd>
|
|
491
|
+
* <sb-company-switcher />
|
|
492
|
+
* <sb-notifications />
|
|
493
|
+
* </ng-container>
|
|
494
|
+
* </nxe-topbar>
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* The start slot is preceded by a hamburger button: an icon-only `nxe-button` named "Toggle
|
|
498
|
+
* navigation" with `aria-controls` the sidenav and `aria-expanded` the sidenav's state. Inside an
|
|
499
|
+
* `nxe-shell` it opens and closes the drawer below the breakpoint and collapses and expands the dock
|
|
500
|
+
* above it. `menuToggle` also fires, so a topbar used without a shell can drive its own sidenav.
|
|
501
|
+
* `[nxeTopbarEnd]` elements sit at the inline end of the bar, where a company switcher, language
|
|
502
|
+
* toggle, notification bell and user menu go.
|
|
503
|
+
*/
|
|
504
|
+
class NxeTopbarComponent {
|
|
505
|
+
messages = inject(NxeMessageService);
|
|
506
|
+
shell = inject(NXE_SHELL, { optional: true });
|
|
507
|
+
/** Show the hamburger button. Default true. */
|
|
508
|
+
menuButton = input(true, { ...(ngDevMode ? { debugName: "menuButton" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
509
|
+
/** The hamburger was pressed. */
|
|
510
|
+
menuToggle = output();
|
|
511
|
+
label = computed(() => this.messages.t('shell.toggleNavigation'), /* @ts-ignore */
|
|
512
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
513
|
+
controls = computed(() => this.shell?.sidenavId() ?? null, /* @ts-ignore */
|
|
514
|
+
...(ngDevMode ? [{ debugName: "controls" }] : /* istanbul ignore next */ []));
|
|
515
|
+
/** Drawer: open. Dock: not collapsed. Without a shell the button says nothing about it. */
|
|
516
|
+
expanded = computed(() => {
|
|
517
|
+
if (!this.shell) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
return this.shell.mode() === 'drawer' ? this.shell.drawerShown() : !this.shell.rail();
|
|
521
|
+
}, /* @ts-ignore */
|
|
522
|
+
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
523
|
+
toggle() {
|
|
524
|
+
this.shell?.toggleNavigation();
|
|
525
|
+
this.menuToggle.emit();
|
|
526
|
+
}
|
|
527
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
528
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTopbarComponent, isStandalone: true, selector: "nxe-topbar", inputs: { menuButton: { classPropertyName: "menuButton", publicName: "menuButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuToggle: "menuToggle" }, host: { classAttribute: "nxe-topbar" }, ngImport: i0, template: "<div class=\"nxe-topbar-start\">\n @if (menuButton()) {\n <nxe-button\n class=\"nxe-topbar-menu\"\n stylingMode=\"text\"\n [attr.aria-label]=\"label()\"\n [attr.aria-controls]=\"controls()\"\n [attr.aria-expanded]=\"expanded()\"\n (onClick)=\"toggle()\">\n <svg nxeButtonIcon viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M2 3.5h12V5H2zm0 3.75h12v1.5H2zM2 11h12v1.5H2z\" />\n </svg>\n </nxe-button>\n }\n <ng-content select=\"[nxeTopbarStart]\" />\n</div>\n<div class=\"nxe-topbar-center\"><ng-content select=\"[nxeTopbarCenter]\" /></div>\n<div class=\"nxe-topbar-end\"><ng-content select=\"[nxeTopbarEnd]\" /></div>\n", styles: ["nxe-topbar.nxe-topbar{display:flex;align-items:center;gap:var(--nxe-ui-space-4);box-sizing:border-box;height:var(--nxe-ui-shell-topbar-height);padding-inline:var(--nxe-ui-space-3);border-block-end:1px solid var(--nxe-ui-border);background-color:var(--nxe-ui-shell-topbar-bg);color:var(--nxe-ui-shell-topbar-fg);font-family:var(--nxe-ui-font-family)}nxe-topbar.nxe-topbar .nxe-topbar-start,nxe-topbar.nxe-topbar .nxe-topbar-end{display:flex;align-items:center;flex:0 0 auto;gap:var(--nxe-ui-space-2)}nxe-topbar.nxe-topbar .nxe-topbar-center{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:var(--nxe-ui-space-2)}nxe-topbar.nxe-topbar .nxe-topbar-end{margin-inline-start:auto}nxe-topbar.nxe-topbar .nxe-topbar-menu{color:inherit}\n"], dependencies: [{ kind: "component", type: NxeButtonComponent, selector: "nxe-button", inputs: ["text", "type", "stylingMode", "size", "loading", "block", "disabled", "validationGroup", "focusStateEnabled", "icon", "height", "width"], outputs: ["onClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
529
|
+
}
|
|
530
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTopbarComponent, decorators: [{
|
|
531
|
+
type: Component,
|
|
532
|
+
args: [{ selector: 'nxe-topbar', imports: [NxeButtonComponent], host: { class: 'nxe-topbar' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-topbar-start\">\n @if (menuButton()) {\n <nxe-button\n class=\"nxe-topbar-menu\"\n stylingMode=\"text\"\n [attr.aria-label]=\"label()\"\n [attr.aria-controls]=\"controls()\"\n [attr.aria-expanded]=\"expanded()\"\n (onClick)=\"toggle()\">\n <svg nxeButtonIcon viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M2 3.5h12V5H2zm0 3.75h12v1.5H2zM2 11h12v1.5H2z\" />\n </svg>\n </nxe-button>\n }\n <ng-content select=\"[nxeTopbarStart]\" />\n</div>\n<div class=\"nxe-topbar-center\"><ng-content select=\"[nxeTopbarCenter]\" /></div>\n<div class=\"nxe-topbar-end\"><ng-content select=\"[nxeTopbarEnd]\" /></div>\n", styles: ["nxe-topbar.nxe-topbar{display:flex;align-items:center;gap:var(--nxe-ui-space-4);box-sizing:border-box;height:var(--nxe-ui-shell-topbar-height);padding-inline:var(--nxe-ui-space-3);border-block-end:1px solid var(--nxe-ui-border);background-color:var(--nxe-ui-shell-topbar-bg);color:var(--nxe-ui-shell-topbar-fg);font-family:var(--nxe-ui-font-family)}nxe-topbar.nxe-topbar .nxe-topbar-start,nxe-topbar.nxe-topbar .nxe-topbar-end{display:flex;align-items:center;flex:0 0 auto;gap:var(--nxe-ui-space-2)}nxe-topbar.nxe-topbar .nxe-topbar-center{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:var(--nxe-ui-space-2)}nxe-topbar.nxe-topbar .nxe-topbar-end{margin-inline-start:auto}nxe-topbar.nxe-topbar .nxe-topbar-menu{color:inherit}\n"] }]
|
|
533
|
+
}], propDecorators: { menuButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuButton", required: false }] }], menuToggle: [{ type: i0.Output, args: ["menuToggle"] }] } });
|
|
534
|
+
|
|
535
|
+
// @nexterajs/ui/shell — public API.
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Generated bundle index. Do not edit.
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
export { NXE_SHELL, NxeShellComponent, NxeSidenavComponent, NxeSidenavIconDirective, NxeTopbarComponent };
|
|
542
|
+
//# sourceMappingURL=nexterajs-ui-shell.mjs.map
|