@softwarity/rail-nav 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -137,11 +137,93 @@ Navigation item with MD3 pill animation.
|
|
|
137
137
|
| `label` | `string` | - | Label text (below icon when collapsed, beside when expanded) |
|
|
138
138
|
| `badge` | `string \| number \| boolean` | - | Badge value. Use `true` for a small dot badge |
|
|
139
139
|
| `active` | `boolean` | `false` | Whether this item is active (for non-router usage) |
|
|
140
|
+
| `for` | `TemplateRef \| null` | `null` | Template projected as a contextual drawer when the item is hovered or clicked. Aliased to `for` for `mat-datepicker-toggle`-style ergonomics. See [Contextual drawer](#contextual-drawer). |
|
|
140
141
|
|
|
141
142
|
| Output | Type | Description |
|
|
142
143
|
|--------|------|-------------|
|
|
143
144
|
| `itemClick` | `void` | Emitted when clicked (automatically collapses rail) |
|
|
144
145
|
|
|
146
|
+
### RailnavSeparatorComponent
|
|
147
|
+
|
|
148
|
+
Visual separator between groups of `<rail-nav-item>`. Hairline 1px rule, vertically centered in a fixed-height host so the spacing stays consistent across collapsed / expanded modes.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<rail-nav>
|
|
152
|
+
<rail-nav-item label="Home">...</rail-nav-item>
|
|
153
|
+
<rail-nav-separator />
|
|
154
|
+
<rail-nav-item label="Trash">...</rail-nav-item>
|
|
155
|
+
</rail-nav>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Color overridable via `--rail-nav-separator-color`.
|
|
159
|
+
|
|
160
|
+
### RailnavSpacerComponent
|
|
161
|
+
|
|
162
|
+
Flexible spacer (`flex: 1 1 auto`). Place between two groups of items to push everything after it to the bottom of the rail — the classic pattern for primary nav on top and Settings / Profile anchored at the bottom.
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<rail-nav>
|
|
166
|
+
<rail-nav-item label="Home">...</rail-nav-item>
|
|
167
|
+
<rail-nav-item label="Inbox">...</rail-nav-item>
|
|
168
|
+
<rail-nav-spacer />
|
|
169
|
+
<rail-nav-item label="Settings">...</rail-nav-item>
|
|
170
|
+
</rail-nav>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Contextual drawer
|
|
174
|
+
|
|
175
|
+
Any rail item can declare a contextual side drawer with `[for]="someTemplate"`. The library renders the template in a CDK overlay positioned right next to the rail, handles hover-intent (200ms), close debounce (300ms for cursor transit), outside-tap dismiss, auto-close when the rail is expanded, and re-targets the same overlay when the cursor moves between trigger items.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import {
|
|
179
|
+
RailnavComponent,
|
|
180
|
+
RailnavContainerComponent,
|
|
181
|
+
RailnavContentComponent,
|
|
182
|
+
RailnavItemComponent,
|
|
183
|
+
} from '@softwarity/rail-nav';
|
|
184
|
+
|
|
185
|
+
@Component({
|
|
186
|
+
imports: [
|
|
187
|
+
RailnavComponent,
|
|
188
|
+
RailnavContainerComponent,
|
|
189
|
+
RailnavContentComponent,
|
|
190
|
+
RailnavItemComponent,
|
|
191
|
+
],
|
|
192
|
+
template: `
|
|
193
|
+
<rail-nav-container>
|
|
194
|
+
<rail-nav>
|
|
195
|
+
<rail-nav-item label="Workspaces" [for]="wsTpl">
|
|
196
|
+
<mat-icon>workspaces</mat-icon>
|
|
197
|
+
</rail-nav-item>
|
|
198
|
+
</rail-nav>
|
|
199
|
+
<rail-nav-content>...</rail-nav-content>
|
|
200
|
+
</rail-nav-container>
|
|
201
|
+
|
|
202
|
+
<ng-template #wsTpl>
|
|
203
|
+
<a routerLink="/ws/1">Workspace 1</a>
|
|
204
|
+
<a routerLink="/ws/2">Workspace 2</a>
|
|
205
|
+
</ng-template>
|
|
206
|
+
`,
|
|
207
|
+
})
|
|
208
|
+
export class AppComponent {}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The drawer panel applies its own chrome (background, shadow, rounded right corner) and a default nav-list layout (flex column, padding, gap, fixed width) — your template only needs the children. Both desktop hover and mobile tap open the drawer; the lib swallows the synthetic mouse events touch devices fire after a tap so the drawer doesn't close instantly.
|
|
212
|
+
|
|
213
|
+
### Drawer CSS custom properties
|
|
214
|
+
|
|
215
|
+
Override per rail (or globally) — all read with sane Material defaults as fallback.
|
|
216
|
+
|
|
217
|
+
| Variable | Default | Description |
|
|
218
|
+
|----------|---------|-------------|
|
|
219
|
+
| `--rail-nav-drawer-width` | `240px` | Fixed drawer width |
|
|
220
|
+
| `--rail-nav-drawer-padding` | `12px` | Inner padding |
|
|
221
|
+
| `--rail-nav-drawer-gap` | `4px` | Vertical gap between children |
|
|
222
|
+
| `--rail-nav-drawer-surface` | `var(--mat-sys-surface)` | Background color |
|
|
223
|
+
| `--rail-nav-drawer-on-surface` | `var(--mat-sys-on-surface)` | Text color |
|
|
224
|
+
| `--rail-nav-drawer-shadow` | `var(--mat-sys-level2)` | Elevation |
|
|
225
|
+
| `--rail-nav-drawer-radius` | `0 12px 12px 0` | Outer border-radius |
|
|
226
|
+
|
|
145
227
|
## SCSS Theming
|
|
146
228
|
|
|
147
229
|
Use the SCSS mixin to customize colors following the Angular Material pattern:
|
|
@@ -1,11 +1,197 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Directive, input, signal, contentChild, computed,
|
|
2
|
+
import { inject, ViewContainerRef, ElementRef, effect, DestroyRef, Injectable, Directive, input, signal, contentChild, computed, Component, ContentChild, output, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
import { MatSidenav, MatSidenavContainer, MatDrawerContainer, MatSidenavContent } from '@angular/material/sidenav';
|
|
4
4
|
import * as i1 from '@angular/material/core';
|
|
5
5
|
import { MatRippleModule } from '@angular/material/core';
|
|
6
6
|
import { NgTemplateOutlet } from '@angular/common';
|
|
7
|
+
import { Overlay } from '@angular/cdk/overlay';
|
|
8
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
7
9
|
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* One per `RailnavComponent` (provided on its `providers`). Owns the single
|
|
13
|
+
* CDK overlay used to render the currently-open `<rail-nav-drawer>`, and the
|
|
14
|
+
* close timer that lets the cursor travel between the trigger item and the
|
|
15
|
+
* drawer without the drawer closing on transit.
|
|
16
|
+
*
|
|
17
|
+
* - Anchors the overlay to the rail's host element (right edge), so the drawer
|
|
18
|
+
* sits next to the collapsed rail regardless of which trigger item opened it.
|
|
19
|
+
* - Auto-closes when the rail enters `expanded()` mode (mutual exclusion).
|
|
20
|
+
* - Mouse enter/leave on the overlay panel itself cancels/arms the close timer,
|
|
21
|
+
* so the drawer stays open while the cursor is over it.
|
|
22
|
+
*/
|
|
23
|
+
class RailnavDrawerOrchestrator {
|
|
24
|
+
/** Window after which we close if nothing cancels — covers cursor transit. */
|
|
25
|
+
static { this.LEAVE_DELAY_MS = 300; }
|
|
26
|
+
/** Post-open grace period during which `armClose()` is a no-op. Touch devices
|
|
27
|
+
* dispatch a burst of synthetic mouse events (with `pointerType === 'mouse'`)
|
|
28
|
+
* right after `click` — this window swallows them. */
|
|
29
|
+
static { this.SYNTHETIC_GUARD_MS = 500; }
|
|
30
|
+
constructor() {
|
|
31
|
+
this.overlay = inject(Overlay);
|
|
32
|
+
this.vcr = inject(ViewContainerRef);
|
|
33
|
+
this.rail = inject(RailnavComponent);
|
|
34
|
+
this.railEl = inject((ElementRef));
|
|
35
|
+
/** Timestamp of the last `open()` — used to gate close-on-leave so that
|
|
36
|
+
* synthetic `mouseleave` events dispatched right after a touch tap
|
|
37
|
+
* (with `pointerType === 'mouse'`) don't shut the drawer instantly. */
|
|
38
|
+
this.lastOpenAt = 0;
|
|
39
|
+
// Arrow-functions so the listener references stay stable across add/remove.
|
|
40
|
+
// Touch / pen pointers are ignored — those flows are click-driven.
|
|
41
|
+
this.onOverlayEnter = (event) => {
|
|
42
|
+
if (event.pointerType !== 'mouse')
|
|
43
|
+
return;
|
|
44
|
+
this.clearCloseTimer();
|
|
45
|
+
};
|
|
46
|
+
this.onOverlayLeave = (event) => {
|
|
47
|
+
if (event.pointerType !== 'mouse')
|
|
48
|
+
return;
|
|
49
|
+
this.armClose();
|
|
50
|
+
};
|
|
51
|
+
// Mutual exclusion with the rail's expanded mode: expanding closes any open
|
|
52
|
+
// drawer, and `open()` short-circuits while expanded. The expanded rail is
|
|
53
|
+
// itself the alternative nav surface, no point stacking a drawer on top.
|
|
54
|
+
effect(() => {
|
|
55
|
+
if (this.rail.expanded())
|
|
56
|
+
this.closeNow();
|
|
57
|
+
});
|
|
58
|
+
inject(DestroyRef).onDestroy(() => {
|
|
59
|
+
this.clearCloseTimer();
|
|
60
|
+
this.overlayRef?.dispose();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** Opens or re-targets the overlay to the given template. No-op while the
|
|
64
|
+
* rail is expanded. Cancels any pending close timer (e.g. user hovered back
|
|
65
|
+
* from a quick traverse). */
|
|
66
|
+
open(template) {
|
|
67
|
+
if (this.rail.expanded())
|
|
68
|
+
return;
|
|
69
|
+
this.clearCloseTimer();
|
|
70
|
+
this.lastOpenAt = performance.now();
|
|
71
|
+
if (this.currentTemplate === template && this.overlayRef?.hasAttached()) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.detach();
|
|
75
|
+
this.currentTemplate = template;
|
|
76
|
+
const positionStrategy = this.overlay
|
|
77
|
+
.position()
|
|
78
|
+
.flexibleConnectedTo(this.railEl)
|
|
79
|
+
.withPositions([
|
|
80
|
+
{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top' },
|
|
81
|
+
]);
|
|
82
|
+
this.overlayRef = this.overlay.create({
|
|
83
|
+
positionStrategy,
|
|
84
|
+
hasBackdrop: false,
|
|
85
|
+
panelClass: 'rail-nav-drawer-panel',
|
|
86
|
+
// Reposition on every scroll so the drawer stays glued to the rail
|
|
87
|
+
// when the page scrolls (default `noop` strategy leaves the overlay
|
|
88
|
+
// fixed to the viewport, which visually decouples it from the rail).
|
|
89
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
90
|
+
});
|
|
91
|
+
// Tap / click outside the overlay closes it — except when the click
|
|
92
|
+
// lands in the rail itself (re-clicking a `[for]` item should re-target
|
|
93
|
+
// or no-op, not close). Crucial on touch where hover-leave doesn't fire.
|
|
94
|
+
this.outsideClickSub = this.overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
95
|
+
const target = event.target;
|
|
96
|
+
if (target && this.railEl.nativeElement.contains(target))
|
|
97
|
+
return;
|
|
98
|
+
this.closeNow();
|
|
99
|
+
});
|
|
100
|
+
// Keep the drawer open while the cursor is over it; close shortly after
|
|
101
|
+
// it leaves (matching the trigger's own leave debounce). pointer* events
|
|
102
|
+
// let us ignore the synthetic mouseleave fired around touch taps.
|
|
103
|
+
this.overlayRef.overlayElement.addEventListener('pointerenter', this.onOverlayEnter);
|
|
104
|
+
this.overlayRef.overlayElement.addEventListener('pointerleave', this.onOverlayLeave);
|
|
105
|
+
// Inline-style the panel: the projected template content lives outside any
|
|
106
|
+
// component tree, so component-encapsulated styles can't reach it without
|
|
107
|
+
// ::ng-deep / Encapsulation.None. Setting styles directly on overlayElement
|
|
108
|
+
// sidesteps the issue entirely. Each value reads a CSS custom property so
|
|
109
|
+
// consumers can override via `--rail-nav-drawer-*` on the rail.
|
|
110
|
+
//
|
|
111
|
+
// The lib applies both the chrome (background / shadow / radius) AND the
|
|
112
|
+
// default nav-list layout (flex column / padding / gap / min-width) so the
|
|
113
|
+
// consumer's template can be just a flat list of children — they stack
|
|
114
|
+
// vertically with proper spacing out of the box.
|
|
115
|
+
const el = this.overlayRef.overlayElement;
|
|
116
|
+
el.style.background = 'var(--rail-nav-drawer-surface, var(--mat-sys-surface))';
|
|
117
|
+
el.style.color = 'var(--rail-nav-drawer-on-surface, var(--mat-sys-on-surface))';
|
|
118
|
+
el.style.boxShadow = 'var(--rail-nav-drawer-shadow, var(--mat-sys-level2))';
|
|
119
|
+
el.style.borderRadius = 'var(--rail-nav-drawer-radius, 0 12px 12px 0)';
|
|
120
|
+
el.style.overflow = 'auto';
|
|
121
|
+
el.style.boxSizing = 'border-box';
|
|
122
|
+
el.style.display = 'flex';
|
|
123
|
+
el.style.flexDirection = 'column';
|
|
124
|
+
el.style.padding = 'var(--rail-nav-drawer-padding, 12px)';
|
|
125
|
+
el.style.gap = 'var(--rail-nav-drawer-gap, 4px)';
|
|
126
|
+
// Fixed width so the drawer doesn't shrink-fit its content (which would
|
|
127
|
+
// make each trigger's drawer a different size) and absorbs the layout
|
|
128
|
+
// variance between MatButton variants (e.g. text vs tonal padding).
|
|
129
|
+
el.style.width = 'var(--rail-nav-drawer-width, 240px)';
|
|
130
|
+
// Subtle fade-in on every (re-)open: applied via Web Animations API so we
|
|
131
|
+
// don't ship CSS keyframes globally. Re-targeting the overlay to another
|
|
132
|
+
// template re-runs this animation, giving a soft cross-fade feel.
|
|
133
|
+
el.animate([
|
|
134
|
+
{ opacity: 0, transform: 'translateX(-4px)' },
|
|
135
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
136
|
+
], { duration: 180, easing: 'ease-out' });
|
|
137
|
+
this.overlayRef.attach(new TemplatePortal(template, this.vcr));
|
|
138
|
+
// Match the rail's full height so the drawer sits flush against it
|
|
139
|
+
// regardless of viewport / layout. ResizeObserver keeps it in sync.
|
|
140
|
+
this.applyHeight();
|
|
141
|
+
this.resizeObserver?.disconnect();
|
|
142
|
+
this.resizeObserver = new ResizeObserver(() => this.applyHeight());
|
|
143
|
+
this.resizeObserver.observe(this.railEl.nativeElement);
|
|
144
|
+
}
|
|
145
|
+
applyHeight() {
|
|
146
|
+
if (!this.overlayRef)
|
|
147
|
+
return;
|
|
148
|
+
const rect = this.railEl.nativeElement.getBoundingClientRect();
|
|
149
|
+
this.overlayRef.overlayElement.style.height = `${rect.height}px`;
|
|
150
|
+
}
|
|
151
|
+
/** Arms the close timer. Cancelled if the cursor returns to the trigger or
|
|
152
|
+
* enters the overlay before it fires. Suppressed during the post-open guard
|
|
153
|
+
* window so synthetic mouseleave events fired right after a touch tap
|
|
154
|
+
* don't shut the drawer instantly. */
|
|
155
|
+
armClose() {
|
|
156
|
+
if (performance.now() - this.lastOpenAt < RailnavDrawerOrchestrator.SYNTHETIC_GUARD_MS) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.clearCloseTimer();
|
|
160
|
+
this.closeTimer = setTimeout(() => this.closeNow(), RailnavDrawerOrchestrator.LEAVE_DELAY_MS);
|
|
161
|
+
}
|
|
162
|
+
/** Force-close immediately, e.g. on a click inside the drawer that navigates. */
|
|
163
|
+
closeNow() {
|
|
164
|
+
this.clearCloseTimer();
|
|
165
|
+
this.detach();
|
|
166
|
+
}
|
|
167
|
+
detach() {
|
|
168
|
+
this.resizeObserver?.disconnect();
|
|
169
|
+
this.resizeObserver = undefined;
|
|
170
|
+
this.outsideClickSub?.unsubscribe();
|
|
171
|
+
this.outsideClickSub = undefined;
|
|
172
|
+
if (!this.overlayRef)
|
|
173
|
+
return;
|
|
174
|
+
this.overlayRef.overlayElement.removeEventListener('pointerenter', this.onOverlayEnter);
|
|
175
|
+
this.overlayRef.overlayElement.removeEventListener('pointerleave', this.onOverlayLeave);
|
|
176
|
+
// dispose (not just detach) so the next `open()` builds a fresh overlay
|
|
177
|
+
// with its own position strategy + listeners — no stale state to leak.
|
|
178
|
+
this.overlayRef.dispose();
|
|
179
|
+
this.overlayRef = undefined;
|
|
180
|
+
this.currentTemplate = undefined;
|
|
181
|
+
}
|
|
182
|
+
clearCloseTimer() {
|
|
183
|
+
if (this.closeTimer !== undefined) {
|
|
184
|
+
clearTimeout(this.closeTimer);
|
|
185
|
+
this.closeTimer = undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavDrawerOrchestrator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
189
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavDrawerOrchestrator }); }
|
|
190
|
+
}
|
|
191
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavDrawerOrchestrator, decorators: [{
|
|
192
|
+
type: Injectable
|
|
193
|
+
}], ctorParameters: () => [] });
|
|
194
|
+
|
|
9
195
|
/** Directive to mark custom branding content */
|
|
10
196
|
class RailnavBrandingDirective {
|
|
11
197
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavBrandingDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
@@ -58,7 +244,7 @@ class RailnavComponent extends MatSidenav {
|
|
|
58
244
|
this.expanded.set(true);
|
|
59
245
|
}
|
|
60
246
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
61
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavComponent, isStandalone: true, selector: "rail-nav", inputs: { railPosition: { classPropertyName: "railPosition", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, hideDefaultHeader: { classPropertyName: "hideDefaultHeader", publicName: "hideDefaultHeader", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, autoCollapse: { classPropertyName: "autoCollapse", publicName: "autoCollapse", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.expanded": "expanded()", "class.position-end": "railPosition() === \"end\"" }, classAttribute: "mat-drawer mat-sidenav" }, queries: [{ propertyName: "customBranding", first: true, predicate: RailnavBrandingDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
247
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavComponent, isStandalone: true, selector: "rail-nav", inputs: { railPosition: { classPropertyName: "railPosition", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, hideDefaultHeader: { classPropertyName: "hideDefaultHeader", publicName: "hideDefaultHeader", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, autoCollapse: { classPropertyName: "autoCollapse", publicName: "autoCollapse", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.expanded": "expanded()", "class.position-end": "railPosition() === \"end\"" }, classAttribute: "mat-drawer mat-sidenav" }, providers: [RailnavDrawerOrchestrator], queries: [{ propertyName: "customBranding", first: true, predicate: RailnavBrandingDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
|
|
62
248
|
<!-- Default branding template (title/subtitle) -->
|
|
63
249
|
<ng-template #defaultBrandingTpl>
|
|
64
250
|
<div class="rail-branding" [class.position-end]="railPosition() === 'end'">
|
|
@@ -97,7 +283,7 @@ class RailnavComponent extends MatSidenav {
|
|
|
97
283
|
<nav class="rail-items">
|
|
98
284
|
<ng-content />
|
|
99
285
|
</nav>
|
|
100
|
-
`, isInline: true, styles: [":host{display:
|
|
286
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;position:absolute;top:0;bottom:0;left:0;z-index:100;width:var(--rail-nav-collapsed-width, 72px);border:none!important;outline:none!important;box-shadow:none;background:var(--rail-nav-surface-color, var(--mat-sys-surface));transition:width .2s ease;overflow:visible;--rail-nav-separator-shift: 12px}:host(.expanded){width:var(--rail-nav-expanded-width, fit-content);box-shadow:4px 0 8px #0003;--rail-nav-separator-shift: 0}:host(.position-end){left:auto;right:0}:host(.position-end.expanded){box-shadow:-4px 0 8px #0003}.rail-header{display:flex;align-items:center;gap:12px;height:var(--rail-nav-header-height, 64px);padding:16px 16px 16px 24px;box-sizing:border-box;cursor:pointer;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-header.position-end{padding:16px 24px 16px 16px}.rail-header.position-end .rail-burger{margin-left:auto}.rail-header:hover{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-items{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:12px;gap:0;box-sizing:border-box;width:100%}.rail-burger{position:relative;width:24px;height:24px;flex-shrink:0}.rail-burger.position-end{transform:scaleX(-1)}.rail-burger svg{position:absolute;top:0;left:0;transition:opacity .3s ease,transform .3s ease}.rail-burger .icon-menu{opacity:1;transform:rotate(0)}.rail-burger .icon-menu-open{opacity:0;transform:rotate(-90deg)}.rail-burger.expanded .icon-menu{opacity:0;transform:rotate(90deg)}.rail-burger.expanded .icon-menu-open{opacity:1;transform:rotate(0)}.rail-burger.position-end .icon-menu{transform:rotate(0)}.rail-burger.position-end .icon-menu-open{transform:rotate(90deg)}.rail-burger.position-end.expanded .icon-menu{transform:rotate(-90deg)}.rail-burger.position-end.expanded .icon-menu-open{transform:rotate(0)}.rail-branding{display:flex;flex-direction:column;justify-content:center;min-width:0;overflow:hidden;text-align:right;padding-top:5px}.rail-branding.position-end{text-align:left}.rail-title{font-size:16px;font-weight:500;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-subtitle{font-size:11px;line-height:1;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
|
|
101
287
|
}
|
|
102
288
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavComponent, decorators: [{
|
|
103
289
|
type: Component,
|
|
@@ -144,7 +330,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
144
330
|
'class': 'mat-drawer mat-sidenav',
|
|
145
331
|
'[class.expanded]': 'expanded()',
|
|
146
332
|
'[class.position-end]': 'railPosition() === "end"'
|
|
147
|
-
}, styles: [":host{display:
|
|
333
|
+
}, providers: [RailnavDrawerOrchestrator], styles: [":host{display:flex;flex-direction:column;position:absolute;top:0;bottom:0;left:0;z-index:100;width:var(--rail-nav-collapsed-width, 72px);border:none!important;outline:none!important;box-shadow:none;background:var(--rail-nav-surface-color, var(--mat-sys-surface));transition:width .2s ease;overflow:visible;--rail-nav-separator-shift: 12px}:host(.expanded){width:var(--rail-nav-expanded-width, fit-content);box-shadow:4px 0 8px #0003;--rail-nav-separator-shift: 0}:host(.position-end){left:auto;right:0}:host(.position-end.expanded){box-shadow:-4px 0 8px #0003}.rail-header{display:flex;align-items:center;gap:12px;height:var(--rail-nav-header-height, 64px);padding:16px 16px 16px 24px;box-sizing:border-box;cursor:pointer;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-header.position-end{padding:16px 24px 16px 16px}.rail-header.position-end .rail-burger{margin-left:auto}.rail-header:hover{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-items{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:12px;gap:0;box-sizing:border-box;width:100%}.rail-burger{position:relative;width:24px;height:24px;flex-shrink:0}.rail-burger.position-end{transform:scaleX(-1)}.rail-burger svg{position:absolute;top:0;left:0;transition:opacity .3s ease,transform .3s ease}.rail-burger .icon-menu{opacity:1;transform:rotate(0)}.rail-burger .icon-menu-open{opacity:0;transform:rotate(-90deg)}.rail-burger.expanded .icon-menu{opacity:0;transform:rotate(90deg)}.rail-burger.expanded .icon-menu-open{opacity:1;transform:rotate(0)}.rail-burger.position-end .icon-menu{transform:rotate(0)}.rail-burger.position-end .icon-menu-open{transform:rotate(90deg)}.rail-burger.position-end.expanded .icon-menu{transform:rotate(-90deg)}.rail-burger.position-end.expanded .icon-menu-open{transform:rotate(0)}.rail-branding{display:flex;flex-direction:column;justify-content:center;min-width:0;overflow:hidden;text-align:right;padding-top:5px}.rail-branding.position-end{text-align:left}.rail-title{font-size:16px;font-weight:500;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--rail-nav-on-surface, var(--mat-sys-on-surface))}.rail-subtitle{font-size:11px;line-height:1;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.7}\n"] }]
|
|
148
334
|
}], ctorParameters: () => [], propDecorators: { railPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], hideDefaultHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideDefaultHeader", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], autoCollapse: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCollapse", required: false }] }], customBranding: [{ type: i0.ContentChild, args: [i0.forwardRef(() => RailnavBrandingDirective), { isSignal: true }] }] } });
|
|
149
335
|
|
|
150
336
|
class RailnavContainerComponent extends MatSidenavContainer {
|
|
@@ -217,6 +403,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
217
403
|
}], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
|
|
218
404
|
|
|
219
405
|
class RailnavItemComponent {
|
|
406
|
+
/** Hover-intent delay (ms) before opening the drawer. Cancelled if the
|
|
407
|
+
* cursor leaves the item first. Click bypasses the delay. */
|
|
408
|
+
static { this.ENTER_DELAY_MS = 200; }
|
|
220
409
|
constructor() {
|
|
221
410
|
/** Router link for navigation */
|
|
222
411
|
this.routerLink = input(...(ngDevMode ? [undefined, { debugName: "routerLink" }] : []));
|
|
@@ -226,6 +415,17 @@ class RailnavItemComponent {
|
|
|
226
415
|
this.badge = input(...(ngDevMode ? [undefined, { debugName: "badge" }] : []));
|
|
227
416
|
/** Whether this item is active (for non-router usage) */
|
|
228
417
|
this.active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
418
|
+
/** Template projected as a contextual side panel when the item is hovered
|
|
419
|
+
* or clicked. Aliased `for` to mirror Material's `mat-datepicker-toggle`
|
|
420
|
+
* (`<rail-nav-item [for]="myTpl">`). Declare the content as a plain
|
|
421
|
+
* `<ng-template #myTpl>...</ng-template>` anywhere in the host template.
|
|
422
|
+
*
|
|
423
|
+
* When set, the item becomes a trigger; the rail's orchestrator handles
|
|
424
|
+
* overlay rendering, animation, hover-intent and close timer. The panel
|
|
425
|
+
* carries the `rail-nav-drawer-panel` class — style it via the theme mixin
|
|
426
|
+
* `rail-nav.panel()` (or your own global rule). Suppressed while the rail
|
|
427
|
+
* is `expanded()`. */
|
|
428
|
+
this.for = input(null, { ...(ngDevMode ? { debugName: "for" } : {}), alias: 'for' });
|
|
229
429
|
/** Whether to show a badge */
|
|
230
430
|
this.hasBadge = computed(() => {
|
|
231
431
|
const b = this.badge();
|
|
@@ -240,17 +440,25 @@ class RailnavItemComponent {
|
|
|
240
440
|
this.itemClick = output();
|
|
241
441
|
/** Reference to parent rail-nav */
|
|
242
442
|
this.railnav = inject(RailnavComponent);
|
|
443
|
+
/** Owned by the parent rail (one orchestrator per rail). null in tests where
|
|
444
|
+
* the item is rendered outside a real rail. */
|
|
445
|
+
this.orchestrator = inject(RailnavDrawerOrchestrator, { optional: true });
|
|
243
446
|
/** Whether the rail is expanded */
|
|
244
447
|
this.expanded = computed(() => this.railnav.expanded(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
245
448
|
/** Position of the rail (start or end) */
|
|
246
449
|
this.position = computed(() => this.railnav.railPosition(), ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
450
|
+
inject(DestroyRef).onDestroy(() => this.clearEnterTimeout());
|
|
247
451
|
}
|
|
248
|
-
/** Handle item click - emit event
|
|
452
|
+
/** Handle item click - emit event, optionally collapse rail, then open drawer.
|
|
453
|
+
* Collapse must come BEFORE openDrawer: while the rail is expanded, the
|
|
454
|
+
* orchestrator suppresses opens (mutual exclusion). Collapsing first ensures
|
|
455
|
+
* the click on an expanded rail still opens the drawer. */
|
|
249
456
|
onItemClick() {
|
|
250
457
|
this.itemClick.emit();
|
|
251
458
|
if (this.railnav.autoCollapse()) {
|
|
252
459
|
this.railnav.collapse();
|
|
253
460
|
}
|
|
461
|
+
this.openDrawerIfAny();
|
|
254
462
|
}
|
|
255
463
|
/** Handle router link click - optionally collapse rail */
|
|
256
464
|
onRouterLinkClick() {
|
|
@@ -258,8 +466,44 @@ class RailnavItemComponent {
|
|
|
258
466
|
this.railnav.collapse();
|
|
259
467
|
}
|
|
260
468
|
}
|
|
469
|
+
/** Hover-intent: when the item carries a `for` drawer, open it after a
|
|
470
|
+
* short delay. The delay is cancelled if the cursor leaves before it fires.
|
|
471
|
+
* Ignored on non-mouse pointers (touch/pen) — the click path handles them. */
|
|
472
|
+
onHostPointerEnter(event) {
|
|
473
|
+
if (event.pointerType !== 'mouse')
|
|
474
|
+
return;
|
|
475
|
+
if (!this.for() || !this.orchestrator)
|
|
476
|
+
return;
|
|
477
|
+
if (this.railnav.expanded())
|
|
478
|
+
return;
|
|
479
|
+
this.clearEnterTimeout();
|
|
480
|
+
this.enterTimeout = setTimeout(() => this.openDrawerIfAny(), RailnavItemComponent.ENTER_DELAY_MS);
|
|
481
|
+
}
|
|
482
|
+
/** Arm the orchestrator's close timer when leaving the trigger item. If
|
|
483
|
+
* the cursor reaches the overlay before it fires, the overlay's own
|
|
484
|
+
* `pointerenter` cancels it. Ignored on touch/pen for the same reason. */
|
|
485
|
+
onHostPointerLeave(event) {
|
|
486
|
+
if (event.pointerType !== 'mouse')
|
|
487
|
+
return;
|
|
488
|
+
this.clearEnterTimeout();
|
|
489
|
+
if (this.for())
|
|
490
|
+
this.orchestrator?.armClose();
|
|
491
|
+
}
|
|
492
|
+
openDrawerIfAny() {
|
|
493
|
+
const drawer = this.for();
|
|
494
|
+
if (!drawer || !this.orchestrator)
|
|
495
|
+
return;
|
|
496
|
+
this.clearEnterTimeout();
|
|
497
|
+
this.orchestrator.open(drawer);
|
|
498
|
+
}
|
|
499
|
+
clearEnterTimeout() {
|
|
500
|
+
if (this.enterTimeout !== undefined) {
|
|
501
|
+
clearTimeout(this.enterTimeout);
|
|
502
|
+
this.enterTimeout = undefined;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
261
505
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
262
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavItemComponent, isStandalone: true, selector: "rail-nav-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "class.expanded": "expanded()", "class.position-end": "position() === \"end\"" } }, ngImport: i0, template: `
|
|
506
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: RailnavItemComponent, isStandalone: true, selector: "rail-nav-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, for: { classPropertyName: "for", publicName: "for", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { listeners: { "pointerenter": "onHostPointerEnter($event)", "pointerleave": "onHostPointerLeave($event)" }, properties: { "class.expanded": "expanded()", "class.position-end": "position() === \"end\"" } }, ngImport: i0, template: `
|
|
263
507
|
<ng-template #iconTpl>
|
|
264
508
|
<div class="rail-item-pill">
|
|
265
509
|
<div class="rail-item-ripple" matRipple></div>
|
|
@@ -297,7 +541,7 @@ class RailnavItemComponent {
|
|
|
297
541
|
<ng-container [ngTemplateOutlet]="iconTpl" />
|
|
298
542
|
</button>
|
|
299
543
|
}
|
|
300
|
-
`, isInline: true, styles: [":host{display:block}.rail-item{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;text-decoration:none;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));cursor:pointer;padding:0;gap:4px;background:none;border:none;font:inherit;min-height:56px;box-sizing:border-box;outline:none;width:100
|
|
544
|
+
`, isInline: true, styles: [":host{display:block}.rail-item{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;text-decoration:none;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));cursor:pointer;padding:0;gap:4px;background:none;border:none;font:inherit;min-height:56px;box-sizing:border-box;outline:none;width:100%;transition:gap .2s ease}.rail-item.position-end{align-items:flex-end}.rail-item:focus-visible .rail-item-pill{border-color:var(--rail-nav-primary, var(--mat-sys-primary))}.rail-item.position-end .rail-item-pill{justify-content:flex-end;padding-left:0;padding-right:10px}.rail-item.expanded{gap:0}.rail-item-pill{position:relative;display:flex;align-items:center;justify-content:flex-start;width:48px;height:32px;border-radius:9999px;border:2px solid transparent;overflow:visible;margin-top:12px;padding-left:10px;box-sizing:border-box;transition:background .2s ease,width .2s ease,height .2s ease,margin .2s ease}.rail-item-ripple{position:absolute;inset:0;border-radius:inherit;overflow:hidden}.rail-item:hover .rail-item-pill{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-item.active .rail-item-pill{background:var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));color:var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container))}.rail-item.expanded .rail-item-pill{width:auto;height:48px;padding:0 16px 0 10px;gap:12px;margin-top:0}.rail-item.expanded.position-end .rail-item-pill{flex-direction:row-reverse;justify-content:flex-start;padding:0 10px 0 16px}:host:first-child .rail-item-pill{margin-top:8px}:host:first-child .rail-item.expanded .rail-item-pill{margin-top:0}:host:last-child .rail-item.expanded .rail-item-pill{margin-bottom:12px}.rail-item-icon-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:visible}.rail-item-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px}.rail-item-icon ::ng-deep>*{font-size:24px;width:24px;height:24px}.rail-badge{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background:var(--rail-nav-error, var(--mat-sys-error));color:var(--rail-nav-on-error, var(--mat-sys-on-error));font-size:11px;font-weight:500;line-height:16px;text-align:center;box-sizing:border-box;z-index:10}.rail-badge.dot{top:-2px;right:-2px;min-width:6px;width:6px;height:6px;padding:0;border-radius:3px}.rail-item-label.label-below{font-size:12px;font-weight:500;line-height:16px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:48px;max-height:16px;opacity:1;transition:opacity .1s ease,max-height .2s ease}.rail-item.expanded .rail-item-label.label-below{opacity:0;max-height:0;pointer-events:none}.rail-item-label.label-inline{font-size:14px;font-weight:500;line-height:24px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:0;opacity:0;pointer-events:none}.rail-item.expanded .rail-item-label.label-inline{width:auto;flex:1;opacity:1;pointer-events:auto;transition:opacity .15s ease .1s}.rail-item.expanded.position-end .rail-item-label.label-inline{text-align:right}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
301
545
|
}
|
|
302
546
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavItemComponent, decorators: [{
|
|
303
547
|
type: Component,
|
|
@@ -341,9 +585,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
341
585
|
}
|
|
342
586
|
`, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
343
587
|
'[class.expanded]': 'expanded()',
|
|
344
|
-
'[class.position-end]': 'position() === "end"'
|
|
345
|
-
|
|
346
|
-
|
|
588
|
+
'[class.position-end]': 'position() === "end"',
|
|
589
|
+
// pointer* events let us filter by pointerType: touch devices emit
|
|
590
|
+
// synthetic mouseenter/leave around taps, which would arm the close timer
|
|
591
|
+
// right after a click-to-open and shut the drawer instantly. Hover-intent
|
|
592
|
+
// only applies for real mouse pointers.
|
|
593
|
+
'(pointerenter)': 'onHostPointerEnter($event)',
|
|
594
|
+
'(pointerleave)': 'onHostPointerLeave($event)'
|
|
595
|
+
}, styles: [":host{display:block}.rail-item{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;text-decoration:none;color:var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));cursor:pointer;padding:0;gap:4px;background:none;border:none;font:inherit;min-height:56px;box-sizing:border-box;outline:none;width:100%;transition:gap .2s ease}.rail-item.position-end{align-items:flex-end}.rail-item:focus-visible .rail-item-pill{border-color:var(--rail-nav-primary, var(--mat-sys-primary))}.rail-item.position-end .rail-item-pill{justify-content:flex-end;padding-left:0;padding-right:10px}.rail-item.expanded{gap:0}.rail-item-pill{position:relative;display:flex;align-items:center;justify-content:flex-start;width:48px;height:32px;border-radius:9999px;border:2px solid transparent;overflow:visible;margin-top:12px;padding-left:10px;box-sizing:border-box;transition:background .2s ease,width .2s ease,height .2s ease,margin .2s ease}.rail-item-ripple{position:absolute;inset:0;border-radius:inherit;overflow:hidden}.rail-item:hover .rail-item-pill{background:var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high))}.rail-item.active .rail-item-pill{background:var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));color:var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container))}.rail-item.expanded .rail-item-pill{width:auto;height:48px;padding:0 16px 0 10px;gap:12px;margin-top:0}.rail-item.expanded.position-end .rail-item-pill{flex-direction:row-reverse;justify-content:flex-start;padding:0 10px 0 16px}:host:first-child .rail-item-pill{margin-top:8px}:host:first-child .rail-item.expanded .rail-item-pill{margin-top:0}:host:last-child .rail-item.expanded .rail-item-pill{margin-bottom:12px}.rail-item-icon-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:visible}.rail-item-icon{display:flex;align-items:center;justify-content:center;width:24px;height:24px}.rail-item-icon ::ng-deep>*{font-size:24px;width:24px;height:24px}.rail-badge{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background:var(--rail-nav-error, var(--mat-sys-error));color:var(--rail-nav-on-error, var(--mat-sys-on-error));font-size:11px;font-weight:500;line-height:16px;text-align:center;box-sizing:border-box;z-index:10}.rail-badge.dot{top:-2px;right:-2px;min-width:6px;width:6px;height:6px;padding:0;border-radius:3px}.rail-item-label.label-below{font-size:12px;font-weight:500;line-height:16px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:48px;max-height:16px;opacity:1;transition:opacity .1s ease,max-height .2s ease}.rail-item.expanded .rail-item-label.label-below{opacity:0;max-height:0;pointer-events:none}.rail-item-label.label-inline{font-size:14px;font-weight:500;line-height:24px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:0;opacity:0;pointer-events:none}.rail-item.expanded .rail-item-label.label-inline{width:auto;flex:1;opacity:1;pointer-events:auto;transition:opacity .15s ease .1s}.rail-item.expanded.position-end .rail-item-label.label-inline{text-align:right}\n"] }]
|
|
596
|
+
}], ctorParameters: () => [], propDecorators: { routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], for: [{ type: i0.Input, args: [{ isSignal: true, alias: "for", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Visual separator between groups of `<rail-nav-item>`. Renders a thin
|
|
600
|
+
* horizontal rule with sensible margins so groups of items breathe.
|
|
601
|
+
*
|
|
602
|
+
* <rail-nav>
|
|
603
|
+
* <rail-nav-item label="Home">...</rail-nav-item>
|
|
604
|
+
* <rail-nav-item label="Search">...</rail-nav-item>
|
|
605
|
+
* <rail-nav-separator />
|
|
606
|
+
* <rail-nav-item label="Trash">...</rail-nav-item>
|
|
607
|
+
* </rail-nav>
|
|
608
|
+
*
|
|
609
|
+
* Color overridable via `--rail-nav-separator-color`.
|
|
610
|
+
*/
|
|
611
|
+
class RailnavSeparatorComponent {
|
|
612
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavSeparatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
613
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.0", type: RailnavSeparatorComponent, isStandalone: true, selector: "rail-nav-separator", ngImport: i0, template: '', isInline: true, styles: [":host{display:flex;align-items:center;height:20px;padding:0 12px;margin-top:var(--rail-nav-separator-shift, 0);box-sizing:border-box;transition:margin-top .2s ease}:host:before{content:\"\";flex:1;height:1px;background:var(--rail-nav-separator-color, var(--mat-sys-outline-variant))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
614
|
+
}
|
|
615
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavSeparatorComponent, decorators: [{
|
|
616
|
+
type: Component,
|
|
617
|
+
args: [{ selector: 'rail-nav-separator', changeDetection: ChangeDetectionStrategy.OnPush, template: '', styles: [":host{display:flex;align-items:center;height:20px;padding:0 12px;margin-top:var(--rail-nav-separator-shift, 0);box-sizing:border-box;transition:margin-top .2s ease}:host:before{content:\"\";flex:1;height:1px;background:var(--rail-nav-separator-color, var(--mat-sys-outline-variant))}\n"] }]
|
|
618
|
+
}] });
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Flexible spacer between rail items. Placed between two groups of items, it
|
|
622
|
+
* pushes everything after it to the bottom of the rail — the classic pattern
|
|
623
|
+
* for keeping primary nav at the top and secondary entries (Settings,
|
|
624
|
+
* Profile…) anchored at the bottom.
|
|
625
|
+
*
|
|
626
|
+
* <rail-nav>
|
|
627
|
+
* <rail-nav-item label="Home">...</rail-nav-item>
|
|
628
|
+
* <rail-nav-item label="Inbox">...</rail-nav-item>
|
|
629
|
+
* <rail-nav-spacer />
|
|
630
|
+
* <rail-nav-item label="Settings">...</rail-nav-item>
|
|
631
|
+
* </rail-nav>
|
|
632
|
+
*
|
|
633
|
+
* Requires the rail to own its full height (already the case by default).
|
|
634
|
+
*/
|
|
635
|
+
class RailnavSpacerComponent {
|
|
636
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavSpacerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
637
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.0", type: RailnavSpacerComponent, isStandalone: true, selector: "rail-nav-spacer", ngImport: i0, template: '', isInline: true, styles: [":host{display:block;flex:1 1 auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
638
|
+
}
|
|
639
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: RailnavSpacerComponent, decorators: [{
|
|
640
|
+
type: Component,
|
|
641
|
+
args: [{ selector: 'rail-nav-spacer', changeDetection: ChangeDetectionStrategy.OnPush, template: '', styles: [":host{display:block;flex:1 1 auto}\n"] }]
|
|
642
|
+
}] });
|
|
347
643
|
|
|
348
644
|
/*
|
|
349
645
|
* Public API Surface of @softwarity/rail-nav
|
|
@@ -353,5 +649,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
353
649
|
* Generated bundle index. Do not edit.
|
|
354
650
|
*/
|
|
355
651
|
|
|
356
|
-
export { RailnavBrandingDirective, RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent };
|
|
652
|
+
export { RailnavBrandingDirective, RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent, RailnavSeparatorComponent, RailnavSpacerComponent };
|
|
357
653
|
//# sourceMappingURL=softwarity-rail-nav.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"softwarity-rail-nav.mjs","sources":["../../src/lib/railnav.component.ts","../../src/lib/railnav-container.component.ts","../../src/lib/railnav-content.component.ts","../../src/lib/railnav-item.component.ts","../../src/public-api.ts","../../src/softwarity-rail-nav.ts"],"sourcesContent":["import { Component, signal, effect, input, contentChild, ElementRef, Directive, computed } from '@angular/core';\nimport { MatSidenav } from '@angular/material/sidenav';\nimport { MatRippleModule } from '@angular/material/core';\nimport { NgTemplateOutlet } from '@angular/common';\n\n/** Directive to mark custom branding content */\n@Directive({\n selector: '[railNavBranding]'\n})\nexport class RailnavBrandingDirective {}\n\n@Component({\n selector: 'rail-nav',\n imports: [MatRippleModule, NgTemplateOutlet],\n template: `\n <!-- Default branding template (title/subtitle) -->\n <ng-template #defaultBrandingTpl>\n <div class=\"rail-branding\" [class.position-end]=\"railPosition() === 'end'\">\n @if (title()) {\n <span class=\"rail-title\">{{ title() }}</span>\n }\n @if (subtitle()) {\n <span class=\"rail-subtitle\">{{ subtitle() }}</span>\n }\n </div>\n </ng-template>\n <!-- Custom branding template (projected content) -->\n <ng-template #customBrandingTpl>\n <div class=\"rail-branding rail-branding-custom\" [class.position-end]=\"railPosition() === 'end'\">\n <ng-content select=\"[railNavBranding]\" />\n </div>\n </ng-template>\n @if (!hideDefaultHeader()) {\n <div class=\"rail-header\" [class.position-end]=\"railPosition() === 'end'\" matRipple (click)=\"toggleExpanded()\">\n @if (expanded() && hasBranding() && railPosition() === 'end') {\n <ng-container [ngTemplateOutlet]=\"customBranding() ? customBrandingTpl : defaultBrandingTpl\" />\n }\n <div class=\"rail-burger\" [class.expanded]=\"expanded()\" [class.position-end]=\"railPosition() === 'end'\">\n <svg class=\"icon-menu\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z\"/>\n </svg>\n <svg class=\"icon-menu-open\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z\"/>\n </svg>\n </div>\n @if (expanded() && hasBranding() && railPosition() === 'start') {\n <ng-container [ngTemplateOutlet]=\"customBranding() ? customBrandingTpl : defaultBrandingTpl\" />\n }\n </div>\n }\n <nav class=\"rail-items\">\n <ng-content />\n </nav>\n `,\n styles: [`\n :host {\n display: block;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 100;\n width: var(--rail-nav-collapsed-width, 72px);\n border: none !important;\n outline: none !important;\n box-shadow: none;\n background: var(--rail-nav-surface-color, var(--mat-sys-surface));\n transition: width 0.2s ease;\n overflow: visible;\n }\n\n :host(.expanded) {\n width: var(--rail-nav-expanded-width, fit-content);\n box-shadow: 4px 0 8px rgba(0,0,0,.2);\n }\n\n :host(.position-end) {\n left: auto;\n right: 0;\n }\n\n :host(.position-end.expanded) {\n box-shadow: -4px 0 8px rgba(0,0,0,.2);\n }\n\n .rail-header {\n display: flex;\n align-items: center;\n gap: 12px;\n height: var(--rail-nav-header-height, 64px);\n padding: 16px 16px 16px 24px;\n box-sizing: border-box;\n cursor: pointer;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-header.position-end {\n padding: 16px 24px 16px 16px;\n }\n\n .rail-header.position-end .rail-burger {\n margin-left: auto;\n }\n\n .rail-header:hover {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-items {\n display: flex;\n flex-direction: column;\n padding: 12px 12px 4px;\n gap: 0;\n box-sizing: border-box;\n width: 100%;\n }\n\n .rail-burger {\n position: relative;\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n }\n\n .rail-burger.position-end {\n transform: scaleX(-1);\n }\n\n .rail-burger svg {\n position: absolute;\n top: 0;\n left: 0;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n\n /* Position start (left) - clockwise rotation */\n .rail-burger .icon-menu {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n .rail-burger .icon-menu-open {\n opacity: 0;\n transform: rotate(-90deg);\n }\n\n .rail-burger.expanded .icon-menu {\n opacity: 0;\n transform: rotate(90deg);\n }\n\n .rail-burger.expanded .icon-menu-open {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n /* Position end (right) - counter-clockwise rotation (mirrored) */\n .rail-burger.position-end .icon-menu {\n transform: rotate(0deg);\n }\n\n .rail-burger.position-end .icon-menu-open {\n transform: rotate(90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu {\n transform: rotate(-90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu-open {\n transform: rotate(0deg);\n }\n\n .rail-branding {\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n text-align: right;\n padding-top: 5px;\n }\n\n .rail-branding.position-end {\n text-align: left;\n }\n\n .rail-title {\n font-size: 16px;\n font-weight: 500;\n line-height: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-subtitle {\n font-size: 11px;\n line-height: 1;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n opacity: 0.7;\n }\n `],\n host: {\n 'class': 'mat-drawer mat-sidenav',\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'railPosition() === \"end\"'\n }\n})\nexport class RailnavComponent extends MatSidenav {\n /** Position: 'start' (left) or 'end' (right) - aliased to avoid conflict with MatSidenav.position */\n readonly railPosition = input<'start' | 'end'>('start', { alias: 'position' });\n\n /** Hide the default header (burger + title/subtitle) */\n readonly hideDefaultHeader = input(false);\n\n /** Title displayed when expanded (ignored if railNavBranding is projected) */\n readonly title = input<string>();\n\n /** Subtitle displayed when expanded (ignored if railNavBranding is projected) */\n readonly subtitle = input<string>();\n\n /** Whether to auto-collapse when an item is clicked */\n readonly autoCollapse = input(true);\n\n /** Whether the rail is expanded to show labels */\n readonly expanded = signal(false);\n\n /** Detect custom branding content projection */\n protected readonly customBranding = contentChild(RailnavBrandingDirective);\n\n /** Whether there's any branding to show (custom or default) */\n protected readonly hasBranding = computed(() =>\n !!this.customBranding() || !!this.title() || !!this.subtitle()\n );\n\n constructor() {\n super();\n // Default settings for rail behavior\n this.opened = true;\n this.disableClose = true;\n this.mode = 'side';\n\n // Sync mode with expanded state\n effect(() => {\n this.mode = this.expanded() ? 'over' : 'side';\n });\n }\n\n /** Toggle between collapsed (rail) and expanded (drawer) */\n toggleExpanded(): void {\n this.expanded.update(e => !e);\n }\n\n /** Collapse the rail */\n collapse(): void {\n this.expanded.set(false);\n }\n\n /** Expand the rail to drawer */\n expand(): void {\n this.expanded.set(true);\n }\n}\n","import { Component, ContentChild, input } from '@angular/core';\nimport { MatSidenavContainer, MatDrawerContainer } from '@angular/material/sidenav';\nimport { RailnavComponent } from './railnav.component';\n\n@Component({\n selector: 'rail-nav-container',\n template: `\n <ng-content />\n @if (showBackdrop()) {\n <div\n class=\"railnav-backdrop\"\n [class.visible]=\"railnav?.expanded()\"\n [class.position-end]=\"railnav?.railPosition() === 'end'\"\n (click)=\"railnav?.collapse()\">\n </div>\n }\n `,\n styles: [`\n :host {\n display: block;\n position: relative;\n height: 100%;\n overflow: hidden;\n }\n\n .railnav-backdrop {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--rail-nav-collapsed-width, 72px);\n right: 0;\n background: var(--rail-nav-backdrop-color, rgba(0, 0, 0, 0.4));\n z-index: 99;\n cursor: pointer;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.3s ease-in-out;\n }\n\n .railnav-backdrop.position-end {\n left: 0;\n right: var(--rail-nav-collapsed-width, 72px);\n }\n\n .railnav-backdrop.visible {\n opacity: 1;\n pointer-events: auto;\n }\n `],\n host: {\n 'class': 'mat-drawer-container mat-sidenav-container'\n },\n providers: [\n { provide: MatDrawerContainer, useExisting: RailnavContainerComponent }\n ]\n})\nexport class RailnavContainerComponent extends MatSidenavContainer {\n /** Whether to show backdrop when expanded */\n readonly showBackdrop = input(true);\n\n @ContentChild(RailnavComponent) railnav?: RailnavComponent;\n}\n","import { Component, input, inject, computed } from '@angular/core';\nimport { MatSidenavContent } from '@angular/material/sidenav';\nimport { RailnavContainerComponent } from './railnav-container.component';\n\n@Component({\n selector: 'rail-nav-content',\n template: `<ng-content />`,\n styles: [`\n :host {\n display: block;\n height: 100%;\n overflow: auto;\n margin-left: var(--rail-nav-collapsed-width, 72px);\n }\n\n :host(.position-end) {\n margin-left: 0;\n margin-right: var(--rail-nav-collapsed-width, 72px);\n }\n `],\n host: {\n 'class': 'mat-drawer-content mat-sidenav-content',\n '[class.position-end]': 'effectivePosition() === \"end\"'\n }\n})\nexport class RailnavContentComponent extends MatSidenavContent {\n /** Optional: Position of the rail. If not set, uses the sibling rail-nav's position */\n readonly position = input<'start' | 'end' | undefined>(undefined);\n\n /** Parent container that gives access to sibling rail-nav */\n private container = inject(RailnavContainerComponent, { optional: true });\n\n /** Effective position - from input or from sibling rail-nav */\n protected readonly effectivePosition = computed(() => {\n const inputValue = this.position();\n if (inputValue !== undefined) return inputValue;\n return this.container?.railnav?.railPosition() ?? 'start';\n });\n}\n","import { Component, input, output, computed, inject, ChangeDetectionStrategy } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\nimport { MatRippleModule } from '@angular/material/core';\nimport { RailnavComponent } from './railnav.component';\nimport { NgTemplateOutlet } from '@angular/common';\n\n@Component({\n selector: 'rail-nav-item',\n imports: [RouterLink, RouterLinkActive, MatRippleModule, NgTemplateOutlet],\n template: `\n <ng-template #iconTpl>\n <div class=\"rail-item-pill\">\n <div class=\"rail-item-ripple\" matRipple></div>\n <div class=\"rail-item-icon-wrapper\">\n <div class=\"rail-item-icon\">\n <ng-content />\n </div>\n @if (hasBadge()) {\n <span class=\"rail-badge\" [class.dot]=\"isDotBadge()\">{{ isDotBadge() ? '' : badge() }}</span>\n }\n </div>\n <span class=\"rail-item-label label-inline\">{{ label() }}</span>\n </div>\n <span class=\"rail-item-label label-below\">{{ label() }}</span>\n </ng-template>\n @if (routerLink()) {\n <a\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"active\"\n (click)=\"onRouterLinkClick()\">\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n (click)=\"onItemClick()\">\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n </button>\n }\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .rail-item {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n text-decoration: none;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n cursor: pointer;\n padding: 0;\n gap: 4px;\n background: none;\n border: none;\n font: inherit;\n min-height: 56px;\n box-sizing: border-box;\n outline: none;\n width: 100%;\n }\n\n .rail-item.position-end {\n align-items: flex-end;\n }\n\n .rail-item:focus-visible .rail-item-pill {\n border-color: var(--rail-nav-primary, var(--mat-sys-primary));\n }\n\n .rail-item.position-end .rail-item-pill {\n justify-content: flex-end;\n padding-left: 0;\n padding-right: 10px;\n }\n\n .rail-item.expanded {\n gap: 0;\n }\n\n .rail-item-pill {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n width: 48px;\n height: 32px;\n border-radius: 9999px;\n border: 2px solid transparent;\n overflow: visible;\n margin-top: 12px;\n padding-left: 10px;\n box-sizing: border-box;\n transition: background 0.2s ease, width 0.2s ease, height 0.2s ease, margin 0.2s ease;\n }\n\n .rail-item-ripple {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n overflow: hidden;\n }\n\n .rail-item:hover .rail-item-pill {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-item.active .rail-item-pill {\n background: var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));\n color: var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container));\n }\n\n /* Expanded pill includes both icon and label */\n .rail-item.expanded .rail-item-pill {\n width: auto;\n height: 48px;\n padding: 0 16px 0 10px;\n gap: 12px;\n margin-top: 0;\n }\n\n .rail-item.expanded.position-end .rail-item-pill {\n flex-direction: row-reverse;\n justify-content: flex-start;\n padding: 0 10px 0 16px;\n }\n\n /* First item: reduce space after header and keep icon stable during expand */\n /* Collapsed: 8px + 16px (half of 32px) = 24px from top */\n /* Expanded: 0px + 24px (half of 48px) = 24px from top */\n :host:first-child .rail-item-pill {\n margin-top: 8px;\n }\n\n :host:first-child .rail-item.expanded .rail-item-pill {\n margin-top: 0;\n }\n\n .rail-item-icon-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: visible;\n }\n\n .rail-item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n\n .rail-item-icon ::ng-deep > * {\n font-size: 24px;\n width: 24px;\n height: 24px;\n }\n\n .rail-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 8px;\n background: var(--rail-nav-error, var(--mat-sys-error));\n color: var(--rail-nav-on-error, var(--mat-sys-on-error));\n font-size: 11px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n box-sizing: border-box;\n z-index: 10;\n }\n\n /* Small dot badge (no text) */\n .rail-badge.dot {\n top: -2px;\n right: -2px;\n min-width: 6px;\n width: 6px;\n height: 6px;\n padding: 0;\n border-radius: 3px;\n }\n\n /* Label below icon (collapsed mode) */\n .rail-item-label.label-below {\n font-size: 12px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 48px;\n max-height: 16px;\n opacity: 1;\n transition: opacity 0.1s ease, max-height 0.2s ease;\n }\n\n .rail-item.expanded .rail-item-label.label-below {\n opacity: 0;\n max-height: 0;\n pointer-events: none;\n }\n\n /* Label inline with icon (expanded mode) */\n .rail-item-label.label-inline {\n font-size: 14px;\n font-weight: 500;\n line-height: 24px;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 0;\n opacity: 0;\n pointer-events: none;\n }\n\n .rail-item.expanded .rail-item-label.label-inline {\n width: auto;\n flex: 1;\n opacity: 1;\n pointer-events: auto;\n transition: opacity 0.15s ease 0.1s;\n }\n\n .rail-item.expanded.position-end .rail-item-label.label-inline {\n text-align: right;\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'position() === \"end\"'\n }\n})\nexport class RailnavItemComponent {\n /** Router link for navigation */\n readonly routerLink = input<string | any[]>();\n\n /** Label text displayed */\n readonly label = input<string>();\n\n /** Badge value (number, text, or true for dot badge) */\n readonly badge = input<string | number | boolean>();\n\n /** Whether this item is active (for non-router usage) */\n readonly active = input(false);\n\n /** Whether to show a badge */\n protected readonly hasBadge = computed(() => {\n const b = this.badge();\n return b !== undefined && b !== null && b !== false;\n });\n\n /** Whether to show a small dot badge (no text) */\n protected readonly isDotBadge = computed(() => {\n const b = this.badge();\n return b === true || b === '';\n });\n\n /** Click event (for non-router usage) */\n readonly itemClick = output<void>();\n\n /** Reference to parent rail-nav */\n private railnav = inject(RailnavComponent);\n\n /** Whether the rail is expanded */\n protected expanded = computed(() => this.railnav.expanded());\n\n /** Position of the rail (start or end) */\n protected position = computed(() => this.railnav.railPosition());\n\n /** Handle item click - emit event and optionally collapse rail */\n protected onItemClick(): void {\n this.itemClick.emit();\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n }\n\n /** Handle router link click - optionally collapse rail */\n protected onRouterLinkClick(): void {\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n }\n}\n","/*\n * Public API Surface of @softwarity/rail-nav\n */\n\nexport { RailnavComponent, RailnavBrandingDirective } from './lib/railnav.component';\nexport { RailnavContainerComponent } from './lib/railnav-container.component';\nexport { RailnavContentComponent } from './lib/railnav-content.component';\nexport { RailnavItemComponent } from './lib/railnav-item.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"softwarity-rail-nav.mjs","sources":["../../src/lib/railnav-drawer.orchestrator.ts","../../src/lib/railnav.component.ts","../../src/lib/railnav-container.component.ts","../../src/lib/railnav-content.component.ts","../../src/lib/railnav-item.component.ts","../../src/lib/railnav-separator.component.ts","../../src/lib/railnav-spacer.component.ts","../../src/public-api.ts","../../src/softwarity-rail-nav.ts"],"sourcesContent":["import { Overlay, OverlayRef } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport { DestroyRef, ElementRef, Injectable, TemplateRef, ViewContainerRef, effect, inject } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RailnavComponent } from './railnav.component';\n\n/**\n * One per `RailnavComponent` (provided on its `providers`). Owns the single\n * CDK overlay used to render the currently-open `<rail-nav-drawer>`, and the\n * close timer that lets the cursor travel between the trigger item and the\n * drawer without the drawer closing on transit.\n *\n * - Anchors the overlay to the rail's host element (right edge), so the drawer\n * sits next to the collapsed rail regardless of which trigger item opened it.\n * - Auto-closes when the rail enters `expanded()` mode (mutual exclusion).\n * - Mouse enter/leave on the overlay panel itself cancels/arms the close timer,\n * so the drawer stays open while the cursor is over it.\n */\n@Injectable()\nexport class RailnavDrawerOrchestrator {\n private readonly overlay = inject(Overlay);\n private readonly vcr = inject(ViewContainerRef);\n private readonly rail = inject(RailnavComponent);\n private readonly railEl = inject(ElementRef<HTMLElement>);\n\n /** Window after which we close if nothing cancels — covers cursor transit. */\n private static readonly LEAVE_DELAY_MS = 300;\n /** Post-open grace period during which `armClose()` is a no-op. Touch devices\n * dispatch a burst of synthetic mouse events (with `pointerType === 'mouse'`)\n * right after `click` — this window swallows them. */\n private static readonly SYNTHETIC_GUARD_MS = 500;\n\n private overlayRef?: OverlayRef;\n private currentTemplate?: TemplateRef<unknown>;\n private closeTimer?: ReturnType<typeof setTimeout>;\n private resizeObserver?: ResizeObserver;\n private outsideClickSub?: Subscription;\n /** Timestamp of the last `open()` — used to gate close-on-leave so that\n * synthetic `mouseleave` events dispatched right after a touch tap\n * (with `pointerType === 'mouse'`) don't shut the drawer instantly. */\n private lastOpenAt = 0;\n\n constructor() {\n // Mutual exclusion with the rail's expanded mode: expanding closes any open\n // drawer, and `open()` short-circuits while expanded. The expanded rail is\n // itself the alternative nav surface, no point stacking a drawer on top.\n effect(() => {\n if (this.rail.expanded()) this.closeNow();\n });\n inject(DestroyRef).onDestroy(() => {\n this.clearCloseTimer();\n this.overlayRef?.dispose();\n });\n }\n\n /** Opens or re-targets the overlay to the given template. No-op while the\n * rail is expanded. Cancels any pending close timer (e.g. user hovered back\n * from a quick traverse). */\n open(template: TemplateRef<unknown>): void {\n if (this.rail.expanded()) return;\n this.clearCloseTimer();\n this.lastOpenAt = performance.now();\n if (this.currentTemplate === template && this.overlayRef?.hasAttached()) {\n return;\n }\n this.detach();\n this.currentTemplate = template;\n\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(this.railEl)\n .withPositions([\n { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top' },\n ]);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n hasBackdrop: false,\n panelClass: 'rail-nav-drawer-panel',\n // Reposition on every scroll so the drawer stays glued to the rail\n // when the page scrolls (default `noop` strategy leaves the overlay\n // fixed to the viewport, which visually decouples it from the rail).\n scrollStrategy: this.overlay.scrollStrategies.reposition(),\n });\n\n // Tap / click outside the overlay closes it — except when the click\n // lands in the rail itself (re-clicking a `[for]` item should re-target\n // or no-op, not close). Crucial on touch where hover-leave doesn't fire.\n this.outsideClickSub = this.overlayRef.outsidePointerEvents().subscribe((event) => {\n const target = event.target as Node | null;\n if (target && this.railEl.nativeElement.contains(target)) return;\n this.closeNow();\n });\n\n // Keep the drawer open while the cursor is over it; close shortly after\n // it leaves (matching the trigger's own leave debounce). pointer* events\n // let us ignore the synthetic mouseleave fired around touch taps.\n this.overlayRef.overlayElement.addEventListener('pointerenter', this.onOverlayEnter);\n this.overlayRef.overlayElement.addEventListener('pointerleave', this.onOverlayLeave);\n\n // Inline-style the panel: the projected template content lives outside any\n // component tree, so component-encapsulated styles can't reach it without\n // ::ng-deep / Encapsulation.None. Setting styles directly on overlayElement\n // sidesteps the issue entirely. Each value reads a CSS custom property so\n // consumers can override via `--rail-nav-drawer-*` on the rail.\n //\n // The lib applies both the chrome (background / shadow / radius) AND the\n // default nav-list layout (flex column / padding / gap / min-width) so the\n // consumer's template can be just a flat list of children — they stack\n // vertically with proper spacing out of the box.\n const el = this.overlayRef.overlayElement;\n el.style.background = 'var(--rail-nav-drawer-surface, var(--mat-sys-surface))';\n el.style.color = 'var(--rail-nav-drawer-on-surface, var(--mat-sys-on-surface))';\n el.style.boxShadow = 'var(--rail-nav-drawer-shadow, var(--mat-sys-level2))';\n el.style.borderRadius = 'var(--rail-nav-drawer-radius, 0 12px 12px 0)';\n el.style.overflow = 'auto';\n el.style.boxSizing = 'border-box';\n el.style.display = 'flex';\n el.style.flexDirection = 'column';\n el.style.padding = 'var(--rail-nav-drawer-padding, 12px)';\n el.style.gap = 'var(--rail-nav-drawer-gap, 4px)';\n // Fixed width so the drawer doesn't shrink-fit its content (which would\n // make each trigger's drawer a different size) and absorbs the layout\n // variance between MatButton variants (e.g. text vs tonal padding).\n el.style.width = 'var(--rail-nav-drawer-width, 240px)';\n\n // Subtle fade-in on every (re-)open: applied via Web Animations API so we\n // don't ship CSS keyframes globally. Re-targeting the overlay to another\n // template re-runs this animation, giving a soft cross-fade feel.\n el.animate(\n [\n { opacity: 0, transform: 'translateX(-4px)' },\n { opacity: 1, transform: 'translateX(0)' },\n ],\n { duration: 180, easing: 'ease-out' },\n );\n\n this.overlayRef.attach(new TemplatePortal(template, this.vcr));\n\n // Match the rail's full height so the drawer sits flush against it\n // regardless of viewport / layout. ResizeObserver keeps it in sync.\n this.applyHeight();\n this.resizeObserver?.disconnect();\n this.resizeObserver = new ResizeObserver(() => this.applyHeight());\n this.resizeObserver.observe(this.railEl.nativeElement);\n }\n\n private applyHeight(): void {\n if (!this.overlayRef) return;\n const rect = this.railEl.nativeElement.getBoundingClientRect();\n this.overlayRef.overlayElement.style.height = `${rect.height}px`;\n }\n\n /** Arms the close timer. Cancelled if the cursor returns to the trigger or\n * enters the overlay before it fires. Suppressed during the post-open guard\n * window so synthetic mouseleave events fired right after a touch tap\n * don't shut the drawer instantly. */\n armClose(): void {\n if (performance.now() - this.lastOpenAt < RailnavDrawerOrchestrator.SYNTHETIC_GUARD_MS) {\n return;\n }\n this.clearCloseTimer();\n this.closeTimer = setTimeout(\n () => this.closeNow(),\n RailnavDrawerOrchestrator.LEAVE_DELAY_MS,\n );\n }\n\n /** Force-close immediately, e.g. on a click inside the drawer that navigates. */\n closeNow(): void {\n this.clearCloseTimer();\n this.detach();\n }\n\n private detach(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n this.outsideClickSub?.unsubscribe();\n this.outsideClickSub = undefined;\n if (!this.overlayRef) return;\n this.overlayRef.overlayElement.removeEventListener('pointerenter', this.onOverlayEnter);\n this.overlayRef.overlayElement.removeEventListener('pointerleave', this.onOverlayLeave);\n // dispose (not just detach) so the next `open()` builds a fresh overlay\n // with its own position strategy + listeners — no stale state to leak.\n this.overlayRef.dispose();\n this.overlayRef = undefined;\n this.currentTemplate = undefined;\n }\n\n private clearCloseTimer(): void {\n if (this.closeTimer !== undefined) {\n clearTimeout(this.closeTimer);\n this.closeTimer = undefined;\n }\n }\n\n // Arrow-functions so the listener references stay stable across add/remove.\n // Touch / pen pointers are ignored — those flows are click-driven.\n private readonly onOverlayEnter = (event: PointerEvent): void => {\n if (event.pointerType !== 'mouse') return;\n this.clearCloseTimer();\n };\n private readonly onOverlayLeave = (event: PointerEvent): void => {\n if (event.pointerType !== 'mouse') return;\n this.armClose();\n };\n}\n","import { Component, signal, effect, input, contentChild, ElementRef, Directive, computed } from '@angular/core';\nimport { MatSidenav } from '@angular/material/sidenav';\nimport { MatRippleModule } from '@angular/material/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { RailnavDrawerOrchestrator } from './railnav-drawer.orchestrator';\n\n/** Directive to mark custom branding content */\n@Directive({\n selector: '[railNavBranding]'\n})\nexport class RailnavBrandingDirective {}\n\n@Component({\n selector: 'rail-nav',\n imports: [MatRippleModule, NgTemplateOutlet],\n template: `\n <!-- Default branding template (title/subtitle) -->\n <ng-template #defaultBrandingTpl>\n <div class=\"rail-branding\" [class.position-end]=\"railPosition() === 'end'\">\n @if (title()) {\n <span class=\"rail-title\">{{ title() }}</span>\n }\n @if (subtitle()) {\n <span class=\"rail-subtitle\">{{ subtitle() }}</span>\n }\n </div>\n </ng-template>\n <!-- Custom branding template (projected content) -->\n <ng-template #customBrandingTpl>\n <div class=\"rail-branding rail-branding-custom\" [class.position-end]=\"railPosition() === 'end'\">\n <ng-content select=\"[railNavBranding]\" />\n </div>\n </ng-template>\n @if (!hideDefaultHeader()) {\n <div class=\"rail-header\" [class.position-end]=\"railPosition() === 'end'\" matRipple (click)=\"toggleExpanded()\">\n @if (expanded() && hasBranding() && railPosition() === 'end') {\n <ng-container [ngTemplateOutlet]=\"customBranding() ? customBrandingTpl : defaultBrandingTpl\" />\n }\n <div class=\"rail-burger\" [class.expanded]=\"expanded()\" [class.position-end]=\"railPosition() === 'end'\">\n <svg class=\"icon-menu\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z\"/>\n </svg>\n <svg class=\"icon-menu-open\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"currentColor\">\n <path d=\"M120-240v-80h520v80H120Zm664-40L584-480l200-200 56 56-144 144 144 144-56 56ZM120-440v-80h400v80H120Zm0-200v-80h520v80H120Z\"/>\n </svg>\n </div>\n @if (expanded() && hasBranding() && railPosition() === 'start') {\n <ng-container [ngTemplateOutlet]=\"customBranding() ? customBrandingTpl : defaultBrandingTpl\" />\n }\n </div>\n }\n <nav class=\"rail-items\">\n <ng-content />\n </nav>\n `,\n styles: [`\n :host {\n /* flex column so .rail-items can stretch (flex: 1) and */\n /* rail-nav-spacer inside it can push siblings to the bottom. */\n display: flex;\n flex-direction: column;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n z-index: 100;\n width: var(--rail-nav-collapsed-width, 72px);\n border: none !important;\n outline: none !important;\n box-shadow: none;\n background: var(--rail-nav-surface-color, var(--mat-sys-surface));\n transition: width 0.2s ease;\n overflow: visible;\n\n /* Collapsed default: rail items show their label-below (~20px) but */\n /* the next pill keeps its margin-top, so the inter-item gap is */\n /* asymmetric. Shift the separator down to recenter it visually. */\n --rail-nav-separator-shift: 12px;\n }\n\n :host(.expanded) {\n width: var(--rail-nav-expanded-width, fit-content);\n box-shadow: 4px 0 8px rgba(0,0,0,.2);\n /* Expanded mode has no label-below, items are flush. No shift needed. */\n --rail-nav-separator-shift: 0;\n }\n\n :host(.position-end) {\n left: auto;\n right: 0;\n }\n\n :host(.position-end.expanded) {\n box-shadow: -4px 0 8px rgba(0,0,0,.2);\n }\n\n .rail-header {\n display: flex;\n align-items: center;\n gap: 12px;\n height: var(--rail-nav-header-height, 64px);\n padding: 16px 16px 16px 24px;\n box-sizing: border-box;\n cursor: pointer;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-header.position-end {\n padding: 16px 24px 16px 16px;\n }\n\n .rail-header.position-end .rail-burger {\n margin-left: auto;\n }\n\n .rail-header:hover {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-items {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n /* Symmetric vertical padding so the last item (typically Settings */\n /* anchored via <rail-nav-spacer/>) breathes against the rail edge. */\n padding: 12px;\n gap: 0;\n box-sizing: border-box;\n width: 100%;\n }\n\n .rail-burger {\n position: relative;\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n }\n\n .rail-burger.position-end {\n transform: scaleX(-1);\n }\n\n .rail-burger svg {\n position: absolute;\n top: 0;\n left: 0;\n transition: opacity 0.3s ease, transform 0.3s ease;\n }\n\n /* Position start (left) - clockwise rotation */\n .rail-burger .icon-menu {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n .rail-burger .icon-menu-open {\n opacity: 0;\n transform: rotate(-90deg);\n }\n\n .rail-burger.expanded .icon-menu {\n opacity: 0;\n transform: rotate(90deg);\n }\n\n .rail-burger.expanded .icon-menu-open {\n opacity: 1;\n transform: rotate(0deg);\n }\n\n /* Position end (right) - counter-clockwise rotation (mirrored) */\n .rail-burger.position-end .icon-menu {\n transform: rotate(0deg);\n }\n\n .rail-burger.position-end .icon-menu-open {\n transform: rotate(90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu {\n transform: rotate(-90deg);\n }\n\n .rail-burger.position-end.expanded .icon-menu-open {\n transform: rotate(0deg);\n }\n\n .rail-branding {\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 0;\n overflow: hidden;\n text-align: right;\n padding-top: 5px;\n }\n\n .rail-branding.position-end {\n text-align: left;\n }\n\n .rail-title {\n font-size: 16px;\n font-weight: 500;\n line-height: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--rail-nav-on-surface, var(--mat-sys-on-surface));\n }\n\n .rail-subtitle {\n font-size: 11px;\n line-height: 1;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n opacity: 0.7;\n }\n `],\n host: {\n 'class': 'mat-drawer mat-sidenav',\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'railPosition() === \"end\"'\n },\n providers: [RailnavDrawerOrchestrator]\n})\nexport class RailnavComponent extends MatSidenav {\n /** Position: 'start' (left) or 'end' (right) - aliased to avoid conflict with MatSidenav.position */\n readonly railPosition = input<'start' | 'end'>('start', { alias: 'position' });\n\n /** Hide the default header (burger + title/subtitle) */\n readonly hideDefaultHeader = input(false);\n\n /** Title displayed when expanded (ignored if railNavBranding is projected) */\n readonly title = input<string>();\n\n /** Subtitle displayed when expanded (ignored if railNavBranding is projected) */\n readonly subtitle = input<string>();\n\n /** Whether to auto-collapse when an item is clicked */\n readonly autoCollapse = input(true);\n\n /** Whether the rail is expanded to show labels */\n readonly expanded = signal(false);\n\n /** Detect custom branding content projection */\n protected readonly customBranding = contentChild(RailnavBrandingDirective);\n\n /** Whether there's any branding to show (custom or default) */\n protected readonly hasBranding = computed(() =>\n !!this.customBranding() || !!this.title() || !!this.subtitle()\n );\n\n constructor() {\n super();\n // Default settings for rail behavior\n this.opened = true;\n this.disableClose = true;\n this.mode = 'side';\n\n // Sync mode with expanded state\n effect(() => {\n this.mode = this.expanded() ? 'over' : 'side';\n });\n }\n\n /** Toggle between collapsed (rail) and expanded (drawer) */\n toggleExpanded(): void {\n this.expanded.update(e => !e);\n }\n\n /** Collapse the rail */\n collapse(): void {\n this.expanded.set(false);\n }\n\n /** Expand the rail to drawer */\n expand(): void {\n this.expanded.set(true);\n }\n}\n","import { Component, ContentChild, input } from '@angular/core';\nimport { MatSidenavContainer, MatDrawerContainer } from '@angular/material/sidenav';\nimport { RailnavComponent } from './railnav.component';\n\n@Component({\n selector: 'rail-nav-container',\n template: `\n <ng-content />\n @if (showBackdrop()) {\n <div\n class=\"railnav-backdrop\"\n [class.visible]=\"railnav?.expanded()\"\n [class.position-end]=\"railnav?.railPosition() === 'end'\"\n (click)=\"railnav?.collapse()\">\n </div>\n }\n `,\n styles: [`\n :host {\n display: block;\n position: relative;\n height: 100%;\n overflow: hidden;\n }\n\n .railnav-backdrop {\n position: absolute;\n top: 0;\n bottom: 0;\n left: var(--rail-nav-collapsed-width, 72px);\n right: 0;\n background: var(--rail-nav-backdrop-color, rgba(0, 0, 0, 0.4));\n z-index: 99;\n cursor: pointer;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.3s ease-in-out;\n }\n\n .railnav-backdrop.position-end {\n left: 0;\n right: var(--rail-nav-collapsed-width, 72px);\n }\n\n .railnav-backdrop.visible {\n opacity: 1;\n pointer-events: auto;\n }\n `],\n host: {\n 'class': 'mat-drawer-container mat-sidenav-container'\n },\n providers: [\n { provide: MatDrawerContainer, useExisting: RailnavContainerComponent }\n ]\n})\nexport class RailnavContainerComponent extends MatSidenavContainer {\n /** Whether to show backdrop when expanded */\n readonly showBackdrop = input(true);\n\n @ContentChild(RailnavComponent) railnav?: RailnavComponent;\n}\n","import { Component, input, inject, computed } from '@angular/core';\nimport { MatSidenavContent } from '@angular/material/sidenav';\nimport { RailnavContainerComponent } from './railnav-container.component';\n\n@Component({\n selector: 'rail-nav-content',\n template: `<ng-content />`,\n styles: [`\n :host {\n display: block;\n height: 100%;\n overflow: auto;\n margin-left: var(--rail-nav-collapsed-width, 72px);\n }\n\n :host(.position-end) {\n margin-left: 0;\n margin-right: var(--rail-nav-collapsed-width, 72px);\n }\n `],\n host: {\n 'class': 'mat-drawer-content mat-sidenav-content',\n '[class.position-end]': 'effectivePosition() === \"end\"'\n }\n})\nexport class RailnavContentComponent extends MatSidenavContent {\n /** Optional: Position of the rail. If not set, uses the sibling rail-nav's position */\n readonly position = input<'start' | 'end' | undefined>(undefined);\n\n /** Parent container that gives access to sibling rail-nav */\n private container = inject(RailnavContainerComponent, { optional: true });\n\n /** Effective position - from input or from sibling rail-nav */\n protected readonly effectivePosition = computed(() => {\n const inputValue = this.position();\n if (inputValue !== undefined) return inputValue;\n return this.container?.railnav?.railPosition() ?? 'start';\n });\n}\n","import { Component, DestroyRef, TemplateRef, input, output, computed, inject, ChangeDetectionStrategy } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\nimport { MatRippleModule } from '@angular/material/core';\nimport { RailnavComponent } from './railnav.component';\nimport { RailnavDrawerOrchestrator } from './railnav-drawer.orchestrator';\nimport { NgTemplateOutlet } from '@angular/common';\n\n@Component({\n selector: 'rail-nav-item',\n imports: [RouterLink, RouterLinkActive, MatRippleModule, NgTemplateOutlet],\n template: `\n <ng-template #iconTpl>\n <div class=\"rail-item-pill\">\n <div class=\"rail-item-ripple\" matRipple></div>\n <div class=\"rail-item-icon-wrapper\">\n <div class=\"rail-item-icon\">\n <ng-content />\n </div>\n @if (hasBadge()) {\n <span class=\"rail-badge\" [class.dot]=\"isDotBadge()\">{{ isDotBadge() ? '' : badge() }}</span>\n }\n </div>\n <span class=\"rail-item-label label-inline\">{{ label() }}</span>\n </div>\n <span class=\"rail-item-label label-below\">{{ label() }}</span>\n </ng-template>\n @if (routerLink()) {\n <a\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n [routerLink]=\"routerLink()\"\n routerLinkActive=\"active\"\n (click)=\"onRouterLinkClick()\">\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"rail-item\"\n [class.expanded]=\"expanded()\"\n [class.position-end]=\"position() === 'end'\"\n [class.active]=\"active()\"\n (click)=\"onItemClick()\">\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n </button>\n }\n `,\n styles: [`\n :host {\n display: block;\n }\n\n .rail-item {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n text-decoration: none;\n color: var(--rail-nav-on-surface-variant, var(--mat-sys-on-surface-variant));\n cursor: pointer;\n padding: 0;\n gap: 4px;\n background: none;\n border: none;\n font: inherit;\n min-height: 56px;\n box-sizing: border-box;\n outline: none;\n width: 100%;\n /* Transition gap so it animates in sync with the pill (height, margin) */\n /* and the label (max-height) — otherwise gap snaps instantly to 0 and */\n /* siblings jolt during the expand/collapse animation. */\n transition: gap 0.2s ease;\n }\n\n .rail-item.position-end {\n align-items: flex-end;\n }\n\n .rail-item:focus-visible .rail-item-pill {\n border-color: var(--rail-nav-primary, var(--mat-sys-primary));\n }\n\n .rail-item.position-end .rail-item-pill {\n justify-content: flex-end;\n padding-left: 0;\n padding-right: 10px;\n }\n\n .rail-item.expanded {\n gap: 0;\n }\n\n .rail-item-pill {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n width: 48px;\n height: 32px;\n border-radius: 9999px;\n border: 2px solid transparent;\n overflow: visible;\n margin-top: 12px;\n padding-left: 10px;\n box-sizing: border-box;\n transition: background 0.2s ease, width 0.2s ease, height 0.2s ease, margin 0.2s ease;\n }\n\n .rail-item-ripple {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n overflow: hidden;\n }\n\n .rail-item:hover .rail-item-pill {\n background: var(--rail-nav-surface-container-high, var(--mat-sys-surface-container-high));\n }\n\n .rail-item.active .rail-item-pill {\n background: var(--rail-nav-secondary-container, var(--mat-sys-secondary-container));\n color: var(--rail-nav-on-secondary-container, var(--mat-sys-on-secondary-container));\n }\n\n /* Expanded pill includes both icon and label */\n .rail-item.expanded .rail-item-pill {\n width: auto;\n height: 48px;\n padding: 0 16px 0 10px;\n gap: 12px;\n margin-top: 0;\n }\n\n .rail-item.expanded.position-end .rail-item-pill {\n flex-direction: row-reverse;\n justify-content: flex-start;\n padding: 0 10px 0 16px;\n }\n\n /* First item: reduce space after header and keep icon stable during expand */\n /* Collapsed: 8px + 16px (half of 32px) = 24px from top */\n /* Expanded: 0px + 24px (half of 48px) = 24px from top */\n :host:first-child .rail-item-pill {\n margin-top: 8px;\n }\n\n :host:first-child .rail-item.expanded .rail-item-pill {\n margin-top: 0;\n }\n\n /* Last item (typically anchored at the bottom via <rail-nav-spacer/>): */\n /* in expanded mode the pill loses both its margin-top AND the label-below */\n /* below it, so the icon center jumps up 12px relative to the rail bottom. */\n /* Mirror of the first-child fix: add a matching margin-bottom on the pill */\n /* in expanded mode to keep the icon visually anchored. */\n :host:last-child .rail-item.expanded .rail-item-pill {\n margin-bottom: 12px;\n }\n\n .rail-item-icon-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n overflow: visible;\n }\n\n .rail-item-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n }\n\n .rail-item-icon ::ng-deep > * {\n font-size: 24px;\n width: 24px;\n height: 24px;\n }\n\n .rail-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 8px;\n background: var(--rail-nav-error, var(--mat-sys-error));\n color: var(--rail-nav-on-error, var(--mat-sys-on-error));\n font-size: 11px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n box-sizing: border-box;\n z-index: 10;\n }\n\n /* Small dot badge (no text) */\n .rail-badge.dot {\n top: -2px;\n right: -2px;\n min-width: 6px;\n width: 6px;\n height: 6px;\n padding: 0;\n border-radius: 3px;\n }\n\n /* Label below icon (collapsed mode) */\n .rail-item-label.label-below {\n font-size: 12px;\n font-weight: 500;\n line-height: 16px;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 48px;\n max-height: 16px;\n opacity: 1;\n transition: opacity 0.1s ease, max-height 0.2s ease;\n }\n\n .rail-item.expanded .rail-item-label.label-below {\n opacity: 0;\n max-height: 0;\n pointer-events: none;\n }\n\n /* Label inline with icon (expanded mode) */\n .rail-item-label.label-inline {\n font-size: 14px;\n font-weight: 500;\n line-height: 24px;\n text-align: left;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 0;\n opacity: 0;\n pointer-events: none;\n }\n\n .rail-item.expanded .rail-item-label.label-inline {\n width: auto;\n flex: 1;\n opacity: 1;\n pointer-events: auto;\n transition: opacity 0.15s ease 0.1s;\n }\n\n .rail-item.expanded.position-end .rail-item-label.label-inline {\n text-align: right;\n }\n `],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class.expanded]': 'expanded()',\n '[class.position-end]': 'position() === \"end\"',\n // pointer* events let us filter by pointerType: touch devices emit\n // synthetic mouseenter/leave around taps, which would arm the close timer\n // right after a click-to-open and shut the drawer instantly. Hover-intent\n // only applies for real mouse pointers.\n '(pointerenter)': 'onHostPointerEnter($event)',\n '(pointerleave)': 'onHostPointerLeave($event)'\n }\n})\nexport class RailnavItemComponent {\n /** Router link for navigation */\n readonly routerLink = input<string | any[]>();\n\n /** Label text displayed */\n readonly label = input<string>();\n\n /** Badge value (number, text, or true for dot badge) */\n readonly badge = input<string | number | boolean>();\n\n /** Whether this item is active (for non-router usage) */\n readonly active = input(false);\n\n /** Template projected as a contextual side panel when the item is hovered\n * or clicked. Aliased `for` to mirror Material's `mat-datepicker-toggle`\n * (`<rail-nav-item [for]=\"myTpl\">`). Declare the content as a plain\n * `<ng-template #myTpl>...</ng-template>` anywhere in the host template.\n *\n * When set, the item becomes a trigger; the rail's orchestrator handles\n * overlay rendering, animation, hover-intent and close timer. The panel\n * carries the `rail-nav-drawer-panel` class — style it via the theme mixin\n * `rail-nav.panel()` (or your own global rule). Suppressed while the rail\n * is `expanded()`. */\n readonly for = input<TemplateRef<unknown> | null>(null, { alias: 'for' });\n\n /** Hover-intent delay (ms) before opening the drawer. Cancelled if the\n * cursor leaves the item first. Click bypasses the delay. */\n private static readonly ENTER_DELAY_MS = 200;\n\n /** Whether to show a badge */\n protected readonly hasBadge = computed(() => {\n const b = this.badge();\n return b !== undefined && b !== null && b !== false;\n });\n\n /** Whether to show a small dot badge (no text) */\n protected readonly isDotBadge = computed(() => {\n const b = this.badge();\n return b === true || b === '';\n });\n\n /** Click event (for non-router usage) */\n readonly itemClick = output<void>();\n\n /** Reference to parent rail-nav */\n private railnav = inject(RailnavComponent);\n\n /** Owned by the parent rail (one orchestrator per rail). null in tests where\n * the item is rendered outside a real rail. */\n private orchestrator = inject(RailnavDrawerOrchestrator, { optional: true });\n\n /** Pending hover-intent timer, cleared on leave or destroy. */\n private enterTimeout?: ReturnType<typeof setTimeout>;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.clearEnterTimeout());\n }\n\n /** Whether the rail is expanded */\n protected expanded = computed(() => this.railnav.expanded());\n\n /** Position of the rail (start or end) */\n protected position = computed(() => this.railnav.railPosition());\n\n /** Handle item click - emit event, optionally collapse rail, then open drawer.\n * Collapse must come BEFORE openDrawer: while the rail is expanded, the\n * orchestrator suppresses opens (mutual exclusion). Collapsing first ensures\n * the click on an expanded rail still opens the drawer. */\n protected onItemClick(): void {\n this.itemClick.emit();\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n this.openDrawerIfAny();\n }\n\n /** Handle router link click - optionally collapse rail */\n protected onRouterLinkClick(): void {\n if (this.railnav.autoCollapse()) {\n this.railnav.collapse();\n }\n }\n\n /** Hover-intent: when the item carries a `for` drawer, open it after a\n * short delay. The delay is cancelled if the cursor leaves before it fires.\n * Ignored on non-mouse pointers (touch/pen) — the click path handles them. */\n protected onHostPointerEnter(event: PointerEvent): void {\n if (event.pointerType !== 'mouse') return;\n if (!this.for() || !this.orchestrator) return;\n if (this.railnav.expanded()) return;\n this.clearEnterTimeout();\n this.enterTimeout = setTimeout(\n () => this.openDrawerIfAny(),\n RailnavItemComponent.ENTER_DELAY_MS,\n );\n }\n\n /** Arm the orchestrator's close timer when leaving the trigger item. If\n * the cursor reaches the overlay before it fires, the overlay's own\n * `pointerenter` cancels it. Ignored on touch/pen for the same reason. */\n protected onHostPointerLeave(event: PointerEvent): void {\n if (event.pointerType !== 'mouse') return;\n this.clearEnterTimeout();\n if (this.for()) this.orchestrator?.armClose();\n }\n\n private openDrawerIfAny(): void {\n const drawer = this.for();\n if (!drawer || !this.orchestrator) return;\n this.clearEnterTimeout();\n this.orchestrator.open(drawer);\n }\n\n private clearEnterTimeout(): void {\n if (this.enterTimeout !== undefined) {\n clearTimeout(this.enterTimeout);\n this.enterTimeout = undefined;\n }\n }\n}\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n/**\n * Visual separator between groups of `<rail-nav-item>`. Renders a thin\n * horizontal rule with sensible margins so groups of items breathe.\n *\n * <rail-nav>\n * <rail-nav-item label=\"Home\">...</rail-nav-item>\n * <rail-nav-item label=\"Search\">...</rail-nav-item>\n * <rail-nav-separator />\n * <rail-nav-item label=\"Trash\">...</rail-nav-item>\n * </rail-nav>\n *\n * Color overridable via `--rail-nav-separator-color`.\n */\n@Component({\n selector: 'rail-nav-separator',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: '',\n styles: [`\n /* Fixed-height host with the 1px line centered via flex. */\n /* The parent rail sets --rail-nav-separator-shift to recenter the */\n /* line visually between items (label-below in collapsed mode makes */\n /* the inter-item gap asymmetric without this). */\n :host {\n display: flex;\n align-items: center;\n height: 20px;\n padding: 0 12px;\n margin-top: var(--rail-nav-separator-shift, 0);\n box-sizing: border-box;\n transition: margin-top 0.2s ease;\n }\n :host::before {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--rail-nav-separator-color, var(--mat-sys-outline-variant));\n }\n `],\n})\nexport class RailnavSeparatorComponent {}\n","import { ChangeDetectionStrategy, Component } from '@angular/core';\n\n/**\n * Flexible spacer between rail items. Placed between two groups of items, it\n * pushes everything after it to the bottom of the rail — the classic pattern\n * for keeping primary nav at the top and secondary entries (Settings,\n * Profile…) anchored at the bottom.\n *\n * <rail-nav>\n * <rail-nav-item label=\"Home\">...</rail-nav-item>\n * <rail-nav-item label=\"Inbox\">...</rail-nav-item>\n * <rail-nav-spacer />\n * <rail-nav-item label=\"Settings\">...</rail-nav-item>\n * </rail-nav>\n *\n * Requires the rail to own its full height (already the case by default).\n */\n@Component({\n selector: 'rail-nav-spacer',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: '',\n styles: [`\n :host {\n display: block;\n flex: 1 1 auto;\n }\n `],\n})\nexport class RailnavSpacerComponent {}\n","/*\n * Public API Surface of @softwarity/rail-nav\n */\n\nexport { RailnavComponent, RailnavBrandingDirective } from './lib/railnav.component';\nexport { RailnavContainerComponent } from './lib/railnav-container.component';\nexport { RailnavContentComponent } from './lib/railnav-content.component';\nexport { RailnavItemComponent } from './lib/railnav-item.component';\nexport { RailnavSeparatorComponent } from './lib/railnav-separator.component';\nexport { RailnavSpacerComponent } from './lib/railnav-spacer.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { TemplateRef } from '@angular/core';
|
|
2
3
|
import { MatSidenav, MatSidenavContainer, MatSidenavContent } from '@angular/material/sidenav';
|
|
3
4
|
|
|
4
5
|
/** Directive to mark custom branding content */
|
|
@@ -62,6 +63,20 @@ declare class RailnavItemComponent {
|
|
|
62
63
|
readonly badge: _angular_core.InputSignal<string | number | boolean | undefined>;
|
|
63
64
|
/** Whether this item is active (for non-router usage) */
|
|
64
65
|
readonly active: _angular_core.InputSignal<boolean>;
|
|
66
|
+
/** Template projected as a contextual side panel when the item is hovered
|
|
67
|
+
* or clicked. Aliased `for` to mirror Material's `mat-datepicker-toggle`
|
|
68
|
+
* (`<rail-nav-item [for]="myTpl">`). Declare the content as a plain
|
|
69
|
+
* `<ng-template #myTpl>...</ng-template>` anywhere in the host template.
|
|
70
|
+
*
|
|
71
|
+
* When set, the item becomes a trigger; the rail's orchestrator handles
|
|
72
|
+
* overlay rendering, animation, hover-intent and close timer. The panel
|
|
73
|
+
* carries the `rail-nav-drawer-panel` class — style it via the theme mixin
|
|
74
|
+
* `rail-nav.panel()` (or your own global rule). Suppressed while the rail
|
|
75
|
+
* is `expanded()`. */
|
|
76
|
+
readonly for: _angular_core.InputSignal<TemplateRef<unknown> | null>;
|
|
77
|
+
/** Hover-intent delay (ms) before opening the drawer. Cancelled if the
|
|
78
|
+
* cursor leaves the item first. Click bypasses the delay. */
|
|
79
|
+
private static readonly ENTER_DELAY_MS;
|
|
65
80
|
/** Whether to show a badge */
|
|
66
81
|
protected readonly hasBadge: _angular_core.Signal<boolean>;
|
|
67
82
|
/** Whether to show a small dot badge (no text) */
|
|
@@ -70,16 +85,73 @@ declare class RailnavItemComponent {
|
|
|
70
85
|
readonly itemClick: _angular_core.OutputEmitterRef<void>;
|
|
71
86
|
/** Reference to parent rail-nav */
|
|
72
87
|
private railnav;
|
|
88
|
+
/** Owned by the parent rail (one orchestrator per rail). null in tests where
|
|
89
|
+
* the item is rendered outside a real rail. */
|
|
90
|
+
private orchestrator;
|
|
91
|
+
/** Pending hover-intent timer, cleared on leave or destroy. */
|
|
92
|
+
private enterTimeout?;
|
|
93
|
+
constructor();
|
|
73
94
|
/** Whether the rail is expanded */
|
|
74
95
|
protected expanded: _angular_core.Signal<boolean>;
|
|
75
96
|
/** Position of the rail (start or end) */
|
|
76
97
|
protected position: _angular_core.Signal<"start" | "end">;
|
|
77
|
-
/** Handle item click - emit event
|
|
98
|
+
/** Handle item click - emit event, optionally collapse rail, then open drawer.
|
|
99
|
+
* Collapse must come BEFORE openDrawer: while the rail is expanded, the
|
|
100
|
+
* orchestrator suppresses opens (mutual exclusion). Collapsing first ensures
|
|
101
|
+
* the click on an expanded rail still opens the drawer. */
|
|
78
102
|
protected onItemClick(): void;
|
|
79
103
|
/** Handle router link click - optionally collapse rail */
|
|
80
104
|
protected onRouterLinkClick(): void;
|
|
105
|
+
/** Hover-intent: when the item carries a `for` drawer, open it after a
|
|
106
|
+
* short delay. The delay is cancelled if the cursor leaves before it fires.
|
|
107
|
+
* Ignored on non-mouse pointers (touch/pen) — the click path handles them. */
|
|
108
|
+
protected onHostPointerEnter(event: PointerEvent): void;
|
|
109
|
+
/** Arm the orchestrator's close timer when leaving the trigger item. If
|
|
110
|
+
* the cursor reaches the overlay before it fires, the overlay's own
|
|
111
|
+
* `pointerenter` cancels it. Ignored on touch/pen for the same reason. */
|
|
112
|
+
protected onHostPointerLeave(event: PointerEvent): void;
|
|
113
|
+
private openDrawerIfAny;
|
|
114
|
+
private clearEnterTimeout;
|
|
81
115
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavItemComponent, never>;
|
|
82
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavItemComponent, "rail-nav-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, ["*"], true, never>;
|
|
116
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavItemComponent, "rail-nav-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "for": { "alias": "for"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, ["*"], true, never>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Visual separator between groups of `<rail-nav-item>`. Renders a thin
|
|
121
|
+
* horizontal rule with sensible margins so groups of items breathe.
|
|
122
|
+
*
|
|
123
|
+
* <rail-nav>
|
|
124
|
+
* <rail-nav-item label="Home">...</rail-nav-item>
|
|
125
|
+
* <rail-nav-item label="Search">...</rail-nav-item>
|
|
126
|
+
* <rail-nav-separator />
|
|
127
|
+
* <rail-nav-item label="Trash">...</rail-nav-item>
|
|
128
|
+
* </rail-nav>
|
|
129
|
+
*
|
|
130
|
+
* Color overridable via `--rail-nav-separator-color`.
|
|
131
|
+
*/
|
|
132
|
+
declare class RailnavSeparatorComponent {
|
|
133
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavSeparatorComponent, never>;
|
|
134
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavSeparatorComponent, "rail-nav-separator", never, {}, {}, never, never, true, never>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Flexible spacer between rail items. Placed between two groups of items, it
|
|
139
|
+
* pushes everything after it to the bottom of the rail — the classic pattern
|
|
140
|
+
* for keeping primary nav at the top and secondary entries (Settings,
|
|
141
|
+
* Profile…) anchored at the bottom.
|
|
142
|
+
*
|
|
143
|
+
* <rail-nav>
|
|
144
|
+
* <rail-nav-item label="Home">...</rail-nav-item>
|
|
145
|
+
* <rail-nav-item label="Inbox">...</rail-nav-item>
|
|
146
|
+
* <rail-nav-spacer />
|
|
147
|
+
* <rail-nav-item label="Settings">...</rail-nav-item>
|
|
148
|
+
* </rail-nav>
|
|
149
|
+
*
|
|
150
|
+
* Requires the rail to own its full height (already the case by default).
|
|
151
|
+
*/
|
|
152
|
+
declare class RailnavSpacerComponent {
|
|
153
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RailnavSpacerComponent, never>;
|
|
154
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<RailnavSpacerComponent, "rail-nav-spacer", never, {}, {}, never, never, true, never>;
|
|
83
155
|
}
|
|
84
156
|
|
|
85
|
-
export { RailnavBrandingDirective, RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent };
|
|
157
|
+
export { RailnavBrandingDirective, RailnavComponent, RailnavContainerComponent, RailnavContentComponent, RailnavItemComponent, RailnavSeparatorComponent, RailnavSpacerComponent };
|