@design-factory/angular 21.0.0-next.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.
@@ -0,0 +1,1354 @@
1
+ import { createNavManager, useDirectiveForHost } from '@agnos-ui/angular-headless';
2
+ import { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { input, model, inject, contentChild, computed, Directive, effect, ChangeDetectionStrategy, Component, signal, viewChild, contentChildren, booleanAttribute, linkedSignal, ElementRef, PLATFORM_ID, Injector, afterRenderEffect, afterNextRender } from '@angular/core';
5
+ import { toSignal } from '@angular/core/rxjs-interop';
6
+ import * as i1$1 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+ import { DfDrawerComponent } from '@design-factory/angular/drawer';
9
+ import { DfMedia } from '@design-factory/design-factory';
10
+ import * as i1 from '@angular/router';
11
+ import { RouterModule } from '@angular/router';
12
+
13
+ /**
14
+ * Base item class that is extended by desktop and mobile item components
15
+ * The item represents a container which can hold other items or links
16
+ * Handles collapsed state
17
+ * @internal
18
+ */
19
+ class DfSidenavItemBaseComponent {
20
+ constructor() {
21
+ /**
22
+ * The label of the item
23
+ */
24
+ this.label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
25
+ /**
26
+ * Collapsed state of the item
27
+ * Only used for the desktop version
28
+ * @default false
29
+ */
30
+ this.collapsed = model(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
31
+ /**
32
+ * The icon class name or inline content (e.g., font awesome class)
33
+ * @default ''
34
+ */
35
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
36
+ /**
37
+ * Whether the wrapper component has icon content (internally used)
38
+ * @default false
39
+ */
40
+ this.wrapperHasIconContent = input(false, ...(ngDevMode ? [{ debugName: "wrapperHasIconContent" }] : []));
41
+ this.sidenav = inject(DfSidenavComponent);
42
+ this.wrapperInstance = inject(DfSidenavItemComponent);
43
+ // check for the parent item to determine nesting level
44
+ this.parentItem = inject(DfSidenavItemComponent, {
45
+ optional: true,
46
+ skipSelf: true
47
+ });
48
+ this.isFirstLevel = this.parentItem?.['parentWrapper'] === null;
49
+ this.iconContent = contentChild('dfSidenavIcon', ...(ngDevMode ? [{ debugName: "iconContent" }] : []));
50
+ this.hasActiveLinks = computed(() => this.childLinks().some((link) => link?.isActiveLink()), ...(ngDevMode ? [{ debugName: "hasActiveLinks" }] : []));
51
+ this.hasActiveItems = computed(() => this.childItems().some((item) => item?.hasActiveLinks()), ...(ngDevMode ? [{ debugName: "hasActiveItems" }] : []));
52
+ this.hasActiveChildren = computed(() => this.hasActiveLinks() || this.hasActiveItems(), ...(ngDevMode ? [{ debugName: "hasActiveChildren" }] : []));
53
+ /**
54
+ * Computed signal indicating if the item has visible children
55
+ */
56
+ this.hasVisibleChildren = computed(() => this.childLinks().some((link) => link?.['isVisible']()), ...(ngDevMode ? [{ debugName: "hasVisibleChildren" }] : []));
57
+ }
58
+ /**
59
+ * Method to toggle the collapsed state of the item
60
+ * If the sidenav is minimized, it will expand it and uncollapse the item
61
+ */
62
+ toggleCollapse() {
63
+ if (this.sidenav.isMinimized()) {
64
+ this.sidenav.toggleMinimize(false);
65
+ this.collapsed.set(false);
66
+ }
67
+ else {
68
+ this.collapsed.set(!this.collapsed());
69
+ }
70
+ }
71
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
72
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.0.6", type: DfSidenavItemBaseComponent, isStandalone: true, inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, wrapperHasIconContent: { classPropertyName: "wrapperHasIconContent", publicName: "wrapperHasIconContent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, queries: [{ propertyName: "iconContent", first: true, predicate: ["dfSidenavIcon"], descendants: true, isSignal: true }], ngImport: i0 }); }
73
+ }
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemBaseComponent, decorators: [{
75
+ type: Directive
76
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], wrapperHasIconContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperHasIconContent", required: false }] }], iconContent: [{ type: i0.ContentChild, args: ['dfSidenavIcon', { isSignal: true }] }] } });
77
+
78
+ /**
79
+ * Component representing an item in the sidenav for desktop devices
80
+ * The item represents a container which can hold other items or links
81
+ * @internal
82
+ */
83
+ class DfSidenavItemDesktopComponent extends DfSidenavItemBaseComponent {
84
+ constructor() {
85
+ super();
86
+ this.childLinks = computed(() => this.wrapperInstance['childrenLinks']().map((link) => link['desktopInstance']()), ...(ngDevMode ? [{ debugName: "childLinks" }] : []));
87
+ this.childItems = computed(() => this.wrapperInstance['childrenItems']().map((item) => item['desktopInstance']()), ...(ngDevMode ? [{ debugName: "childItems" }] : []));
88
+ this.isVisible = computed(() => {
89
+ const search = this.sidenav['searchTerm']().toLowerCase().trim() || '';
90
+ if (!search) {
91
+ return true;
92
+ }
93
+ return this.childLinks().some((link) => link?.['isVisible']());
94
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
95
+ // Automatically collapse when minimized
96
+ effect(() => {
97
+ if (this.sidenav.isMinimized()) {
98
+ this.collapsed.set(true);
99
+ }
100
+ });
101
+ // Auto-expand when searching and has visible children
102
+ effect(() => {
103
+ if (this.sidenav['searchTerm']().trim() && this.hasVisibleChildren()) {
104
+ this.collapsed.set(false);
105
+ }
106
+ });
107
+ // Auto-expand when the child is active and sidenav is not minimized
108
+ effect(() => {
109
+ if (this.hasActiveChildren() && !this.sidenav.isMinimized()) {
110
+ this.collapsed.set(false);
111
+ }
112
+ });
113
+ }
114
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemDesktopComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
115
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavItemDesktopComponent, isStandalone: true, selector: "df-sidenav-item-desktop", host: { properties: { "class.d-none": "!isVisible() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))" } }, usesInheritance: true, ngImport: i0, template: `
116
+ <button
117
+ class="btn df-sidenav-button d-flex gap-3"
118
+ [class.df-sidenav-active-level]="sidenav.isMinimized() && hasActiveChildren() && isFirstLevel"
119
+ [class.df-sidenav-active]="hasActiveChildren()"
120
+ [class.w-100]="!sidenav.isMinimized()"
121
+ (click)="toggleCollapse()"
122
+ [attr.aria-expanded]="!collapsed()"
123
+ [class.justify-content-start]="!sidenav.isMinimized()"
124
+ [attr.aria-label]="label()"
125
+ >
126
+ <ng-content select="[dfSidenavIcon]" />
127
+ @if (icon()) {
128
+ <span class="{{ icon() }}"></span>
129
+ }
130
+ @if (!sidenav.isMinimized()) {
131
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
132
+ <span
133
+ [class.expanded]="!collapsed()"
134
+ class="fa-light df-sidenav-collapse-toggle fa-chevron-down ms-auto"
135
+ ></span>
136
+ }
137
+ </button>
138
+
139
+ <div [class.d-none]="collapsed()" role="list">
140
+ <ng-container [ngTemplateOutlet]="content" />
141
+ </div>
142
+ <ng-template #content>
143
+ <ng-content />
144
+ </ng-template>
145
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
146
+ }
147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemDesktopComponent, decorators: [{
148
+ type: Component,
149
+ args: [{
150
+ changeDetection: ChangeDetectionStrategy.OnPush,
151
+ selector: 'df-sidenav-item-desktop',
152
+ template: `
153
+ <button
154
+ class="btn df-sidenav-button d-flex gap-3"
155
+ [class.df-sidenav-active-level]="sidenav.isMinimized() && hasActiveChildren() && isFirstLevel"
156
+ [class.df-sidenav-active]="hasActiveChildren()"
157
+ [class.w-100]="!sidenav.isMinimized()"
158
+ (click)="toggleCollapse()"
159
+ [attr.aria-expanded]="!collapsed()"
160
+ [class.justify-content-start]="!sidenav.isMinimized()"
161
+ [attr.aria-label]="label()"
162
+ >
163
+ <ng-content select="[dfSidenavIcon]" />
164
+ @if (icon()) {
165
+ <span class="{{ icon() }}"></span>
166
+ }
167
+ @if (!sidenav.isMinimized()) {
168
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
169
+ <span
170
+ [class.expanded]="!collapsed()"
171
+ class="fa-light df-sidenav-collapse-toggle fa-chevron-down ms-auto"
172
+ ></span>
173
+ }
174
+ </button>
175
+
176
+ <div [class.d-none]="collapsed()" role="list">
177
+ <ng-container [ngTemplateOutlet]="content" />
178
+ </div>
179
+ <ng-template #content>
180
+ <ng-content />
181
+ </ng-template>
182
+ `,
183
+ host: {
184
+ '[class.d-none]': '!isVisible() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))'
185
+ },
186
+ imports: [NgTemplateOutlet]
187
+ }]
188
+ }], ctorParameters: () => [] });
189
+
190
+ /**
191
+ * Component representing an item in the sidenav for mobile devices
192
+ * The item represents a container which can hold other items or links
193
+ * @internal
194
+ */
195
+ class DfSidenavItemMobileComponent extends DfSidenavItemBaseComponent {
196
+ constructor() {
197
+ super(...arguments);
198
+ this.childLinks = computed(() => this.wrapperInstance['childrenLinks']().map((link) => link['mobileInstance']()), ...(ngDevMode ? [{ debugName: "childLinks" }] : []));
199
+ this.childItems = computed(() => this.wrapperInstance['childrenItems']().map((item) => item['mobileInstance']()), ...(ngDevMode ? [{ debugName: "childItems" }] : []));
200
+ this.isSearching = computed(() => !!this.sidenav['searchTerm']().trim(), ...(ngDevMode ? [{ debugName: "isSearching" }] : []));
201
+ // Check if this item is in the navigation path (is an ancestor of current panel)
202
+ this.isInNavigationPath = computed(() => this.sidenav['mobileNavigationStack']().includes(this.wrapperInstance), ...(ngDevMode ? [{ debugName: "isInNavigationPath" }] : []));
203
+ // Check if this item should show as mobile header
204
+ this.shouldShowMobileHeader = computed(() => {
205
+ // Don't show header when searching
206
+ if (this.isSearching()) {
207
+ return false;
208
+ }
209
+ return this.sidenav['currentMobilePanel']() === this.wrapperInstance;
210
+ }, ...(ngDevMode ? [{ debugName: "shouldShowMobileHeader" }] : []));
211
+ // Determine if this item's children should be visible
212
+ this.shouldShowChildren = computed(() => {
213
+ // Mobile with search: show children to display matching links
214
+ if (this.isSearching()) {
215
+ return this.hasMatchingChildren();
216
+ }
217
+ // Mobile without search: show if this item is in the navigation path
218
+ return this.isInNavigationPath();
219
+ }, ...(ngDevMode ? [{ debugName: "shouldShowChildren" }] : []));
220
+ // Determine if this item should be visible in the list
221
+ this.isVisibleInList = computed(() => {
222
+ // Mobile with search: don't show items, only links (items just provide structure)
223
+ if (this.isSearching()) {
224
+ return false;
225
+ }
226
+ // Mobile without search: use navigation levels
227
+ const currentPanel = this.sidenav['currentMobilePanel']();
228
+ if (currentPanel === null) {
229
+ // Root level: show only first-level items
230
+ return this.isFirstLevel && this.isVisible();
231
+ }
232
+ else if (currentPanel === this.parentItem) {
233
+ // This is the active panel, don't show it in the list (it's in the header)
234
+ return false;
235
+ }
236
+ else {
237
+ // Show items whose parent matches the current panel
238
+ const parentMobile = this.parentItem?.['parentWrapper'];
239
+ return parentMobile === currentPanel && this.isVisible();
240
+ }
241
+ }, ...(ngDevMode ? [{ debugName: "isVisibleInList" }] : []));
242
+ // Determine if the entire component should be hidden
243
+ this.shouldHideComponent = computed(() => {
244
+ // Don't hide if this is the current mobile panel showing its children
245
+ if (this.shouldShowMobileHeader()) {
246
+ return false;
247
+ }
248
+ // Otherwise, use the regular visibility logic
249
+ return !this.isVisibleInList() && !this.shouldShowChildren();
250
+ }, ...(ngDevMode ? [{ debugName: "shouldHideComponent" }] : []));
251
+ this.isVisible = computed(() => {
252
+ const search = this.sidenav['searchTerm']().toLowerCase().trim() || '';
253
+ if (!search) {
254
+ return true;
255
+ }
256
+ // Check if this item's label matches
257
+ return String(this.label()).toLowerCase().includes(search);
258
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
259
+ // Check if this item has any matching children (for showing the container)
260
+ this.hasMatchingChildren = computed(() => {
261
+ const search = this.sidenav['searchTerm']().toLowerCase().trim() || '';
262
+ if (!search) {
263
+ return false;
264
+ }
265
+ return this.childLinks().some((link) => link?.['isVisible']());
266
+ }, ...(ngDevMode ? [{ debugName: "hasMatchingChildren" }] : []));
267
+ }
268
+ /**
269
+ * Handle click event on the item
270
+ */
271
+ handleItemClick() {
272
+ this.sidenav.navigateToChildren(this.wrapperInstance);
273
+ }
274
+ /**
275
+ * Handle click event on the header back button
276
+ */
277
+ handleBackClick() {
278
+ this.sidenav.navigateBack();
279
+ }
280
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemMobileComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
281
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavItemMobileComponent, isStandalone: true, selector: "df-sidenav-item-mobile", host: { properties: { "class.d-none": "shouldHideComponent() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))" }, classAttribute: "mobile" }, usesInheritance: true, ngImport: i0, template: `
282
+ @if (shouldShowMobileHeader()) {
283
+ <div class="df-sidenav-mobile-header mobile mb-2" role="listitem">
284
+ <button
285
+ class="btn w-100 d-flex gap-0 align-items-center justify-content-start df-sidenav-button"
286
+ (click)="handleBackClick()"
287
+ [class.df-sidenav-active]="hasActiveChildren()"
288
+ >
289
+ <span class="fa-light df-sidenav-mobile-header-back-arrow fa-arrow-left me-4"></span>
290
+ <ng-content select="[dfSidenavIcon]" />
291
+ @if (icon()) {
292
+ <span class="{{ icon() }}"></span>
293
+ }
294
+ <span class="ms-3 sidenav-label">{{ label() }}</span>
295
+ </button>
296
+ </div>
297
+ }
298
+
299
+ <!-- Regular item button (hidden when it's the active mobile panel) -->
300
+ @if (isVisibleInList()) {
301
+ <!-- Mobile search: Show as breadcrumb/path (non-clickable) -->
302
+ @if (isSearching()) {
303
+ <div class="btn w-100 d-flex gap-3 justify-content-start text-muted disabled pe-none">
304
+ <ng-content select="[dfSidenavIcon]" />
305
+ @if (icon()) {
306
+ <span class="{{ icon() }}"></span>
307
+ }
308
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
309
+ <span class="fa-light df-sidenav-collapse-toggle fa-chevron-right ms-auto"></span>
310
+ </div>
311
+ } @else {
312
+ <!-- Regular clickable button -->
313
+ <button
314
+ class="btn w-100 d-flex gap-3 justify-content-start df-sidenav-button"
315
+ (click)="handleItemClick()"
316
+ [class.df-sidenav-active]="hasActiveChildren()"
317
+ >
318
+ <ng-content select="[dfSidenavIcon]" />
319
+ @if (icon()) {
320
+ <span class="{{ icon() }}"></span>
321
+ }
322
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
323
+ <!-- Mobile: Show forward arrow for items with children -->
324
+ <span class="fa-light df-sidenav-collapse-toggle fa-chevron-right ms-auto"></span>
325
+ </button>
326
+ }
327
+ }
328
+
329
+ <!-- Children container - rendered separately from the button -->
330
+ @if (shouldShowChildren()) {
331
+ <div role="list" class="mobile">
332
+ <ng-container [ngTemplateOutlet]="content" />
333
+ </div>
334
+ }
335
+
336
+ <ng-template #content>
337
+ <ng-content />
338
+ </ng-template>
339
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
340
+ }
341
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemMobileComponent, decorators: [{
342
+ type: Component,
343
+ args: [{
344
+ changeDetection: ChangeDetectionStrategy.OnPush,
345
+ selector: 'df-sidenav-item-mobile',
346
+ template: `
347
+ @if (shouldShowMobileHeader()) {
348
+ <div class="df-sidenav-mobile-header mobile mb-2" role="listitem">
349
+ <button
350
+ class="btn w-100 d-flex gap-0 align-items-center justify-content-start df-sidenav-button"
351
+ (click)="handleBackClick()"
352
+ [class.df-sidenav-active]="hasActiveChildren()"
353
+ >
354
+ <span class="fa-light df-sidenav-mobile-header-back-arrow fa-arrow-left me-4"></span>
355
+ <ng-content select="[dfSidenavIcon]" />
356
+ @if (icon()) {
357
+ <span class="{{ icon() }}"></span>
358
+ }
359
+ <span class="ms-3 sidenav-label">{{ label() }}</span>
360
+ </button>
361
+ </div>
362
+ }
363
+
364
+ <!-- Regular item button (hidden when it's the active mobile panel) -->
365
+ @if (isVisibleInList()) {
366
+ <!-- Mobile search: Show as breadcrumb/path (non-clickable) -->
367
+ @if (isSearching()) {
368
+ <div class="btn w-100 d-flex gap-3 justify-content-start text-muted disabled pe-none">
369
+ <ng-content select="[dfSidenavIcon]" />
370
+ @if (icon()) {
371
+ <span class="{{ icon() }}"></span>
372
+ }
373
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
374
+ <span class="fa-light df-sidenav-collapse-toggle fa-chevron-right ms-auto"></span>
375
+ </div>
376
+ } @else {
377
+ <!-- Regular clickable button -->
378
+ <button
379
+ class="btn w-100 d-flex gap-3 justify-content-start df-sidenav-button"
380
+ (click)="handleItemClick()"
381
+ [class.df-sidenav-active]="hasActiveChildren()"
382
+ >
383
+ <ng-content select="[dfSidenavIcon]" />
384
+ @if (icon()) {
385
+ <span class="{{ icon() }}"></span>
386
+ }
387
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
388
+ <!-- Mobile: Show forward arrow for items with children -->
389
+ <span class="fa-light df-sidenav-collapse-toggle fa-chevron-right ms-auto"></span>
390
+ </button>
391
+ }
392
+ }
393
+
394
+ <!-- Children container - rendered separately from the button -->
395
+ @if (shouldShowChildren()) {
396
+ <div role="list" class="mobile">
397
+ <ng-container [ngTemplateOutlet]="content" />
398
+ </div>
399
+ }
400
+
401
+ <ng-template #content>
402
+ <ng-content />
403
+ </ng-template>
404
+ `,
405
+ host: {
406
+ class: 'mobile',
407
+ '[class.d-none]': 'shouldHideComponent() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))'
408
+ },
409
+ imports: [NgTemplateOutlet]
410
+ }]
411
+ }] });
412
+
413
+ /**
414
+ * Base link class that is extended by desktop and mobile link components
415
+ * @internal
416
+ */
417
+ class DfSidenavLinkBaseComponent {
418
+ constructor() {
419
+ /**
420
+ * The router link path for to the link
421
+ */
422
+ this.linkPath = input.required(...(ngDevMode ? [{ debugName: "linkPath" }] : []));
423
+ /**
424
+ * The label of the link
425
+ */
426
+ this.label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
427
+ /**
428
+ * The icon class name or inline content (e.g., font awesome class)
429
+ */
430
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
431
+ /**
432
+ * Whether the wrapper component has icon content (internally used)
433
+ */
434
+ this.wrapperHasIconContent = input(false, ...(ngDevMode ? [{ debugName: "wrapperHasIconContent" }] : []));
435
+ /**
436
+ * Indicates if the link is currently active based on the Router state
437
+ */
438
+ this.isActiveLink = signal(false, ...(ngDevMode ? [{ debugName: "isActiveLink" }] : []));
439
+ /**
440
+ * Options to determine when the router link is considered active
441
+ */
442
+ this.routerLinkActiveOptions = input({ exact: false }, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
443
+ this.isSmallScreen = toSignal(inject(DfMedia).getObservable(['sm', 'xs']), { initialValue: false });
444
+ this.sidenav = inject(DfSidenavComponent);
445
+ this.parentItem = inject(DfSidenavItemComponent, {
446
+ optional: true,
447
+ skipSelf: true
448
+ });
449
+ this.isFirstLevel = this.parentItem === null;
450
+ this.iconContent = contentChild('dfSidenavIcon', ...(ngDevMode ? [{ debugName: "iconContent" }] : []));
451
+ /**
452
+ * Computed signal indicating if the link is visible based on search term
453
+ */
454
+ this.isVisible = computed(() => {
455
+ const search = this.sidenav['searchTerm']().toLowerCase().trim() || '';
456
+ if (!search) {
457
+ return true;
458
+ }
459
+ return this.label().toLowerCase().includes(search);
460
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
461
+ }
462
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
463
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.0.6", type: DfSidenavLinkBaseComponent, isStandalone: true, inputs: { linkPath: { classPropertyName: "linkPath", publicName: "linkPath", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, wrapperHasIconContent: { classPropertyName: "wrapperHasIconContent", publicName: "wrapperHasIconContent", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActiveOptions: { classPropertyName: "routerLinkActiveOptions", publicName: "routerLinkActiveOptions", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "iconContent", first: true, predicate: ["dfSidenavIcon"], descendants: true, isSignal: true }], ngImport: i0 }); }
464
+ }
465
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkBaseComponent, decorators: [{
466
+ type: Directive
467
+ }], propDecorators: { linkPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkPath", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], wrapperHasIconContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperHasIconContent", required: false }] }], routerLinkActiveOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLinkActiveOptions", required: false }] }], iconContent: [{ type: i0.ContentChild, args: ['dfSidenavIcon', { isSignal: true }] }] } });
468
+
469
+ /**
470
+ * Component representing a leaf in the sidenav for desktop devices
471
+ * @internal
472
+ */
473
+ class DfSidenavLinkDesktopComponent extends DfSidenavLinkBaseComponent {
474
+ constructor() {
475
+ super(...arguments);
476
+ this.anchor = viewChild.required('anchor');
477
+ }
478
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkDesktopComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
479
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavLinkDesktopComponent, isStandalone: true, selector: "df-sidenav-link-desktop", host: { properties: { "class.d-none": "!isVisible() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))" }, classAttribute: "d-flex" }, viewQueries: [{ propertyName: "anchor", first: true, predicate: ["anchor"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
480
+ <a
481
+ class="btn df-sidenav-button d-flex gap-3"
482
+ [class.w-100]="!sidenav.isMinimized()"
483
+ routerLinkActive="df-sidenav-active-level"
484
+ ariaCurrentWhenActive="page"
485
+ (isActiveChange)="isActiveLink.set($event)"
486
+ [routerLink]="linkPath()"
487
+ [class.justify-content-start]="!sidenav.isMinimized()"
488
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
489
+ [attr.aria-label]="label()"
490
+ (click)="isSmallScreen() && sidenav.toggleMinimize()"
491
+ #anchor
492
+ >
493
+ <ng-container [ngTemplateOutlet]="content" />
494
+ </a>
495
+ <ng-template #content>
496
+ <ng-content select="[dfSidenavIcon]" />
497
+ @if (icon()) {
498
+ <span class="{{ icon() }}"></span>
499
+ }
500
+ @if (!sidenav.isMinimized()) {
501
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
502
+ }
503
+ </ng-template>
504
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
505
+ }
506
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkDesktopComponent, decorators: [{
507
+ type: Component,
508
+ args: [{
509
+ changeDetection: ChangeDetectionStrategy.OnPush,
510
+ selector: 'df-sidenav-link-desktop',
511
+ template: `
512
+ <a
513
+ class="btn df-sidenav-button d-flex gap-3"
514
+ [class.w-100]="!sidenav.isMinimized()"
515
+ routerLinkActive="df-sidenav-active-level"
516
+ ariaCurrentWhenActive="page"
517
+ (isActiveChange)="isActiveLink.set($event)"
518
+ [routerLink]="linkPath()"
519
+ [class.justify-content-start]="!sidenav.isMinimized()"
520
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
521
+ [attr.aria-label]="label()"
522
+ (click)="isSmallScreen() && sidenav.toggleMinimize()"
523
+ #anchor
524
+ >
525
+ <ng-container [ngTemplateOutlet]="content" />
526
+ </a>
527
+ <ng-template #content>
528
+ <ng-content select="[dfSidenavIcon]" />
529
+ @if (icon()) {
530
+ <span class="{{ icon() }}"></span>
531
+ }
532
+ @if (!sidenav.isMinimized()) {
533
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
534
+ }
535
+ </ng-template>
536
+ `,
537
+ host: {
538
+ '[class.d-none]': '!isVisible() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))',
539
+ class: 'd-flex'
540
+ },
541
+ imports: [RouterModule, NgTemplateOutlet]
542
+ }]
543
+ }], propDecorators: { anchor: [{ type: i0.ViewChild, args: ['anchor', { isSignal: true }] }] } });
544
+
545
+ /**
546
+ * Component representing a leaf in the sidenav for mobile devices
547
+ * @internal
548
+ */
549
+ class DfSidenavLinkMobileComponent extends DfSidenavLinkBaseComponent {
550
+ constructor() {
551
+ super(...arguments);
552
+ // Inject link wrapper to get parent wrapper reference
553
+ this.linkWrapper = inject(DfSidenavLinkComponent);
554
+ this.currentMobilePanel = computed(() => this.sidenav['currentMobilePanel']() ?? null, ...(ngDevMode ? [{ debugName: "currentMobilePanel" }] : []));
555
+ this.isSearching = computed(() => !!this.sidenav['searchTerm']().trim(), ...(ngDevMode ? [{ debugName: "isSearching" }] : []));
556
+ this.isVisibleInList = computed(() => {
557
+ const currentPanel = this.currentMobilePanel();
558
+ const parentWrapper = this.linkWrapper['parentWrapper'];
559
+ // Mobile with search: show all matching links regardless of level
560
+ if (this.isSearching()) {
561
+ return this.isVisible();
562
+ }
563
+ // Mobile without search: use navigation levels
564
+ if (currentPanel === null) {
565
+ // Root level: show only first-level links
566
+ return this.isFirstLevel && this.isVisible();
567
+ }
568
+ else {
569
+ // Show links whose parent item wrapper matches the current panel
570
+ return parentWrapper === currentPanel && this.isVisible();
571
+ }
572
+ }, ...(ngDevMode ? [{ debugName: "isVisibleInList" }] : []));
573
+ // Build breadcrumb path for mobile search view
574
+ this.breadcrumbPath = computed(() => {
575
+ const path = [];
576
+ let currentItem = this.parentItem;
577
+ // Walk up the parent chain
578
+ while (currentItem?.label()) {
579
+ path.unshift(String(currentItem.label()));
580
+ const grandParent = currentItem?.['parentWrapper'];
581
+ currentItem = grandParent || null;
582
+ }
583
+ return path;
584
+ }, ...(ngDevMode ? [{ debugName: "breadcrumbPath" }] : []));
585
+ }
586
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkMobileComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
587
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavLinkMobileComponent, isStandalone: true, selector: "df-sidenav-link-mobile", host: { properties: { "class.d-none": "!isVisibleInList() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))" }, classAttribute: "d-flex" }, usesInheritance: true, ngImport: i0, template: `
588
+ <a
589
+ class="btn df-sidenav-button d-flex gap-3 justify-content-start w-100 df-sidenav-breadcrumb-button"
590
+ routerLinkActive="df-sidenav-active-level"
591
+ ariaCurrentWhenActive="page"
592
+ (isActiveChange)="isActiveLink.set($event)"
593
+ [routerLink]="linkPath()"
594
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
595
+ (click)="isSmallScreen() && sidenav.toggleMinimize()"
596
+ >
597
+ <ng-container [ngTemplateOutlet]="content" />
598
+ </a>
599
+ <ng-template #content>
600
+ <div class="d-flex gap-3 w-100">
601
+ <ng-content select="[dfSidenavIcon]" />
602
+ @if (icon()) {
603
+ <span class="{{ icon() }}"></span>
604
+ }
605
+ <div class="d-flex flex-column align-items-start">
606
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
607
+ @if (isSearching() && breadcrumbPath().length > 0) {
608
+ <small class="d-flex align-items-center gap-1" [class.text-muted]="!isActiveLink()">
609
+ @for (item of breadcrumbPath(); track $index) {
610
+ <span>{{ item }}</span>
611
+ @if (!$last) {
612
+ <span class="fa-arrow-right"></span>
613
+ }
614
+ }
615
+ </small>
616
+ }
617
+ </div>
618
+ </div>
619
+ </ng-template>
620
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
621
+ }
622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkMobileComponent, decorators: [{
623
+ type: Component,
624
+ args: [{
625
+ changeDetection: ChangeDetectionStrategy.OnPush,
626
+ selector: 'df-sidenav-link-mobile',
627
+ template: `
628
+ <a
629
+ class="btn df-sidenav-button d-flex gap-3 justify-content-start w-100 df-sidenav-breadcrumb-button"
630
+ routerLinkActive="df-sidenav-active-level"
631
+ ariaCurrentWhenActive="page"
632
+ (isActiveChange)="isActiveLink.set($event)"
633
+ [routerLink]="linkPath()"
634
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
635
+ (click)="isSmallScreen() && sidenav.toggleMinimize()"
636
+ >
637
+ <ng-container [ngTemplateOutlet]="content" />
638
+ </a>
639
+ <ng-template #content>
640
+ <div class="d-flex gap-3 w-100">
641
+ <ng-content select="[dfSidenavIcon]" />
642
+ @if (icon()) {
643
+ <span class="{{ icon() }}"></span>
644
+ }
645
+ <div class="d-flex flex-column align-items-start">
646
+ <span class="sidenav-label" [title]="label()">{{ label() }}</span>
647
+ @if (isSearching() && breadcrumbPath().length > 0) {
648
+ <small class="d-flex align-items-center gap-1" [class.text-muted]="!isActiveLink()">
649
+ @for (item of breadcrumbPath(); track $index) {
650
+ <span>{{ item }}</span>
651
+ @if (!$last) {
652
+ <span class="fa-arrow-right"></span>
653
+ }
654
+ }
655
+ </small>
656
+ }
657
+ </div>
658
+ </div>
659
+ </ng-template>
660
+ `,
661
+ host: {
662
+ '[class.d-none]': '!isVisibleInList() || (sidenav.isMinimized() && (!isFirstLevel || !wrapperHasIconContent()))',
663
+ class: 'd-flex'
664
+ },
665
+ imports: [RouterModule, NgTemplateOutlet]
666
+ }]
667
+ }] });
668
+
669
+ /**
670
+ * Utility directive for marking icon content in sidenav items and links.
671
+ * Needed for identification of the projected icon content.
672
+ * Use in case if there is no icon provided with font-awesome.
673
+ * @since 21.0.0
674
+ */
675
+ class DfSidenavIconDirective {
676
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
677
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.6", type: DfSidenavIconDirective, isStandalone: true, selector: "[dfSidenavIcon]", ngImport: i0 }); }
678
+ }
679
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavIconDirective, decorators: [{
680
+ type: Directive,
681
+ args: [{
682
+ selector: '[dfSidenavIcon]'
683
+ }]
684
+ }] });
685
+
686
+ /**
687
+ * Sidenav link that can be used inside <df-sidenav-item> or directly inside the <df-sidenav>
688
+ * @since 21.0.0
689
+ */
690
+ class DfSidenavLinkComponent {
691
+ constructor() {
692
+ /**
693
+ * The router link path for to the link
694
+ */
695
+ this.linkPath = input.required(...(ngDevMode ? [{ debugName: "linkPath" }] : []));
696
+ /**
697
+ * The label of the link
698
+ */
699
+ this.label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
700
+ /**
701
+ * The icon class name or inline content (e.g., font awesome class)
702
+ */
703
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
704
+ /**
705
+ * Options to determine when the router link is considered active
706
+ */
707
+ this.routerLinkActiveOptions = input({ exact: false }, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
708
+ /**
709
+ * The parent wrapper item component (if any)
710
+ */
711
+ this.parentWrapper = inject(DfSidenavItemComponent, {
712
+ optional: true,
713
+ skipSelf: true
714
+ });
715
+ /**
716
+ * Reference to the desktop link component instance
717
+ */
718
+ this.desktopInstance = viewChild(DfSidenavLinkDesktopComponent, ...(ngDevMode ? [{ debugName: "desktopInstance" }] : []));
719
+ /**
720
+ * Reference to the mobile link component instance
721
+ */
722
+ this.mobileInstance = viewChild(DfSidenavLinkMobileComponent, ...(ngDevMode ? [{ debugName: "mobileInstance" }] : []));
723
+ this.iconContentQuery = contentChild(DfSidenavIconDirective, ...(ngDevMode ? [{ debugName: "iconContentQuery" }] : []));
724
+ this.wrapperHasIconContent = computed(() => this.icon() !== '' || this.iconContentQuery() !== undefined, ...(ngDevMode ? [{ debugName: "wrapperHasIconContent" }] : []));
725
+ this.sidenav = inject(DfSidenavComponent);
726
+ }
727
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
728
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavLinkComponent, isStandalone: true, selector: "df-sidenav-link", inputs: { linkPath: { classPropertyName: "linkPath", publicName: "linkPath", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActiveOptions: { classPropertyName: "routerLinkActiveOptions", publicName: "routerLinkActiveOptions", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listitem" }, properties: { "class.mobile": "sidenav['isMobileDevice']" }, classAttribute: "d-block" }, queries: [{ propertyName: "iconContentQuery", first: true, predicate: DfSidenavIconDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "desktopInstance", first: true, predicate: DfSidenavLinkDesktopComponent, descendants: true, isSignal: true }, { propertyName: "mobileInstance", first: true, predicate: DfSidenavLinkMobileComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
729
+ <ng-template #iconSlot>
730
+ <ng-content select="[dfSidenavIcon]" />
731
+ </ng-template>
732
+ <ng-template #defaultContent>
733
+ <ng-content />
734
+ </ng-template>
735
+
736
+ @if (sidenav['isMobileDevice']) {
737
+ <df-sidenav-link-mobile
738
+ [linkPath]="linkPath()"
739
+ [label]="label()"
740
+ [icon]="icon()"
741
+ [wrapperHasIconContent]="wrapperHasIconContent()"
742
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
743
+ >
744
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
745
+ <ng-container [ngTemplateOutlet]="defaultContent" />
746
+ </df-sidenav-link-mobile>
747
+ } @else {
748
+ <df-sidenav-link-desktop
749
+ [linkPath]="linkPath()"
750
+ [label]="label()"
751
+ [icon]="icon()"
752
+ [wrapperHasIconContent]="wrapperHasIconContent()"
753
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
754
+ >
755
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
756
+ <ng-container [ngTemplateOutlet]="defaultContent" />
757
+ </df-sidenav-link-desktop>
758
+ }
759
+ `, isInline: true, dependencies: [{ kind: "component", type: DfSidenavLinkDesktopComponent, selector: "df-sidenav-link-desktop" }, { kind: "component", type: DfSidenavLinkMobileComponent, selector: "df-sidenav-link-mobile" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
760
+ }
761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavLinkComponent, decorators: [{
762
+ type: Component,
763
+ args: [{
764
+ selector: 'df-sidenav-link',
765
+ changeDetection: ChangeDetectionStrategy.OnPush,
766
+ template: `
767
+ <ng-template #iconSlot>
768
+ <ng-content select="[dfSidenavIcon]" />
769
+ </ng-template>
770
+ <ng-template #defaultContent>
771
+ <ng-content />
772
+ </ng-template>
773
+
774
+ @if (sidenav['isMobileDevice']) {
775
+ <df-sidenav-link-mobile
776
+ [linkPath]="linkPath()"
777
+ [label]="label()"
778
+ [icon]="icon()"
779
+ [wrapperHasIconContent]="wrapperHasIconContent()"
780
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
781
+ >
782
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
783
+ <ng-container [ngTemplateOutlet]="defaultContent" />
784
+ </df-sidenav-link-mobile>
785
+ } @else {
786
+ <df-sidenav-link-desktop
787
+ [linkPath]="linkPath()"
788
+ [label]="label()"
789
+ [icon]="icon()"
790
+ [wrapperHasIconContent]="wrapperHasIconContent()"
791
+ [routerLinkActiveOptions]="routerLinkActiveOptions()"
792
+ >
793
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
794
+ <ng-container [ngTemplateOutlet]="defaultContent" />
795
+ </df-sidenav-link-desktop>
796
+ }
797
+ `,
798
+ imports: [DfSidenavLinkDesktopComponent, DfSidenavLinkMobileComponent, NgTemplateOutlet],
799
+ host: {
800
+ role: 'listitem',
801
+ class: 'd-block',
802
+ '[class.mobile]': "sidenav['isMobileDevice']"
803
+ }
804
+ }]
805
+ }], propDecorators: { linkPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkPath", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], routerLinkActiveOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLinkActiveOptions", required: false }] }], desktopInstance: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DfSidenavLinkDesktopComponent), { isSignal: true }] }], mobileInstance: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DfSidenavLinkMobileComponent), { isSignal: true }] }], iconContentQuery: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DfSidenavIconDirective), { isSignal: true }] }] } });
806
+
807
+ /**
808
+ * Sidenav element that should be nested inside <df-sidenav> or <df-sidenav-item>
809
+ * @since 21.0.0
810
+ */
811
+ class DfSidenavItemComponent {
812
+ constructor() {
813
+ /**
814
+ * Label of the sidenav item
815
+ */
816
+ this.label = input.required(...(ngDevMode ? [{ debugName: "label" }] : []));
817
+ /**
818
+ * Flag whether the sidenav item is collapsed
819
+ * @default false
820
+ */
821
+ this.collapsed = model(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : []));
822
+ /**
823
+ * Optional icon class for the sidenav item
824
+ */
825
+ this.icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
826
+ /**
827
+ * Reference to the parent element containing this item
828
+ */
829
+ this.parentWrapper = inject(DfSidenavItemComponent, {
830
+ optional: true,
831
+ skipSelf: true
832
+ });
833
+ /**
834
+ * Child sidenav items nested inside this item
835
+ */
836
+ this.childrenItems = contentChildren(DfSidenavItemComponent, { ...(ngDevMode ? { debugName: "childrenItems" } : {}), descendants: true });
837
+ /**
838
+ * Child sidenav links nested inside this item
839
+ */
840
+ this.childrenLinks = contentChildren(DfSidenavLinkComponent, { ...(ngDevMode ? { debugName: "childrenLinks" } : {}), descendants: true });
841
+ /**
842
+ * Reference to the desktop instance of the sidenav item
843
+ */
844
+ this.desktopInstance = viewChild(DfSidenavItemDesktopComponent, ...(ngDevMode ? [{ debugName: "desktopInstance" }] : []));
845
+ /**
846
+ * Reference to the mobile instance of the sidenav item
847
+ */
848
+ this.mobileInstance = viewChild(DfSidenavItemMobileComponent, ...(ngDevMode ? [{ debugName: "mobileInstance" }] : []));
849
+ this.iconContentQuery = contentChild(DfSidenavIconDirective, ...(ngDevMode ? [{ debugName: "iconContentQuery" }] : []));
850
+ this.wrapperHasIconContent = computed(() => this.icon() !== '' || this.iconContentQuery() !== undefined, ...(ngDevMode ? [{ debugName: "wrapperHasIconContent" }] : []));
851
+ this.sidenav = inject(DfSidenavComponent);
852
+ }
853
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
854
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavItemComponent, isStandalone: true, selector: "df-sidenav-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { attributes: { "role": "listitem" }, properties: { "class.mobile": "sidenav['isMobileDevice']" }, classAttribute: "d-block" }, queries: [{ propertyName: "childrenItems", predicate: DfSidenavItemComponent, descendants: true, isSignal: true }, { propertyName: "childrenLinks", predicate: DfSidenavLinkComponent, descendants: true, isSignal: true }, { propertyName: "iconContentQuery", first: true, predicate: DfSidenavIconDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "desktopInstance", first: true, predicate: DfSidenavItemDesktopComponent, descendants: true, isSignal: true }, { propertyName: "mobileInstance", first: true, predicate: DfSidenavItemMobileComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
855
+ <ng-template #iconSlot>
856
+ <ng-content select="[dfSidenavIcon]" />
857
+ </ng-template>
858
+ <ng-template #content>
859
+ <ng-content />
860
+ </ng-template>
861
+
862
+ @if (sidenav['isMobileDevice']) {
863
+ <df-sidenav-item-mobile
864
+ [label]="label()"
865
+ [(collapsed)]="collapsed"
866
+ [icon]="icon()"
867
+ [wrapperHasIconContent]="wrapperHasIconContent()"
868
+ >
869
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
870
+ <ng-container [ngTemplateOutlet]="content" />
871
+ </df-sidenav-item-mobile>
872
+ } @else {
873
+ <df-sidenav-item-desktop
874
+ [label]="label()"
875
+ [(collapsed)]="collapsed"
876
+ [icon]="icon()"
877
+ [wrapperHasIconContent]="wrapperHasIconContent()"
878
+ >
879
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
880
+ <ng-container [ngTemplateOutlet]="content" />
881
+ </df-sidenav-item-desktop>
882
+ }
883
+ `, isInline: true, dependencies: [{ kind: "component", type: DfSidenavItemDesktopComponent, selector: "df-sidenav-item-desktop" }, { kind: "component", type: DfSidenavItemMobileComponent, selector: "df-sidenav-item-mobile" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
884
+ }
885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavItemComponent, decorators: [{
886
+ type: Component,
887
+ args: [{
888
+ selector: 'df-sidenav-item',
889
+ changeDetection: ChangeDetectionStrategy.OnPush,
890
+ template: `
891
+ <ng-template #iconSlot>
892
+ <ng-content select="[dfSidenavIcon]" />
893
+ </ng-template>
894
+ <ng-template #content>
895
+ <ng-content />
896
+ </ng-template>
897
+
898
+ @if (sidenav['isMobileDevice']) {
899
+ <df-sidenav-item-mobile
900
+ [label]="label()"
901
+ [(collapsed)]="collapsed"
902
+ [icon]="icon()"
903
+ [wrapperHasIconContent]="wrapperHasIconContent()"
904
+ >
905
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
906
+ <ng-container [ngTemplateOutlet]="content" />
907
+ </df-sidenav-item-mobile>
908
+ } @else {
909
+ <df-sidenav-item-desktop
910
+ [label]="label()"
911
+ [(collapsed)]="collapsed"
912
+ [icon]="icon()"
913
+ [wrapperHasIconContent]="wrapperHasIconContent()"
914
+ >
915
+ <ng-container [ngTemplateOutlet]="iconSlot" ngProjectAs="[dfSidenavIcon]" />
916
+ <ng-container [ngTemplateOutlet]="content" />
917
+ </df-sidenav-item-desktop>
918
+ }
919
+ `,
920
+ imports: [DfSidenavItemDesktopComponent, DfSidenavItemMobileComponent, NgTemplateOutlet],
921
+ host: {
922
+ role: 'listitem',
923
+ class: 'd-block',
924
+ '[class.mobile]': "sidenav['isMobileDevice']"
925
+ }
926
+ }]
927
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], childrenItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfSidenavItemComponent), { ...{ descendants: true }, isSignal: true }] }], childrenLinks: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfSidenavLinkComponent), { ...{ descendants: true }, isSignal: true }] }], desktopInstance: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DfSidenavItemDesktopComponent), { isSignal: true }] }], mobileInstance: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DfSidenavItemMobileComponent), { isSignal: true }] }], iconContentQuery: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DfSidenavIconDirective), { isSignal: true }] }] } });
928
+
929
+ /**
930
+ * Utility method to check if the user agent is a mobile device
931
+ *
932
+ * @param userAgent - The user agent string to check
933
+ * @returns `true` if the user agent corresponds to a mobile device, `false` otherwise
934
+ */
935
+ function isMobileUserAgent(userAgent) {
936
+ return /mobile|android|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
937
+ }
938
+ /**
939
+ * SideNav is a component to provide navigation feature with a panel on the side of your page
940
+ * @since 21.0.0
941
+ */
942
+ class DfSidenavComponent {
943
+ constructor() {
944
+ /**
945
+ * Flag whether the sidenav includes a search input
946
+ * @default false
947
+ */
948
+ this.searchable = input(false, { ...(ngDevMode ? { debugName: "searchable" } : {}), transform: booleanAttribute });
949
+ /**
950
+ * The container element for the sidenav (null for body)
951
+ * @default null
952
+ */
953
+ this.container = input(null, ...(ngDevMode ? [{ debugName: "container" }] : []));
954
+ /**
955
+ * Flag whether the sidenav should be resizable
956
+ * @default false
957
+ */
958
+ this.resizable = input(false, { ...(ngDevMode ? { debugName: "resizable" } : {}), transform: booleanAttribute });
959
+ /**
960
+ * Flag whether the sidenav should have the collapse button
961
+ * @default false
962
+ */
963
+ this.collapsible = input(false, { ...(ngDevMode ? { debugName: "collapsible" } : {}), transform: booleanAttribute });
964
+ /**
965
+ * Flag to enable mobile device detection for responsive behavior
966
+ * @default true
967
+ */
968
+ this.enableMobile = input(true, { ...(ngDevMode ? { debugName: "enableMobile" } : {}), transform: booleanAttribute });
969
+ this._isMinimized = linkedSignal(() => {
970
+ // in mobile devices, the default value is true (hiding the drawer) and does not track changes to the screen size
971
+ if (this.isMobileDevice) {
972
+ return true;
973
+ }
974
+ // in desktop, we track the large screen signal in order to reset the minimized state
975
+ // it allows resizing the window and have a more user-friendly behavior
976
+ return !this.isLargeUpScreen();
977
+ }, ...(ngDevMode ? [{ debugName: "_isMinimized" }] : []));
978
+ /**
979
+ * Flag to indicate if the sidenav is minimized
980
+ */
981
+ this.isMinimized = computed(() => this._isMinimized(), ...(ngDevMode ? [{ debugName: "isMinimized" }] : []));
982
+ this.hostRef = inject((ElementRef));
983
+ this.scrollableContainer = viewChild.required('scrollableContainer');
984
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
985
+ this.isMobileDevice = (() => {
986
+ if (this.isBrowser) {
987
+ return this.enableMobile() && isMobileUserAgent(navigator.userAgent);
988
+ }
989
+ else {
990
+ // SSR: try to get request if available
991
+ try {
992
+ const request = inject(Request, { optional: true });
993
+ if (request && 'headers' in request) {
994
+ const userAgent = request.headers.get('user-agent') || '';
995
+ return this.enableMobile() && isMobileUserAgent(userAgent);
996
+ }
997
+ }
998
+ catch {
999
+ // Request token not available or not in SSR context
1000
+ }
1001
+ return false;
1002
+ }
1003
+ })();
1004
+ this.searchInput = viewChild('textInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : []));
1005
+ // Query all links to find active ones
1006
+ this.allLinks = contentChildren(DfSidenavLinkComponent, { ...(ngDevMode ? { debugName: "allLinks" } : {}), descendants: true });
1007
+ this.allItems = contentChildren(DfSidenavItemComponent, { ...(ngDevMode ? { debugName: "allItems" } : {}), descendants: true });
1008
+ this.width = signal(null, ...(ngDevMode ? [{ debugName: "width" }] : []));
1009
+ this.savedWidth = null;
1010
+ this.searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
1011
+ // Mobile navigation stack - tracks which item's children are currently displayed
1012
+ // Using component references instead of labels for uniqueness
1013
+ this.mobileNavigationStack = signal([], ...(ngDevMode ? [{ debugName: "mobileNavigationStack" }] : []));
1014
+ this.currentMobilePanel = computed(() => {
1015
+ const stack = this.mobileNavigationStack();
1016
+ return stack.length > 0 ? stack[stack.length - 1] : null;
1017
+ }, ...(ngDevMode ? [{ debugName: "currentMobilePanel" }] : []));
1018
+ this.isSmallScreen = toSignal(inject(DfMedia).getObservable(['sm', 'xs']), { initialValue: false });
1019
+ this.isMediumScreen = toSignal(inject(DfMedia).getObservable(['md']), { initialValue: false });
1020
+ this.isLargeUpScreen = toSignal(inject(DfMedia).getObservable(['lg', 'xl', 'xxl', 'xxxl']), {
1021
+ initialValue: false
1022
+ });
1023
+ this.isMobileOrSmallScreen = computed(() => this.isSmallScreen() || this.isMediumScreen(), ...(ngDevMode ? [{ debugName: "isMobileOrSmallScreen" }] : []));
1024
+ this.backdrop = computed(() => this.isMediumScreen(), ...(ngDevMode ? [{ debugName: "backdrop" }] : []));
1025
+ this.navManager = createNavManager();
1026
+ this.navManagerConfig = {
1027
+ selector: (node) => node.querySelectorAll('input, button, a'),
1028
+ keys: {
1029
+ Home: this.navManager.focusFirst,
1030
+ End: this.navManager.focusLast
1031
+ }
1032
+ };
1033
+ this.injector = inject(Injector);
1034
+ this.contentHasIcons = computed(() => this.allItems().some((item) => item['wrapperHasIconContent']()) ||
1035
+ this.allLinks().some((link) => link['wrapperHasIconContent']()), ...(ngDevMode ? [{ debugName: "contentHasIcons" }] : []));
1036
+ useDirectiveForHost(this.navManager.directive, this.navManagerConfig);
1037
+ afterRenderEffect(() => {
1038
+ if (this.isBrowser) {
1039
+ if (this.isMobileDevice && !this._isMinimized()) {
1040
+ // Delay to ensure DOM and contentChildren queries are ready
1041
+ this.navigateToActiveItem();
1042
+ }
1043
+ // Keep track of the screen size changes to reset width
1044
+ if (this.isMediumScreen() || this.isSmallScreen() || this.isLargeUpScreen()) {
1045
+ this.width.set(null);
1046
+ }
1047
+ }
1048
+ });
1049
+ }
1050
+ ngAfterContentInit() {
1051
+ if (!this.isMobileDevice && this.isBrowser && this.isLargeUpScreen()) {
1052
+ void this.initializeDesktopNavigation();
1053
+ }
1054
+ }
1055
+ /**
1056
+ * This method allows to center and show the active link, when the sidenav is used in desktop and a large screen.
1057
+ * It collapses all items and expands only the parents of the active link.
1058
+ * Also, it scrolls to the active link to make it visible.
1059
+ */
1060
+ async initializeDesktopNavigation() {
1061
+ // RouterLinkActive queues two microtasks, so we wait for them to complete
1062
+ await Promise.resolve();
1063
+ await Promise.resolve();
1064
+ // Find the first active link
1065
+ const activeLink = this.allLinks()
1066
+ .find((link) => link['desktopInstance']()?.isActiveLink())?.['desktopInstance']?.();
1067
+ if (activeLink) {
1068
+ this.allItems().forEach((item) => {
1069
+ item.collapsed.set(true);
1070
+ });
1071
+ let currentParent = activeLink['parentItem'];
1072
+ while (currentParent) {
1073
+ currentParent.collapsed.set(false);
1074
+ currentParent = currentParent['parentWrapper'];
1075
+ }
1076
+ // We need a render to make sure the getBoundingClientRect calls will be correct AFTER the collapse of the items.
1077
+ afterNextRender({
1078
+ read: () => {
1079
+ const anchorElement = activeLink['anchor']().nativeElement;
1080
+ const containerElement = this.scrollableContainer().nativeElement;
1081
+ const heightPx = anchorElement.getBoundingClientRect().top +
1082
+ anchorElement.clientHeight / 2 -
1083
+ containerElement.getBoundingClientRect().top -
1084
+ containerElement.clientHeight / 3;
1085
+ containerElement.scrollTo({ top: heightPx, behavior: 'instant' });
1086
+ }
1087
+ }, { injector: this.injector });
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Toggles the minimized state of the sidenav
1092
+ *
1093
+ * @param minimized - Optional parameter to explicitly set the minimized state
1094
+ *
1095
+ */
1096
+ toggleMinimize(minimized = !this._isMinimized()) {
1097
+ if (minimized) {
1098
+ this.savedWidth = this.width();
1099
+ this.width.set(0);
1100
+ }
1101
+ else {
1102
+ this.width.set(this.savedWidth);
1103
+ }
1104
+ this._isMinimized.set(minimized);
1105
+ }
1106
+ /**
1107
+ * Handles visibility changes of the sidenav component to handle the component state accordingly.
1108
+ *
1109
+ * @param isVisible - Boolean indicating whether the sidenav is currently visible
1110
+ */
1111
+ visibleChangeHandler(isVisible) {
1112
+ if (!isVisible) {
1113
+ this.width.set(0);
1114
+ }
1115
+ this._isMinimized.set(!isVisible);
1116
+ }
1117
+ /**
1118
+ * Handles changes when the sidenav is resized, to handle the state accordingly.
1119
+ *
1120
+ * @param isResizing - Whether the sidenav is currently being resized
1121
+ */
1122
+ resizingChangeHandler(isResizing) {
1123
+ if (isResizing) {
1124
+ this._isMinimized.set(false);
1125
+ }
1126
+ else {
1127
+ if (this._isMinimized()) {
1128
+ this.width.set(0);
1129
+ }
1130
+ else {
1131
+ this.savedWidth = this.width();
1132
+ }
1133
+ }
1134
+ }
1135
+ // utility to transfer focus from the button when minimized to the search input when uncollapsed
1136
+ focusSearch() {
1137
+ this.toggleMinimize(false);
1138
+ setTimeout(() => {
1139
+ this.searchInput()?.nativeElement.focus();
1140
+ });
1141
+ }
1142
+ /**
1143
+ * Method to add the given item to the mobile navigation stack
1144
+ * @param itemRef item to be added to the navigation stack
1145
+ */
1146
+ navigateToChildren(itemRef) {
1147
+ const stack = this.mobileNavigationStack();
1148
+ this.mobileNavigationStack.set([...stack, itemRef]);
1149
+ }
1150
+ /**
1151
+ * Method to navigate back in the mobile navigation stack
1152
+ */
1153
+ navigateBack() {
1154
+ const stack = this.mobileNavigationStack();
1155
+ if (stack.length > 0) {
1156
+ this.mobileNavigationStack.set(stack.slice(0, -1));
1157
+ }
1158
+ }
1159
+ /**
1160
+ * Navigates to the active item in the sidenav
1161
+ * Expands parent items (desktop) and sets up mobile navigation stack
1162
+ */
1163
+ navigateToActiveItem() {
1164
+ if (!this.isBrowser) {
1165
+ return;
1166
+ }
1167
+ // Find the first active link
1168
+ const activeLink = this.allLinks()
1169
+ .find((link) => link['mobileInstance']()?.isActiveLink())?.['mobileInstance']();
1170
+ if (activeLink && activeLink['parentItem']) {
1171
+ // Build the path from root to the active link's parent (inclusive)
1172
+ const navigationPath = [];
1173
+ let current = activeLink['parentItem'];
1174
+ while (current) {
1175
+ navigationPath.unshift(current); // Add to beginning to build path from root
1176
+ current = current['parentWrapper'];
1177
+ }
1178
+ this.mobileNavigationStack.set(navigationPath);
1179
+ }
1180
+ else if (activeLink && !activeLink['parentItem']) {
1181
+ // Clear navigation stack to show root level
1182
+ this.mobileNavigationStack.set([]);
1183
+ }
1184
+ }
1185
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1186
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DfSidenavComponent, isStandalone: true, selector: "df-sidenav", inputs: { searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, enableMobile: { classPropertyName: "enableMobile", publicName: "enableMobile", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.df-sidenav-lg": "isLargeUpScreen()", "class.df-sidenav-withicon": "contentHasIcons()", "class.d-none": "isSmallScreen() && _isMinimized()", "class.df-sidenav-drawer": "isMediumScreen() && !_isMinimized()" }, classAttribute: "df-sidenav" }, queries: [{ propertyName: "allLinks", predicate: DfSidenavLinkComponent, descendants: true, isSignal: true }, { propertyName: "allItems", predicate: DfSidenavItemComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "scrollableContainer", first: true, predicate: ["scrollableContainer"], descendants: true, isSignal: true }, { propertyName: "searchInput", first: true, predicate: ["textInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
1187
+ <df-drawer
1188
+ #drawer
1189
+ className="inline-start {{ isSmallScreen() ? 'df-sidenav-mobile-drawer' : '' }}"
1190
+ [visible]="!_isMinimized()"
1191
+ (visibleChange)="visibleChangeHandler($event)"
1192
+ (minimizedChange)="_isMinimized.set($event)"
1193
+ (resizingChange)="resizingChangeHandler($event)"
1194
+ [bodyScroll]="true"
1195
+ [backdrop]="backdrop()"
1196
+ [(size)]="width"
1197
+ [container]="isSmallScreen() ? null : hostRef.nativeElement"
1198
+ [resizable]="resizable() && !isSmallScreen()"
1199
+ >
1200
+ <nav class="d-flex flex-column w-100 h-100 p-5 pb-3">
1201
+ <div class="df-sidenav-header flex-shrink-0" [class.df-sidenav-header-minimized]="_isMinimized()">
1202
+ @if (searchable()) {
1203
+ @if (_isMinimized()) {
1204
+ <button
1205
+ class="btn btn-outline-neutral df-btn-icononly mb-3 w-100 fa-search"
1206
+ [class.df-sidenav-search-minimized]="!contentHasIcons()"
1207
+ type="button"
1208
+ (click)="focusSearch()"
1209
+ i18n-aria-label="@@df.sidenav.search.ariaLabel"
1210
+ aria-label="Search"
1211
+ ></button>
1212
+ } @else {
1213
+ <div class="input-group mb-3">
1214
+ <div class="input-group-prepend">
1215
+ <span class="input-group-text fa-light fa-search"></span>
1216
+ </div>
1217
+ <input
1218
+ type="search"
1219
+ class="form-control df-input-withicon"
1220
+ i18n-placeholder="@@df.sidenav.search.placeholder"
1221
+ placeholder="Search"
1222
+ i18n-aria-label="@@df.sidenav.search.ariaLabel"
1223
+ aria-label="Search"
1224
+ [(ngModel)]="searchTerm"
1225
+ #textInput
1226
+ />
1227
+ <div class="input-group-append" [class.df-is-empty]="searchTerm() === ''">
1228
+ <button class="input-group-text fa-light fa-times" (click)="searchTerm.set(''); textInput.focus()">
1229
+ <span class="visually-hidden" i18n="@@df.sidenav.search.clearText">Clear text</span>
1230
+ </button>
1231
+ </div>
1232
+ </div>
1233
+ }
1234
+ }
1235
+ </div>
1236
+
1237
+ <div role="list" class="sidenav-body flex-grow-1 overflow-y-auto" #scrollableContainer>
1238
+ <ng-content />
1239
+ </div>
1240
+ @if (collapsible() && !isSmallScreen()) {
1241
+ <div class="d-flex sidenav-footer flex-shrink-0 py-3">
1242
+ <button
1243
+ class="btn btn-outline-primary df-btn-icononly df-sidenav-toggle-minimized"
1244
+ (click)="toggleMinimize()"
1245
+ i18n-aria-label="@@df.sidenav.toggleMinimize.ariaLabel"
1246
+ aria-label="Toggle minimized sidenav"
1247
+ >
1248
+ @if (isMinimized()) {
1249
+ <span class="fa-arrow-right-to-line"></span>
1250
+ } @else {
1251
+ <span class="fa-arrow-left-to-line"></span>
1252
+ }
1253
+ </button>
1254
+ </div>
1255
+ }
1256
+ </nav>
1257
+ </df-drawer>
1258
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DfDrawerComponent, selector: "df-drawer", inputs: ["className", "bodyScroll", "backdrop", "container", "size", "resizable", "visible", "enableMobile"], outputs: ["sizeChange", "visibleChange", "minimizedChange", "maximizedChange", "resizingChange", "minSize", "maxSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1259
+ }
1260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DfSidenavComponent, decorators: [{
1261
+ type: Component,
1262
+ args: [{
1263
+ changeDetection: ChangeDetectionStrategy.OnPush,
1264
+ selector: 'df-sidenav',
1265
+ template: `
1266
+ <df-drawer
1267
+ #drawer
1268
+ className="inline-start {{ isSmallScreen() ? 'df-sidenav-mobile-drawer' : '' }}"
1269
+ [visible]="!_isMinimized()"
1270
+ (visibleChange)="visibleChangeHandler($event)"
1271
+ (minimizedChange)="_isMinimized.set($event)"
1272
+ (resizingChange)="resizingChangeHandler($event)"
1273
+ [bodyScroll]="true"
1274
+ [backdrop]="backdrop()"
1275
+ [(size)]="width"
1276
+ [container]="isSmallScreen() ? null : hostRef.nativeElement"
1277
+ [resizable]="resizable() && !isSmallScreen()"
1278
+ >
1279
+ <nav class="d-flex flex-column w-100 h-100 p-5 pb-3">
1280
+ <div class="df-sidenav-header flex-shrink-0" [class.df-sidenav-header-minimized]="_isMinimized()">
1281
+ @if (searchable()) {
1282
+ @if (_isMinimized()) {
1283
+ <button
1284
+ class="btn btn-outline-neutral df-btn-icononly mb-3 w-100 fa-search"
1285
+ [class.df-sidenav-search-minimized]="!contentHasIcons()"
1286
+ type="button"
1287
+ (click)="focusSearch()"
1288
+ i18n-aria-label="@@df.sidenav.search.ariaLabel"
1289
+ aria-label="Search"
1290
+ ></button>
1291
+ } @else {
1292
+ <div class="input-group mb-3">
1293
+ <div class="input-group-prepend">
1294
+ <span class="input-group-text fa-light fa-search"></span>
1295
+ </div>
1296
+ <input
1297
+ type="search"
1298
+ class="form-control df-input-withicon"
1299
+ i18n-placeholder="@@df.sidenav.search.placeholder"
1300
+ placeholder="Search"
1301
+ i18n-aria-label="@@df.sidenav.search.ariaLabel"
1302
+ aria-label="Search"
1303
+ [(ngModel)]="searchTerm"
1304
+ #textInput
1305
+ />
1306
+ <div class="input-group-append" [class.df-is-empty]="searchTerm() === ''">
1307
+ <button class="input-group-text fa-light fa-times" (click)="searchTerm.set(''); textInput.focus()">
1308
+ <span class="visually-hidden" i18n="@@df.sidenav.search.clearText">Clear text</span>
1309
+ </button>
1310
+ </div>
1311
+ </div>
1312
+ }
1313
+ }
1314
+ </div>
1315
+
1316
+ <div role="list" class="sidenav-body flex-grow-1 overflow-y-auto" #scrollableContainer>
1317
+ <ng-content />
1318
+ </div>
1319
+ @if (collapsible() && !isSmallScreen()) {
1320
+ <div class="d-flex sidenav-footer flex-shrink-0 py-3">
1321
+ <button
1322
+ class="btn btn-outline-primary df-btn-icononly df-sidenav-toggle-minimized"
1323
+ (click)="toggleMinimize()"
1324
+ i18n-aria-label="@@df.sidenav.toggleMinimize.ariaLabel"
1325
+ aria-label="Toggle minimized sidenav"
1326
+ >
1327
+ @if (isMinimized()) {
1328
+ <span class="fa-arrow-right-to-line"></span>
1329
+ } @else {
1330
+ <span class="fa-arrow-left-to-line"></span>
1331
+ }
1332
+ </button>
1333
+ </div>
1334
+ }
1335
+ </nav>
1336
+ </df-drawer>
1337
+ `,
1338
+ imports: [FormsModule, DfDrawerComponent],
1339
+ host: {
1340
+ class: 'df-sidenav',
1341
+ '[class.df-sidenav-lg]': 'isLargeUpScreen()',
1342
+ '[class.df-sidenav-withicon]': 'contentHasIcons()',
1343
+ '[class.d-none]': 'isSmallScreen() && _isMinimized()',
1344
+ '[class.df-sidenav-drawer]': 'isMediumScreen() && !_isMinimized()'
1345
+ }
1346
+ }]
1347
+ }], ctorParameters: () => [], propDecorators: { searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], enableMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableMobile", required: false }] }], scrollableContainer: [{ type: i0.ViewChild, args: ['scrollableContainer', { isSignal: true }] }], searchInput: [{ type: i0.ViewChild, args: ['textInput', { isSignal: true }] }], allLinks: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfSidenavLinkComponent), { ...{ descendants: true }, isSignal: true }] }], allItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => DfSidenavItemComponent), { ...{ descendants: true }, isSignal: true }] }] } });
1348
+
1349
+ /**
1350
+ * Generated bundle index. Do not edit.
1351
+ */
1352
+
1353
+ export { DfSidenavComponent, DfSidenavIconDirective, DfSidenavItemComponent, DfSidenavLinkComponent };
1354
+ //# sourceMappingURL=design-factory-angular-sidenav.mjs.map