@nanahoshi/mona-ui 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/collapsible/package.json +4 -0
  2. package/fesm2022/nanahoshi-mona-ui-button.mjs +1 -2
  3. package/fesm2022/nanahoshi-mona-ui-collapsible.mjs +304 -0
  4. package/fesm2022/nanahoshi-mona-ui-contextmenu.mjs +1 -1
  5. package/fesm2022/nanahoshi-mona-ui-dropdown-button.mjs +1 -1
  6. package/fesm2022/nanahoshi-mona-ui-expansion-panel.mjs +20 -9
  7. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-cell.component-B6FBLtrr.mjs → nanahoshi-mona-ui-grid-grid-footer-cell.component-D-JCCvYJ.mjs} +2 -2
  8. package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-table-cell.directive-OJe4UYuw.mjs → nanahoshi-mona-ui-grid-grid-footer-table-cell.directive-7zwgl7u1.mjs} +2 -2
  9. package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-DCd8K6ng.mjs → nanahoshi-mona-ui-grid-grid-list.component-n3eQNIza.mjs} +5 -5
  10. package/fesm2022/{nanahoshi-mona-ui-grid-grid-toggle.component-Dl8Hlkhj.mjs → nanahoshi-mona-ui-grid-grid-toggle.component-BhS7c7rW.mjs} +2 -2
  11. package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-C5FRZlvv.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-B4VB3xFw.mjs} +5 -5
  12. package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-BRZUd8cE.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-A6Cq2a4Q.mjs} +11 -11
  13. package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
  14. package/fesm2022/nanahoshi-mona-ui-menubar.mjs +1 -1
  15. package/fesm2022/nanahoshi-mona-ui-popup-menu.mjs +18 -16
  16. package/fesm2022/nanahoshi-mona-ui-popup.mjs +49 -28
  17. package/fesm2022/nanahoshi-mona-ui-sheet.mjs +246 -0
  18. package/fesm2022/nanahoshi-mona-ui-sidebar.mjs +2025 -0
  19. package/fesm2022/nanahoshi-mona-ui-skeleton.mjs +96 -0
  20. package/fesm2022/nanahoshi-mona-ui-split-button.mjs +1 -1
  21. package/fesm2022/nanahoshi-mona-ui-theme.mjs +61 -25
  22. package/fesm2022/nanahoshi-mona-ui-tooltip.mjs +49 -2
  23. package/package.json +17 -1
  24. package/sheet/package.json +4 -0
  25. package/sidebar/package.json +4 -0
  26. package/skeleton/package.json +4 -0
  27. package/types/nanahoshi-mona-ui-collapsible.d.ts +92 -0
  28. package/types/nanahoshi-mona-ui-expansion-panel.d.ts +11 -3
  29. package/types/nanahoshi-mona-ui-popup-menu.d.ts +6 -4
  30. package/types/nanahoshi-mona-ui-popup.d.ts +16 -1
  31. package/types/nanahoshi-mona-ui-sheet.d.ts +62 -0
  32. package/types/nanahoshi-mona-ui-sidebar.d.ts +695 -0
  33. package/types/nanahoshi-mona-ui-skeleton.d.ts +51 -0
  34. package/types/nanahoshi-mona-ui-theme.d.ts +28 -2
@@ -0,0 +1,2025 @@
1
+ import * as i1 from '@angular/cdk/a11y';
2
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
3
+ import * as i0 from '@angular/core';
4
+ import { signal, computed, Service, InjectionToken, inject, PLATFORM_ID, linkedSignal, effect, untracked, ElementRef, Injector, input, model, afterNextRender, ChangeDetectionStrategy, Component, DestroyRef, Directive, makeEnvironmentProviders } from '@angular/core';
5
+ import { classInputToClass } from '@nanahoshi/mona-ui/common';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { createElementControlId, themeRaisedBackdropClasses } from '@nanahoshi/mona-ui/internal';
8
+ import { isPlatformBrowser } from '@angular/common';
9
+ import { cva } from 'class-variance-authority';
10
+ import { SkeletonComponent } from '@nanahoshi/mona-ui/skeleton';
11
+ import * as i1$1 from '@nanahoshi/mona-ui/text-box';
12
+ import { TextBoxDirective } from '@nanahoshi/mona-ui/text-box';
13
+ import * as i1$2 from '@nanahoshi/mona-ui/button';
14
+ import { ButtonDirective } from '@nanahoshi/mona-ui/button';
15
+ import { CollapsibleToken } from '@nanahoshi/mona-ui/collapsible';
16
+
17
+ /**
18
+ * Layout-wide state shared by every sidebar under one `mona-sidebar-layout`. Provided by
19
+ * `SidebarLayoutComponent`, while each `mona-sidebar` provides its own `SidebarService`.
20
+ *
21
+ * A layout can hold more than one sidebar — a navigation column on one edge and an inspector on the
22
+ * other — so the things that genuinely belong to the layout rather than to any one sidebar live here:
23
+ * the breakpoint, the registry the layout and its siblings look sidebars up in, and the rule that only
24
+ * one drawer is open at a time.
25
+ *
26
+ * This class is internal, and is not reachable through the public API. Consumers read a single
27
+ * sidebar's state through `injectSidebar()`.
28
+ */
29
+ class SidebarLayoutService {
30
+ constructor() {
31
+ this.#compact = signal(false, /* @ts-ignore */
32
+ ...(ngDevMode ? [{ debugName: "#compact" }] : /* istanbul ignore next */ []));
33
+ this.#controllers = signal(new Map(), /* @ts-ignore */
34
+ ...(ngDevMode ? [{ debugName: "#controllers" }] : /* istanbul ignore next */ []));
35
+ /**
36
+ * @description Whether any sidebar's drawer is open. The layout paints one backdrop for all of
37
+ * them, and the region beside them steps out of the way for whichever one is showing.
38
+ */
39
+ this.anyMobileOpen = computed(() => [...this.#controllers().values()].some(controller => controller.mobileOpen()), /* @ts-ignore */
40
+ ...(ngDevMode ? [{ debugName: "anyMobileOpen" }] : /* istanbul ignore next */ []));
41
+ /** @description Whether the viewport is narrow enough for sidebars to present as overlay drawers. */
42
+ this.compact = this.#compact.asReadonly();
43
+ /**
44
+ * @description The first sidebar registered in the layout, in author order.
45
+ *
46
+ * What the parts that sit beside the sidebars rather than inside one fall back to: a trigger in a
47
+ * shared header with no `for` set, and the inset's surface. Best effort by design — with more than
48
+ * one sidebar in the layout there is no single right answer, and author order is the predictable
49
+ * one. Name a sidebar with `for` to be explicit.
50
+ */
51
+ this.primaryController = computed(() => this.#controllers().values().next().value ?? null, /* @ts-ignore */
52
+ ...(ngDevMode ? [{ debugName: "primaryController" }] : /* istanbul ignore next */ []));
53
+ /** @description The variant of the primary sidebar, which is the one the inset takes its surface from. */
54
+ this.primaryVariant = computed(() => this.primaryController()?.variant() ?? "sidebar", /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "primaryVariant" }] : /* istanbul ignore next */ []));
56
+ }
57
+ #compact;
58
+ #controllers;
59
+ /** Closes every open drawer. Backs the shared backdrop and the layout's Escape handling. */
60
+ closeAll() {
61
+ for (const controller of this.#controllers().values()) {
62
+ if (controller.mobileOpen()) {
63
+ controller.collapse();
64
+ }
65
+ }
66
+ }
67
+ /** @description The sidebar registered under `id`, or `null` when no sidebar answers to it. */
68
+ getController(id) {
69
+ return this.#controllers().get(id) ?? null;
70
+ }
71
+ /**
72
+ * Closes any other open drawer, so only one is ever showing. Called by a sidebar as it opens its
73
+ * own: two drawers overlaying the same layout would stack their backdrops and fight over the focus
74
+ * trap, and on the viewport that puts them there, there is only room for one.
75
+ */
76
+ notifyOpening(openingId) {
77
+ for (const [id, controller] of this.#controllers()) {
78
+ if (id !== openingId && controller.mobileOpen()) {
79
+ controller.collapse();
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Adds a sidebar to the registry and returns the callback that removes it again. `mona-sidebar`
85
+ * re-runs this whenever its resolved id changes and calls the result on teardown.
86
+ *
87
+ * Two sidebars sharing an id collide here, and the later registration wins. That is a mistake in
88
+ * the markup rather than something to recover from — ids are already required to be unique in a
89
+ * document — so it is left to the duplicate `id` attributes to surface it.
90
+ */
91
+ register(id, controller) {
92
+ this.#controllers.update(controllers => new Map(controllers).set(id, controller));
93
+ return () => {
94
+ this.#controllers.update(controllers => {
95
+ // Only if this registration is still the current one. An id that has since been taken
96
+ // over by another sidebar belongs to that sidebar now, and must survive this cleanup.
97
+ if (controllers.get(id) !== controller) {
98
+ return controllers;
99
+ }
100
+ const next = new Map(controllers);
101
+ next.delete(id);
102
+ return next;
103
+ });
104
+ };
105
+ }
106
+ /** @internal Published by `mona-sidebar-layout` from its breakpoint query. */
107
+ setCompact(compact) {
108
+ this.#compact.set(compact);
109
+ }
110
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarLayoutService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
111
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.6", ngImport: i0, type: SidebarLayoutService, autoProvided: false }); }
112
+ }
113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarLayoutService, decorators: [{
114
+ type: Service,
115
+ args: [{ autoProvided: false }]
116
+ }] });
117
+
118
+ function toLogicalSide(side) {
119
+ return side === "end" || side === "right" ? "end" : "start";
120
+ }
121
+
122
+ /**
123
+ * The default `SidebarStorage`: `localStorage`, under the sidebar's `persistKey`.
124
+ *
125
+ * Reads report `null` anywhere `localStorage` cannot answer, and writes are dropped there. That
126
+ * covers the server, where there is no such thing, and a browser that refuses it — Safari's private
127
+ * mode throws on access rather than returning empty, and a full quota throws on write. None of that
128
+ * is worth taking an application down over: the sidebar simply opens the way its input says.
129
+ *
130
+ * A server-rendered application gets the default state in its markup and the remembered one on
131
+ * hydration, which shows as a flash. Storage that the server can read too — a cookie, or state
132
+ * transferred from the request — avoids it; supply it through `provideSidebarStorage()`.
133
+ */
134
+ class LocalStorageSidebarStorage {
135
+ #browser;
136
+ /** Takes the value of `PLATFORM_ID`, which is a string in practice though typed loosely. */
137
+ constructor(platformId) {
138
+ this.#browser = isPlatformBrowser(platformId);
139
+ }
140
+ read(key) {
141
+ if (!this.#browser) {
142
+ return null;
143
+ }
144
+ try {
145
+ const stored = localStorage.getItem(key);
146
+ return stored === null ? null : stored === "true";
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ write(key, expanded) {
153
+ if (!this.#browser) {
154
+ return;
155
+ }
156
+ try {
157
+ localStorage.setItem(key, String(expanded));
158
+ }
159
+ catch {
160
+ // Nothing to recover: the sidebar still works, it just will not be remembered.
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Where sidebars given a `persistKey` remember whether they were left open. Defaults to
167
+ * `localStorage`. Replace it with `provideSidebarStorage()` — with something the server can read too,
168
+ * for an application that renders there.
169
+ */
170
+ const SIDEBAR_STORAGE = new InjectionToken("Mona UI sidebar storage", {
171
+ providedIn: "root",
172
+ factory: () => new LocalStorageSidebarStorage(inject(PLATFORM_ID))
173
+ });
174
+
175
+ /**
176
+ * State for one sidebar region. Provided by the `mona-sidebar` it belongs to and injectable by any
177
+ * descendant, so triggers and menu parts can read and drive that sidebar without input chains.
178
+ *
179
+ * One instance per sidebar, not per layout: a layout can hold a navigation column on one edge and an
180
+ * inspector on the other, and they collapse, open and sit on their own sides independently. What the
181
+ * two genuinely share — the breakpoint, and the rule that only one drawer is open at a time — lives on
182
+ * `SidebarLayoutService` instead.
183
+ *
184
+ * This class is internal. Consumers reach the same state through `injectSidebar()`, which hands back
185
+ * a `SidebarController`: the reads, and the commands that are safe from anywhere. The structural
186
+ * setters here exist so `mona-sidebar` can publish its own inputs, and calling them from elsewhere
187
+ * would leave the service and the component disagreeing until the next change detection pass.
188
+ */
189
+ class SidebarService {
190
+ #collapsible;
191
+ /**
192
+ * Read from the layout rather than held here, because the breakpoint is one fact about the
193
+ * viewport that every sidebar in the layout answers to. A `mona-sidebar` standing on its own
194
+ * without a layout around it has nothing watching the viewport, and stays docked.
195
+ */
196
+ #compact;
197
+ #expanded;
198
+ #generatedId;
199
+ #layoutService;
200
+ #persistKey;
201
+ #providedId;
202
+ #side;
203
+ #storage;
204
+ #variant;
205
+ /**
206
+ * Reset every time the viewport crosses the breakpoint. A drawer left open on a phone must not
207
+ * still count as open when the window is widened back to a desktop layout, where the same flag
208
+ * would be read as the docked sidebar's state.
209
+ */
210
+ #mobileOpen;
211
+ constructor() {
212
+ this.#collapsible = signal("offcanvas", /* @ts-ignore */
213
+ ...(ngDevMode ? [{ debugName: "#collapsible" }] : /* istanbul ignore next */ []));
214
+ /**
215
+ * Read from the layout rather than held here, because the breakpoint is one fact about the
216
+ * viewport that every sidebar in the layout answers to. A `mona-sidebar` standing on its own
217
+ * without a layout around it has nothing watching the viewport, and stays docked.
218
+ */
219
+ this.#compact = computed(() => this.#layoutService?.compact() ?? false, /* @ts-ignore */
220
+ ...(ngDevMode ? [{ debugName: "#compact" }] : /* istanbul ignore next */ []));
221
+ this.#expanded = signal(true, /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "#expanded" }] : /* istanbul ignore next */ []));
223
+ this.#generatedId = createElementControlId();
224
+ this.#layoutService = inject(SidebarLayoutService, { optional: true });
225
+ this.#persistKey = signal("", /* @ts-ignore */
226
+ ...(ngDevMode ? [{ debugName: "#persistKey" }] : /* istanbul ignore next */ []));
227
+ this.#providedId = signal(null, /* @ts-ignore */
228
+ ...(ngDevMode ? [{ debugName: "#providedId" }] : /* istanbul ignore next */ []));
229
+ this.#side = signal("start", /* @ts-ignore */
230
+ ...(ngDevMode ? [{ debugName: "#side" }] : /* istanbul ignore next */ []));
231
+ this.#storage = inject(SIDEBAR_STORAGE);
232
+ this.#variant = signal("sidebar", /* @ts-ignore */
233
+ ...(ngDevMode ? [{ debugName: "#variant" }] : /* istanbul ignore next */ []));
234
+ /**
235
+ * Reset every time the viewport crosses the breakpoint. A drawer left open on a phone must not
236
+ * still count as open when the window is widened back to a desktop layout, where the same flag
237
+ * would be read as the docked sidebar's state.
238
+ */
239
+ this.#mobileOpen = linkedSignal({ ...(ngDevMode ? { debugName: "#mobileOpen" } : /* istanbul ignore next */ {}), source: () => this.#compact(),
240
+ computation: () => false });
241
+ /** @description How the sidebar behaves when collapsed. Set by the `mona-sidebar` it belongs to. */
242
+ this.collapsible = this.#collapsible.asReadonly();
243
+ /** @description Whether the sidebar presents as an overlay drawer rather than as a column. */
244
+ this.compact = this.#compact;
245
+ /**
246
+ * @description Whether the sidebar is open. While compact this reports the drawer, so one binding
247
+ * drives both presentations. Always `true` while `collapsible` is `"none"` on a wide viewport.
248
+ */
249
+ this.expanded = computed(() => {
250
+ if (this.#compact()) {
251
+ return this.#mobileOpen();
252
+ }
253
+ return this.#collapsible() === "none" || this.#expanded();
254
+ }, /* @ts-ignore */
255
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
256
+ /**
257
+ * @description Whether the sidebar is currently showing its icon rail, meaning parts that only make
258
+ * sense at full width — labels, trailing actions, submenus — should step aside. Never true while
259
+ * compact: a drawer has the room to present everything at full width.
260
+ */
261
+ this.iconOnly = computed(() => !this.#compact() && this.#collapsible() === "icon" && !this.expanded(), /* @ts-ignore */
262
+ ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
263
+ /** @description Whether the overlay drawer is open. Only meaningful while `compact` is true. */
264
+ this.mobileOpen = computed(() => this.#compact() && this.#mobileOpen(), /* @ts-ignore */
265
+ ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
266
+ /** @description Which edge the sidebar sits on, resolved to a direction-relative side. */
267
+ this.side = computed(() => toLogicalSide(this.#side()), /* @ts-ignore */
268
+ ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
269
+ /**
270
+ * @description Id assigned to the sidebar element and referenced by triggers through `aria-controls`.
271
+ * Falls back to a generated id when the consumer supplies none.
272
+ */
273
+ this.sidebarId = computed(() => this.#providedId() ?? this.#generatedId, /* @ts-ignore */
274
+ ...(ngDevMode ? [{ debugName: "sidebarId" }] : /* istanbul ignore next */ []));
275
+ /** @description The expanded state as a string, for `data-state` bindings and styling hooks. */
276
+ this.state = computed(() => (this.expanded() ? "expanded" : "collapsed"), /* @ts-ignore */
277
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
278
+ /** @description The visual treatment of the sidebar. Set by the `mona-sidebar` it belongs to. */
279
+ this.variant = this.#variant.asReadonly();
280
+ /** @description The subset of this service that is safe to hand to a consumer. */
281
+ this.controller = {
282
+ collapse: () => this.collapse(),
283
+ collapsible: this.collapsible,
284
+ compact: this.compact,
285
+ expand: () => this.expand(),
286
+ expanded: this.expanded,
287
+ iconOnly: this.iconOnly,
288
+ mobileOpen: this.mobileOpen,
289
+ side: this.side,
290
+ sidebarId: this.sidebarId,
291
+ state: this.state,
292
+ toggle: () => this.toggle(),
293
+ variant: this.variant
294
+ };
295
+ // Restore before persist, so the first pass cannot write the default back over what was
296
+ // stored. Both sit idle until `mona-sidebar` publishes a key, which it only does when the
297
+ // consumer asked for one.
298
+ effect(() => {
299
+ const key = this.#persistKey();
300
+ if (!key) {
301
+ return;
302
+ }
303
+ untracked(() => {
304
+ const stored = this.#storage.read(key);
305
+ if (stored !== null) {
306
+ this.#expanded.set(stored);
307
+ }
308
+ });
309
+ });
310
+ /*
311
+ * The docked signal, not `expanded`. That one reports the drawer while compact, and a drawer
312
+ * is not what is being remembered here: opening one on a phone would otherwise overwrite the
313
+ * state the desktop is meant to come back to.
314
+ */
315
+ effect(() => {
316
+ const key = this.#persistKey();
317
+ const expanded = this.#expanded();
318
+ if (!key) {
319
+ return;
320
+ }
321
+ untracked(() => this.#storage.write(key, expanded));
322
+ });
323
+ }
324
+ collapse() {
325
+ this.setExpanded(false);
326
+ }
327
+ expand() {
328
+ this.setExpanded(true);
329
+ }
330
+ /** @internal Published by `mona-sidebar` from its own input. */
331
+ setCollapsible(collapsible) {
332
+ this.#collapsible.set(collapsible);
333
+ }
334
+ /**
335
+ * Routed to whichever presentation is current, so a trigger, a rail and a `[(expanded)]` binding
336
+ * all mean the same thing on a phone as they do on a desktop.
337
+ */
338
+ setExpanded(expanded) {
339
+ if (this.#compact()) {
340
+ // Announced before opening, so the layout can close any other drawer first. Closing takes
341
+ // the `expanded === false` path on that sidebar and never comes back through here.
342
+ if (expanded) {
343
+ this.#layoutService?.notifyOpening(this.sidebarId());
344
+ }
345
+ this.#mobileOpen.set(expanded);
346
+ return;
347
+ }
348
+ if (this.#collapsible() === "none") {
349
+ return;
350
+ }
351
+ this.#expanded.set(expanded);
352
+ }
353
+ /** @internal Published by `mona-sidebar` from its own input. */
354
+ setId(id) {
355
+ this.#providedId.set(id || null);
356
+ }
357
+ /** @internal Published by `mona-sidebar` from its own input. */
358
+ setPersistKey(persistKey) {
359
+ this.#persistKey.set(persistKey);
360
+ }
361
+ /** @internal Published by `mona-sidebar` from its own input. */
362
+ setSide(side) {
363
+ this.#side.set(side);
364
+ }
365
+ /** @internal Published by `mona-sidebar` from its own input. */
366
+ setVariant(variant) {
367
+ this.#variant.set(variant);
368
+ }
369
+ toggle() {
370
+ this.setExpanded(!this.expanded());
371
+ }
372
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
373
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.6", ngImport: i0, type: SidebarService, autoProvided: false }); }
374
+ }
375
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarService, decorators: [{
376
+ type: Service,
377
+ args: [{ autoProvided: false }]
378
+ }], ctorParameters: () => [] });
379
+
380
+ /**
381
+ * How much of the sidebar's inline axis each variant's own border takes up.
382
+ *
383
+ * Borders are painted inside the box under the global `border-box`, so they come out of the width the
384
+ * parts inside are measured against: an icon rail of `3rem` less a region's `p-2` is meant to leave
385
+ * exactly the `2rem` square a menu button becomes, and a border silently makes it 1px or 2px short —
386
+ * enough to clip the edge off a footer avatar sized to that square. `SidebarComponent` adds this back
387
+ * on, so the authored width is the width the contents actually get.
388
+ *
389
+ * It lives here, beside the variants that draw those borders, so the two cannot drift apart. It is a
390
+ * plain length rather than a custom property because it lands in a transitioned `width`, and a
391
+ * `var()` there resolves to a pending-substitution value that engines are not obliged to interpolate.
392
+ */
393
+ const sidebarBorderAllowance = Object.freeze({
394
+ // A single edge border, whichever side it sits on.
395
+ sidebar: "1px",
396
+ // Bordered on all four sides, so the inline axis loses two of them.
397
+ floating: "2px",
398
+ // Draws no border of its own; the raised surface moves to the region beside it.
399
+ inset: "0px"
400
+ });
401
+ /*
402
+ * Every part that changes shape when the sidebar collapses runs on `--mona-motion-standard` with the
403
+ * same easing, so the whole region moves as one piece instead of each part arriving on its own
404
+ * schedule. The utilities are written out in full at each use site rather than shared through a
405
+ * constant, because Tailwind extracts class names from source text and cannot follow an identifier.
406
+ */
407
+ /**
408
+ * `relative` is what scopes the drawer and its backdrop to this layout. They are positioned against
409
+ * it rather than the viewport, so a sidebar embedded in a bounded region — a documentation example, a
410
+ * split pane — overlays only that region, and a layout that fills the page behaves identically.
411
+ */
412
+ const sidebarLayoutBaseThemeVariants = cva(`
413
+ relative flex flex-row w-full h-screen h-dvh
414
+ overflow-hidden
415
+ `);
416
+ const sidebarContentThemeVariants = cva("flex-1", {
417
+ variants: {
418
+ iconOnly: {
419
+ true: "overflow-hidden",
420
+ false: "overflow-x-hidden overflow-y-auto"
421
+ }
422
+ }
423
+ });
424
+ const sidebarFooterThemeVariants = cva("p-2 shrink-0");
425
+ const sidebarGroupThemeVariants = cva(`
426
+ relative flex flex-col w-full p-2
427
+ `);
428
+ const sidebarGroupContentThemeVariants = cva("w-full");
429
+ /**
430
+ * This stays a static host class because consumers commonly compose `monaButton` on the same
431
+ * element. The button owns `[class]`, so the sidebar publishes its state through `data-hidden`
432
+ * instead of adding a competing class binding.
433
+ */
434
+ const sidebarGroupActionClasses = `
435
+ p-1 w-auto h-auto
436
+ visible opacity-100
437
+ transition-[opacity,visibility] duration-(--mona-motion-standard) ease-out
438
+ motion-reduce:transition-none
439
+ data-[hidden=true]:invisible data-[hidden=true]:opacity-0
440
+ `;
441
+ const sidebarInsetThemeVariants = cva(`
442
+ flex flex-col grow flex-1 min-w-0
443
+ overflow-auto
444
+ `, {
445
+ variants: {
446
+ variant: {
447
+ sidebar: "h-full",
448
+ floating: "h-full",
449
+ // The `inset` variant moves the raised surface from the sidebar onto this region.
450
+ inset: `
451
+ m-2 h-[calc(100%-1rem)] rounded-lg
452
+ border border-(--color-border) bg-(--color-surface) shadow-(--shadow-raised)
453
+ `
454
+ },
455
+ behindDrawer: {
456
+ true: "overflow-hidden!",
457
+ false: ""
458
+ }
459
+ }
460
+ });
461
+ const sidebarGroupHeaderThemeVariants = cva(`
462
+ flex items-center px-2 overflow-hidden
463
+ transition-[height] duration-(--mona-motion-standard) ease-out
464
+ motion-reduce:transition-none
465
+ `, {
466
+ variants: {
467
+ // On the rail the label and any action have stood down, so the row closes rather than
468
+ // reserving empty space. Closing it by height keeps it in step with the sidebar's width;
469
+ // the inset stays put so nothing inside it slides while it does.
470
+ iconOnly: {
471
+ true: "h-0",
472
+ false: "h-8"
473
+ }
474
+ }
475
+ });
476
+ const sidebarGroupLabelThemeVariants = cva(`
477
+ flex grow-1 items-center shrink-0
478
+ text-xs font-medium whitespace-nowrap text-(--color-sidebar-foreground)/70
479
+ transition-[opacity,visibility] duration-(--mona-motion-standard) ease-out
480
+ motion-reduce:transition-none
481
+ `, {
482
+ variants: {
483
+ hidden: {
484
+ true: "invisible opacity-0",
485
+ false: "visible opacity-100"
486
+ }
487
+ }
488
+ });
489
+ const sidebarHeaderThemeVariants = cva("p-2 shrink-0");
490
+ /**
491
+ * `monaTextBox` owns `[class]`, so the rail state is exposed through `data-hidden` and consumed by
492
+ * this static class rather than by a second class binding.
493
+ */
494
+ const sidebarInputClasses = cva(`
495
+ w-full h-8
496
+ data-[hidden=true]:hidden
497
+ `);
498
+ const sidebarMenuActionClasses = cva(`
499
+ w-auto h-auto p-1!
500
+ hover:bg-(--color-sidebar-accent)! hover:text-(--color-sidebar-accent-foreground)!
501
+ group-data-[active=true]/menu-item:text-(--color-sidebar-primary-foreground)!
502
+ data-[hidden=true]:hidden
503
+ `);
504
+ const sidebarMenuSkeletonThemeVariants = cva(`
505
+ flex h-8 w-full items-center overflow-hidden
506
+ transition-[gap,padding] duration-(--mona-motion-standard) ease-out
507
+ motion-reduce:transition-none
508
+ `, {
509
+ variants: {
510
+ iconOnly: {
511
+ true: "gap-8 px-2",
512
+ false: "gap-2 px-1"
513
+ }
514
+ }
515
+ });
516
+ const sidebarMenuItemThemeVariants = cva(`
517
+ flex text-base gap-1
518
+ group/menu-item
519
+ data-[active=true]:bg-(--color-sidebar-primary)
520
+ data-[active=true]:text-(--color-sidebar-primary-foreground)
521
+ data-[active=true]:hover:bg-(--color-sidebar-primary)
522
+ data-[active=true]:hover:text-(--color-sidebar-primary-foreground)
523
+ `, {
524
+ variants: {
525
+ collapsible: {
526
+ true: "flex-col items-stretch",
527
+ false: "flex-row items-center rounded-md hover:bg-(--color-sidebar-accent)"
528
+ },
529
+ // The item takes its width from the menu, which takes it from the sidebar, so it narrows
530
+ // with the animation instead of snapping to a rail-sized box of its own.
531
+ iconOnly: {
532
+ true: "",
533
+ false: ""
534
+ }
535
+ },
536
+ compoundVariants: [
537
+ // On the rail a collapsible item has nothing left to stack, so it behaves like any other row.
538
+ {
539
+ collapsible: true,
540
+ iconOnly: true,
541
+ class: "flex-row items-center rounded-md hover:bg-(--color-sidebar-accent)"
542
+ }
543
+ ]
544
+ });
545
+ /**
546
+ * The menu row's own appearance.
547
+ *
548
+ * It used to compose `ButtonDirective`, which owns a `[class]` binding of its own; every sidebar rule
549
+ * that had to beat it needed `!important`, and the composition ruled anchors out entirely. Owning the
550
+ * class here costs a handful of declarations and removes both problems.
551
+ *
552
+ * `shrink-0` on the leading visual is what stops a long label crushing it. It applies to the first
553
+ * child whatever it is — an icon, an avatar — and to any icon elsewhere in the row.
554
+ */
555
+ const sidebarMenuButtonThemeVariants = cva(`
556
+ flex w-full items-center justify-start overflow-hidden
557
+ rounded-md border-0 bg-transparent
558
+ text-start text-sm font-normal whitespace-nowrap no-underline
559
+ outline-none
560
+ focus-visible:ring-2 focus-visible:ring-(--color-sidebar-ring)/60
561
+ [&>*:first-child]:shrink-0 [&>svg]:shrink-0
562
+ [transition:gap_var(--mona-motion-standard)_ease-out,height_var(--mona-motion-standard)_ease-out,padding_var(--mona-motion-standard)_ease-out,background-color_100ms_ease-in-out,color_100ms_ease-in-out]
563
+ motion-reduce:[transition:none]
564
+ `, {
565
+ variants: {
566
+ active: {
567
+ true: "text-(--color-sidebar-primary-foreground)",
568
+ false: `
569
+ text-(--color-sidebar-foreground)
570
+ hover:bg-(--color-sidebar-accent) hover:text-(--color-sidebar-accent-foreground)
571
+ `
572
+ },
573
+ disabled: {
574
+ true: "opacity-50 pointer-events-none cursor-default",
575
+ false: "cursor-pointer"
576
+ },
577
+ iconOnly: {
578
+ true: "shrink-0 gap-8 h-8",
579
+ false: "gap-2 p-1"
580
+ },
581
+ size: {
582
+ medium: "",
583
+ large: ""
584
+ }
585
+ },
586
+ compoundVariants: [
587
+ { iconOnly: false, size: "medium", class: "h-8" },
588
+ { iconOnly: false, size: "large", class: "h-12" },
589
+ { iconOnly: true, size: "medium", class: "p-2" },
590
+ { iconOnly: true, size: "large", class: "p-0" }
591
+ ]
592
+ });
593
+ const sidebarMenuSubThemeVariants = cva(`
594
+ flex flex-col w-full space-y-1
595
+ ms-3.5 ps-2.5
596
+ border-s border-(--color-sidebar-border)
597
+ `);
598
+ /**
599
+ * The menu carries no inset of its own. Its enclosing region — a group, header or footer — owns the
600
+ * one inset, and holds it steady in both states, which is what makes the icon rail's arithmetic work
601
+ * out: a `3rem` rail less that region's `p-2` on each side leaves exactly one `2rem` icon square.
602
+ *
603
+ * It also means a menu placed in a region that is already inset needs no override. An override was
604
+ * the only reason a consumer ever reached for `!important` here, because `px-*` and the logical
605
+ * `ps-*`/`pe-*` an inset would have used are separate groups to `tailwind-merge`: it keeps both, and
606
+ * which one lands is left to stylesheet order.
607
+ */
608
+ const sidebarMenuThemeVariants = cva(`
609
+ flex flex-col w-full space-y-1
610
+ `);
611
+ const sidebarThemeVariants = cva(`
612
+ relative flex flex-col shrink-0
613
+ bg-(--color-sidebar) text-(--color-sidebar-foreground) overflow-hidden
614
+ transition-[width] duration-(--mona-motion-standard) ease-out
615
+ motion-reduce:transition-none
616
+ `, {
617
+ variants: {
618
+ // `side` only decides placement; the border it implies is applied per variant below,
619
+ // so the `floating` and `inset` treatments are not left fighting a single edge border.
620
+ // Flex order is already direction-relative, which is why the borders below have to be
621
+ // logical too — a physical `border-r` would land on the wrong edge under RTL.
622
+ side: {
623
+ start: "order-first",
624
+ end: "order-last"
625
+ },
626
+ variant: {
627
+ sidebar: `h-full ${themeRaisedBackdropClasses}`,
628
+ // Any change to the borders below must be matched in `sidebarBorderAllowance` above.
629
+ floating: `
630
+ m-2 h-[calc(100%-1rem)] rounded-lg
631
+ border border-(--color-sidebar-border) shadow-(--shadow-raised)
632
+ ${themeRaisedBackdropClasses}
633
+ `,
634
+ inset: "m-2 h-[calc(100%-1rem)] bg-transparent"
635
+ },
636
+ // Once an offcanvas sidebar is fully collapsed any margin would leave a visible gutter, and
637
+ // the border would leave a 1px sliver of surface down the edge of a panel that is supposed
638
+ // to be gone entirely.
639
+ flush: {
640
+ true: "m-0 border-0",
641
+ false: ""
642
+ },
643
+ /**
644
+ * The compact presentation. The panel leaves the flex flow and becomes an overlay pinned to
645
+ * one edge, sliding in on `translate` rather than `width` — a drawer that animated its width
646
+ * would reflow its own contents for the whole transition.
647
+ */
648
+ drawer: {
649
+ true: `
650
+ absolute inset-y-0 z-50 h-full m-0
651
+ shadow-(--shadow-overlay)
652
+ transition-[translate] duration-(--mona-motion-standard) ease-out
653
+ motion-reduce:transition-none
654
+ `,
655
+ false: ""
656
+ },
657
+ open: {
658
+ true: "",
659
+ false: ""
660
+ }
661
+ },
662
+ compoundVariants: [
663
+ { drawer: false, variant: "sidebar", side: "start", class: "border-e border-(--color-sidebar-border)" },
664
+ { drawer: false, variant: "sidebar", side: "end", class: "border-s border-(--color-sidebar-border)" },
665
+ { drawer: false, variant: "floating", flush: true, class: "h-full" },
666
+ { drawer: false, variant: "inset", flush: true, class: "h-full" },
667
+ // A drawer is a surface in its own right whatever the docked variant looks like, so the
668
+ // variant's margins, rounding and transparency are all dropped for it.
669
+ { drawer: true, side: "start", class: "start-0" },
670
+ { drawer: true, side: "end", class: "end-0" },
671
+ { drawer: true, open: false, side: "start", class: "-translate-x-full" },
672
+ { drawer: true, open: false, side: "end", class: "translate-x-full" },
673
+ { drawer: true, variant: "floating", class: "rounded-none" },
674
+ { drawer: true, variant: "inset", class: "bg-(--color-sidebar)" }
675
+ ]
676
+ });
677
+ /**
678
+ * The scrim behind an open drawer. Fades rather than sliding, so it is not competing with the panel's
679
+ * own movement, and stays below it in the stacking order.
680
+ */
681
+ const sidebarBackdropThemeVariants = cva(`
682
+ absolute inset-0 z-40
683
+ bg-black/50
684
+ transition-opacity duration-(--mona-motion-standard) ease-out
685
+ motion-reduce:transition-none
686
+ `, {
687
+ variants: {
688
+ open: {
689
+ true: "opacity-100",
690
+ false: "opacity-0 pointer-events-none"
691
+ }
692
+ }
693
+ });
694
+ const sidebarMenuBadgeThemeVariants = cva(`
695
+ ms-auto flex items-center justify-center shrink-0
696
+ min-w-5 h-5 px-1.5
697
+ rounded-md text-xs font-medium tabular-nums
698
+ bg-(--color-sidebar-accent) text-(--color-sidebar-accent-foreground)
699
+ pointer-events-none select-none
700
+ `, {
701
+ variants: {
702
+ hidden: {
703
+ true: "hidden!",
704
+ false: ""
705
+ }
706
+ }
707
+ });
708
+ const sidebarRailThemeVariants = cva(`
709
+ absolute inset-y-0 z-20 hidden w-1
710
+ cursor-pointer
711
+ transition-colors hover:bg-(--color-sidebar-border)
712
+ focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-(--color-sidebar-ring)
713
+ sm:flex
714
+ `, {
715
+ variants: {
716
+ // The rail sits on the sidebar's inner edge, which is the opposite one to the side it is
717
+ // docked against. Logical, so it stays on the inner edge under RTL.
718
+ side: {
719
+ start: "end-0",
720
+ end: "start-0"
721
+ }
722
+ }
723
+ });
724
+ const sidebarSeparatorThemeVariants = cva(`
725
+ shrink-0 h-px bg-(--color-sidebar-border)
726
+ transition-[margin] duration-(--mona-motion-standard) ease-out
727
+ motion-reduce:transition-none
728
+ `, {
729
+ variants: {
730
+ iconOnly: {
731
+ true: "mx-1",
732
+ false: "mx-2"
733
+ }
734
+ }
735
+ });
736
+
737
+ /**
738
+ * @description
739
+ * The sidebar region itself. Content is projected in author order, so header, content and footer
740
+ * parts appear exactly where they are written.
741
+ *
742
+ * Below the layout's `mobileBreakpoint` it presents as a modal overlay drawer: focus is trapped
743
+ * inside it while open, restored to whatever opened it on close, and the rest of the layout is made
744
+ * inert. It is not a navigation landmark by itself — wrap the menus in a labelled `nav`, or set
745
+ * `role` and `ariaLabel` if the whole region is navigation.
746
+ */
747
+ class SidebarComponent {
748
+ #focusTrap;
749
+ #host;
750
+ #injector;
751
+ #layoutService;
752
+ #sidebarService;
753
+ #restoreFocusTo;
754
+ constructor() {
755
+ this.#focusTrap = inject(CdkTrapFocus);
756
+ this.#host = inject(ElementRef);
757
+ this.#injector = inject(Injector);
758
+ this.#layoutService = inject(SidebarLayoutService, { optional: true });
759
+ this.#sidebarService = inject(SidebarService);
760
+ this.#restoreFocusTo = null;
761
+ this.baseClass = computed(() => {
762
+ const variantClass = sidebarThemeVariants({
763
+ drawer: this.drawer(),
764
+ open: this.#sidebarService.mobileOpen(),
765
+ side: this.#sidebarService.side(),
766
+ variant: this.variant(),
767
+ flush: this.offCanvasClosed()
768
+ });
769
+ return twMerge(variantClass, this.userClass());
770
+ }, /* @ts-ignore */
771
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
772
+ /** The compact presentation: an overlay pinned to one edge rather than a column in the flow. */
773
+ this.drawer = this.#sidebarService.compact;
774
+ /** Published rather than the raw input, so a `left`/`right` alias still reports as `start`/`end`. */
775
+ this.logicalSide = this.#sidebarService.side;
776
+ /**
777
+ * Whether the panel is out of view entirely. The icon rail does not count — it is still on screen
778
+ * and still operable — but a closed drawer and a fully collapsed off-canvas sidebar both do.
779
+ */
780
+ this.hidden = computed(() => {
781
+ if (this.drawer()) {
782
+ return !this.#sidebarService.mobileOpen();
783
+ }
784
+ return this.offCanvasClosed();
785
+ }, /* @ts-ignore */
786
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
787
+ this.offCanvasClosed = computed(() => !this.drawer() && !this.#sidebarService.expanded() && this.collapsible() === "offcanvas", /* @ts-ignore */
788
+ ...(ngDevMode ? [{ debugName: "offCanvasClosed" }] : /* istanbul ignore next */ []));
789
+ /** A drawer is a modal surface; a docked sidebar keeps whatever role the consumer gave it. */
790
+ this.resolvedRole = computed(() => (this.drawer() ? "dialog" : this.role() || null), /* @ts-ignore */
791
+ ...(ngDevMode ? [{ debugName: "resolvedRole" }] : /* istanbul ignore next */ []));
792
+ this.sidebarId = this.#sidebarService.sidebarId;
793
+ this.state = this.#sidebarService.state;
794
+ /**
795
+ * `width` and `iconWidth` are the width the sidebar's *contents* get, because that is what the
796
+ * parts inside are measured against — an icon rail less a region's padding is meant to leave
797
+ * exactly one icon square. The variant's border is painted inside the box under the global
798
+ * `border-box`, so it is added back on here rather than being taken out of that measurement.
799
+ */
800
+ this.widthString = computed(() => {
801
+ // A drawer is always at full width; it is moved out of view rather than shrunk.
802
+ if (this.drawer()) {
803
+ return this.#withBorderAllowance(toCssLength(this.mobileWidth()));
804
+ }
805
+ if (this.#sidebarService.expanded()) {
806
+ return this.#withBorderAllowance(toCssLength(this.width()));
807
+ }
808
+ // A fully collapsed sidebar is the exception: it has no contents to make room for.
809
+ return this.collapsible() === "icon" ? this.#withBorderAllowance(toCssLength(this.iconWidth())) : "0px";
810
+ }, /* @ts-ignore */
811
+ ...(ngDevMode ? [{ debugName: "widthString" }] : /* istanbul ignore next */ []));
812
+ /**
813
+ * @description Accessible name for the sidebar region. Required when `role` is set, and used as the
814
+ * drawer's name on compact viewports.
815
+ * @default ""
816
+ */
817
+ this.ariaLabel = input("", { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: "aria-label" });
818
+ /**
819
+ * @description How the sidebar behaves when collapsed. `"icon"` keeps a narrow rail of icons visible,
820
+ * `"offcanvas"` removes it from the layout entirely, and `"none"` disables collapsing altogether.
821
+ * @default "offcanvas"
822
+ */
823
+ this.collapsible = input("offcanvas", /* @ts-ignore */
824
+ ...(ngDevMode ? [{ debugName: "collapsible" }] : /* istanbul ignore next */ []));
825
+ /**
826
+ * @description Sets whether this sidebar is open. Supports two-way binding. While the viewport is
827
+ * compact this reflects the drawer, so one binding drives both presentations.
828
+ * @default true
829
+ */
830
+ this.expanded = model(true, /* @ts-ignore */
831
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
832
+ /**
833
+ * @description Width of the icon rail while collapsed. Only applies when `collapsible` is `"icon"`.
834
+ * Numbers are treated as pixels.
835
+ * @default "3rem"
836
+ */
837
+ this.iconWidth = input("3rem", /* @ts-ignore */
838
+ ...(ngDevMode ? [{ debugName: "iconWidth" }] : /* istanbul ignore next */ []));
839
+ /**
840
+ * @description Id for the sidebar element, referenced by triggers through `aria-controls`. Supply a
841
+ * stable value when the markup is server rendered, so the client does not hydrate onto a different
842
+ * generated id. Falls back to a generated id.
843
+ * @default ""
844
+ */
845
+ this.id = input("", /* @ts-ignore */
846
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
847
+ /**
848
+ * @description Width of the overlay drawer on compact viewports. Numbers are treated as pixels.
849
+ * @default "18rem"
850
+ */
851
+ this.mobileWidth = input("18rem", /* @ts-ignore */
852
+ ...(ngDevMode ? [{ debugName: "mobileWidth" }] : /* istanbul ignore next */ []));
853
+ /**
854
+ * @description Remembers whether this sidebar was left open, under this key. Unset means nothing is
855
+ * stored — no key, no storage written.
856
+ *
857
+ * Only the docked state is kept. A drawer is never restored open: on the viewport that makes one,
858
+ * it would cover the page on load. A stored value wins over `expanded`, and is pushed back out
859
+ * through that binding, so do not set a key on a sidebar whose state is already restored elsewhere.
860
+ *
861
+ * Where it is kept is `SIDEBAR_STORAGE`, which defaults to `localStorage`. A server-rendered
862
+ * application cannot read that until hydration and so flashes the default first; supply storage the
863
+ * server can read through `provideSidebarStorage()`.
864
+ * @default ""
865
+ */
866
+ this.persistKey = input("", /* @ts-ignore */
867
+ ...(ngDevMode ? [{ debugName: "persistKey" }] : /* istanbul ignore next */ []));
868
+ /**
869
+ * @description ARIA role for the sidebar region. Leave unset and wrap the menus in a labelled `nav`
870
+ * when the sidebar holds more than navigation; set `"navigation"` with an `aria-label` when it does
871
+ * not. Ignored on compact viewports, where the drawer is a `dialog`.
872
+ * @default ""
873
+ */
874
+ this.role = input("", /* @ts-ignore */
875
+ ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
876
+ /**
877
+ * @description Which edge the sidebar sits on. Prefer `"start"` and `"end"`, which follow the
878
+ * document's writing direction; `"left"` and `"right"` are aliases of them.
879
+ * @default "start"
880
+ */
881
+ this.side = input("start", /* @ts-ignore */
882
+ ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
883
+ /**
884
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
885
+ * @default ""
886
+ */
887
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
888
+ transform: value => classInputToClass(value) });
889
+ /**
890
+ * @description The visual treatment of the sidebar. `"floating"` detaches it from the edge on its own
891
+ * surface, and `"inset"` moves that raised surface onto the region beside it instead.
892
+ * @default "sidebar"
893
+ */
894
+ this.variant = input("sidebar", /* @ts-ignore */
895
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
896
+ /**
897
+ * @description Width of the sidebar while expanded. Numbers are treated as pixels.
898
+ * @default "16rem"
899
+ */
900
+ this.width = input("16rem", /* @ts-ignore */
901
+ ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
902
+ // These are authored here but read by descendants, so they have to reach the shared service.
903
+ effect(() => {
904
+ const collapsible = this.collapsible();
905
+ const id = this.id();
906
+ const persistKey = this.persistKey();
907
+ const side = this.side();
908
+ const variant = this.variant();
909
+ untracked(() => {
910
+ this.#sidebarService.setCollapsible(collapsible);
911
+ this.#sidebarService.setId(id);
912
+ this.#sidebarService.setPersistKey(persistKey);
913
+ this.#sidebarService.setSide(side);
914
+ this.#sidebarService.setVariant(variant);
915
+ });
916
+ });
917
+ effect(() => {
918
+ const expanded = this.expanded();
919
+ untracked(() => this.#sidebarService.setExpanded(expanded));
920
+ });
921
+ effect(() => {
922
+ const expanded = this.#sidebarService.expanded();
923
+ untracked(() => this.expanded.set(expanded));
924
+ });
925
+ // Keyed on the resolved id rather than the input, so a sidebar left to generate its own id is
926
+ // still reachable by a trigger. The effect's cleanup covers both halves of a changed id — the
927
+ // old key is dropped before the new one is added — as well as teardown.
928
+ effect(onCleanup => {
929
+ const layoutService = this.#layoutService;
930
+ if (!layoutService) {
931
+ return;
932
+ }
933
+ const id = this.#sidebarService.sidebarId();
934
+ onCleanup(layoutService.register(id, this.#sidebarService.controller));
935
+ });
936
+ effect(() => {
937
+ const trapped = this.drawer() && this.#sidebarService.mobileOpen();
938
+ untracked(() => this.#setFocusTrapped(trapped));
939
+ });
940
+ }
941
+ /**
942
+ * The trap is only armed for the drawer. A docked sidebar sits in the page beside its content, and
943
+ * holding focus inside it would leave the keyboard unable to reach the rest of the layout at all.
944
+ */
945
+ #setFocusTrapped(trapped) {
946
+ this.#focusTrap.enabled = trapped;
947
+ if (trapped) {
948
+ const active = document.activeElement;
949
+ this.#restoreFocusTo = active instanceof HTMLElement ? active : null;
950
+ this.#focusTrap.focusTrap.focusInitialElementWhenReady();
951
+ return;
952
+ }
953
+ const restoreTo = this.#restoreFocusTo;
954
+ this.#restoreFocusTo = null;
955
+ // Only pull focus back if it is still inside the panel that is closing. Moving it otherwise
956
+ // would yank the caret away from wherever the user has since gone.
957
+ if (!restoreTo || !this.#host.nativeElement.contains(document.activeElement)) {
958
+ return;
959
+ }
960
+ /*
961
+ * Deferred to the next render, not called here. Whatever opened the drawer is almost always
962
+ * behind it, and the region behind it is still `inert` at this point in the change detection
963
+ * pass — `focus()` on an element inside an inert subtree fails silently, which left focus
964
+ * stranded inside the panel that was about to become inert itself.
965
+ */
966
+ afterNextRender(() => {
967
+ if (restoreTo.isConnected) {
968
+ restoreTo.focus();
969
+ }
970
+ }, { injector: this.#injector });
971
+ }
972
+ #withBorderAllowance(width) {
973
+ const allowance = sidebarBorderAllowance[this.variant()];
974
+ // The `inset` variant draws no border, so it is left as an ordinary length.
975
+ return allowance === "0px" ? width : `calc(${width} + ${allowance})`;
976
+ }
977
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
978
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.6", type: SidebarComponent, isStandalone: true, selector: "mona-sidebar", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, iconWidth: { classPropertyName: "iconWidth", publicName: "iconWidth", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, mobileWidth: { classPropertyName: "mobileWidth", publicName: "mobileWidth", isSignal: true, isRequired: false, transformFunction: null }, persistKey: { classPropertyName: "persistKey", publicName: "persistKey", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, host: { properties: { "attr.aria-hidden": "hidden() ? 'true' : null", "attr.aria-label": "ariaLabel() || null", "attr.aria-modal": "drawer() ? 'true' : null", "attr.data-collapsible": "collapsible()", "attr.data-side": "logicalSide()", "attr.data-state": "state()", "attr.data-variant": "variant()", "attr.id": "sidebarId()", "attr.inert": "hidden() ? '' : null", "attr.role": "resolvedRole()", "class": "baseClass()", "style.width": "widthString()" } }, providers: [SidebarService], hostDirectives: [{ directive: i1.CdkTrapFocus }], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
979
+ }
980
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarComponent, decorators: [{
981
+ type: Component,
982
+ args: [{
983
+ selector: "mona-sidebar",
984
+ template: `<ng-content></ng-content>`,
985
+ changeDetection: ChangeDetectionStrategy.OnPush,
986
+ hostDirectives: [CdkTrapFocus],
987
+ host: {
988
+ "[attr.aria-hidden]": "hidden() ? 'true' : null",
989
+ "[attr.aria-label]": "ariaLabel() || null",
990
+ "[attr.aria-modal]": "drawer() ? 'true' : null",
991
+ "[attr.data-collapsible]": "collapsible()",
992
+ "[attr.data-side]": "logicalSide()",
993
+ "[attr.data-state]": "state()",
994
+ "[attr.data-variant]": "variant()",
995
+ "[attr.id]": "sidebarId()",
996
+ // A panel that is out of view must be out of the tab order and out of the accessibility tree
997
+ // with it. `inert` is the one attribute that does both; width alone left every control in the
998
+ // collapsed panel reachable by keyboard and readable by assistive technology.
999
+ "[attr.inert]": "hidden() ? '' : null",
1000
+ "[attr.role]": "resolvedRole()",
1001
+ "[class]": "baseClass()",
1002
+ "[style.width]": "widthString()"
1003
+ },
1004
+ // One service per sidebar, not per layout, so a layout can hold more than one of these and each
1005
+ // keeps its own side, width and open state. Descendants resolve the nearest one, which is theirs.
1006
+ providers: [SidebarService]
1007
+ }]
1008
+ }], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], iconWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconWidth", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], mobileWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileWidth", required: false }] }], persistKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "persistKey", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }] } });
1009
+ function toCssLength(value) {
1010
+ return typeof value === "number" ? `${value}px` : value;
1011
+ }
1012
+
1013
+ /**
1014
+ * @description
1015
+ * Lays its children out in a row and owns what they share. Project one or more `mona-sidebar`
1016
+ * elements and an element marked with `monaSidebarInset` into it. Each sidebar keeps its own open
1017
+ * state, on its own `[(expanded)]` binding, so a layout can hold a navigation column on one edge and
1018
+ * an inspector on the other.
1019
+ *
1020
+ * Below `mobileBreakpoint` sidebars present as overlay drawers instead of columns. A drawer has its
1021
+ * own open state, so a sidebar collapsed on a desktop does not come back open on a phone, and it is
1022
+ * reset whenever the viewport crosses the breakpoint. Only one drawer is open at a time: opening one
1023
+ * closes any other, and they share the single backdrop this layout paints behind them.
1024
+ */
1025
+ class SidebarLayoutComponent {
1026
+ #layoutService;
1027
+ #matches;
1028
+ constructor() {
1029
+ this.#layoutService = inject(SidebarLayoutService);
1030
+ this.#matches = signal(false, /* @ts-ignore */
1031
+ ...(ngDevMode ? [{ debugName: "#matches" }] : /* istanbul ignore next */ []));
1032
+ this.backdropClass = computed(() => sidebarBackdropThemeVariants({ open: this.mobileOpen() }), /* @ts-ignore */
1033
+ ...(ngDevMode ? [{ debugName: "backdropClass" }] : /* istanbul ignore next */ []));
1034
+ this.baseClass = computed(() => twMerge(sidebarLayoutBaseThemeVariants(), this.userClass()), /* @ts-ignore */
1035
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1036
+ this.compact = this.#layoutService.compact;
1037
+ /** One backdrop serves every sidebar, because only one of their drawers is ever open. */
1038
+ this.mobileOpen = this.#layoutService.anyMobileOpen;
1039
+ /**
1040
+ * @description Viewport width, in pixels, below which the sidebar presents as an overlay drawer.
1041
+ * Set to `0` to keep the docked presentation at every size.
1042
+ * @default 768
1043
+ */
1044
+ this.mobileBreakpoint = input(768, /* @ts-ignore */
1045
+ ...(ngDevMode ? [{ debugName: "mobileBreakpoint" }] : /* istanbul ignore next */ []));
1046
+ /**
1047
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1048
+ * @default ""
1049
+ */
1050
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1051
+ transform: value => classInputToClass(value) });
1052
+ this.#watchBreakpoint();
1053
+ }
1054
+ onBackdropClick() {
1055
+ this.#layoutService.closeAll();
1056
+ }
1057
+ onEscape(event) {
1058
+ // Only a drawer is dismissible. A docked sidebar is part of the page, and closing it on
1059
+ // Escape would take the key from anything inside it that wanted to handle it.
1060
+ if (!this.#layoutService.anyMobileOpen()) {
1061
+ return;
1062
+ }
1063
+ event.preventDefault();
1064
+ this.#layoutService.closeAll();
1065
+ }
1066
+ /**
1067
+ * Kept on `matchMedia` rather than a resize listener, so the browser decides when the threshold is
1068
+ * crossed and the query is rebuilt only when the breakpoint input itself changes.
1069
+ */
1070
+ #watchBreakpoint() {
1071
+ const destroyRef = inject(DestroyRef);
1072
+ effect(() => {
1073
+ const compact = this.#matches();
1074
+ untracked(() => this.#layoutService.setCompact(compact));
1075
+ });
1076
+ // Server-rendered and jsdom hosts have no media queries; both keep the docked presentation,
1077
+ // which is the one that degrades gracefully without JavaScript.
1078
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1079
+ return;
1080
+ }
1081
+ let current = null;
1082
+ const onChange = (event) => this.#matches.set(event.matches);
1083
+ const detach = () => current?.removeEventListener("change", onChange);
1084
+ destroyRef.onDestroy(detach);
1085
+ effect(() => {
1086
+ const breakpoint = this.mobileBreakpoint();
1087
+ untracked(() => {
1088
+ detach();
1089
+ if (breakpoint <= 0) {
1090
+ current = null;
1091
+ this.#matches.set(false);
1092
+ return;
1093
+ }
1094
+ current = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
1095
+ current.addEventListener("change", onChange);
1096
+ this.#matches.set(current.matches);
1097
+ });
1098
+ });
1099
+ }
1100
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1101
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SidebarLayoutComponent, isStandalone: true, selector: "mona-sidebar-layout", inputs: { mobileBreakpoint: { classPropertyName: "mobileBreakpoint", publicName: "mobileBreakpoint", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown.escape": "onEscape($event)" }, properties: { "attr.data-compact": "compact() ? 'true' : null", "class": "baseClass()" } }, providers: [SidebarLayoutService], ngImport: i0, template: `
1102
+ <ng-content></ng-content>
1103
+ @if (compact()) {
1104
+ <div
1105
+ [class]="backdropClass()"
1106
+ [attr.aria-hidden]="'true'"
1107
+ [attr.data-state]="mobileOpen() ? 'open' : 'closed'"
1108
+ (click)="onBackdropClick()"></div>
1109
+ }
1110
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1111
+ }
1112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarLayoutComponent, decorators: [{
1113
+ type: Component,
1114
+ args: [{
1115
+ selector: "mona-sidebar-layout",
1116
+ template: `
1117
+ <ng-content></ng-content>
1118
+ @if (compact()) {
1119
+ <div
1120
+ [class]="backdropClass()"
1121
+ [attr.aria-hidden]="'true'"
1122
+ [attr.data-state]="mobileOpen() ? 'open' : 'closed'"
1123
+ (click)="onBackdropClick()"></div>
1124
+ }
1125
+ `,
1126
+ changeDetection: ChangeDetectionStrategy.OnPush,
1127
+ host: {
1128
+ "[attr.data-compact]": "compact() ? 'true' : null",
1129
+ "[class]": "baseClass()",
1130
+ // Escape closes the drawer from anywhere inside the layout, including from within the
1131
+ // trapped focus inside the drawer itself.
1132
+ "(keydown.escape)": "onEscape($event)"
1133
+ },
1134
+ providers: [SidebarLayoutService]
1135
+ }]
1136
+ }], ctorParameters: () => [], propDecorators: { mobileBreakpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileBreakpoint", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1137
+
1138
+ /**
1139
+ * @description
1140
+ * A loading placeholder shaped like a menu item. Drops its label bar on the icon rail, where the
1141
+ * real labels are not visible either.
1142
+ */
1143
+ class SidebarMenuSkeletonComponent {
1144
+ constructor() {
1145
+ this.#sidebarService = inject(SidebarService, { optional: true });
1146
+ // Laid out exactly like a real menu button, so the two animate as one: the same inset centres the
1147
+ // icon square on the rail, and the same widened gap carries the label bar out of the clipped box.
1148
+ this.baseClass = computed(() => sidebarMenuSkeletonThemeVariants({ iconOnly: this.iconOnly() }), /* @ts-ignore */
1149
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1150
+ this.iconOnly = computed(() => this.#sidebarService?.iconOnly() ?? false, /* @ts-ignore */
1151
+ ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
1152
+ /**
1153
+ * @description Width of the label bar. Varying it across a list keeps the placeholder from looking
1154
+ * like a rigid grid. Numbers are treated as pixels.
1155
+ * @default "60%"
1156
+ */
1157
+ this.labelWidth = input("60%", /* @ts-ignore */
1158
+ ...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
1159
+ /**
1160
+ * @description Renders a square standing in for the item's icon.
1161
+ * @default true
1162
+ */
1163
+ this.showIcon = input(true, /* @ts-ignore */
1164
+ ...(ngDevMode ? [{ debugName: "showIcon" }] : /* istanbul ignore next */ []));
1165
+ }
1166
+ #sidebarService;
1167
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1168
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SidebarMenuSkeletonComponent, isStandalone: true, selector: "mona-sidebar-menu-skeleton", inputs: { labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0, template: `
1169
+ @if (showIcon()) {
1170
+ <mona-skeleton width="1rem" height="1rem" rounded="small"></mona-skeleton>
1171
+ }
1172
+ <mona-skeleton [width]="labelWidth()" height="1rem" rounded="small"></mona-skeleton>
1173
+ `, isInline: true, dependencies: [{ kind: "component", type: SkeletonComponent, selector: "mona-skeleton", inputs: ["height", "rounded", "class", "width"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1174
+ }
1175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuSkeletonComponent, decorators: [{
1176
+ type: Component,
1177
+ args: [{
1178
+ selector: "mona-sidebar-menu-skeleton",
1179
+ template: `
1180
+ @if (showIcon()) {
1181
+ <mona-skeleton width="1rem" height="1rem" rounded="small"></mona-skeleton>
1182
+ }
1183
+ <mona-skeleton [width]="labelWidth()" height="1rem" rounded="small"></mona-skeleton>
1184
+ `,
1185
+ changeDetection: ChangeDetectionStrategy.OnPush,
1186
+ imports: [SkeletonComponent],
1187
+ host: {
1188
+ "[class]": "baseClass()"
1189
+ }
1190
+ }]
1191
+ }], propDecorators: { labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }] } });
1192
+
1193
+ class SidebarContentDirective {
1194
+ constructor() {
1195
+ this.#sidebarService = inject(SidebarService, { optional: true });
1196
+ /**
1197
+ * A scrollbar takes its width out of the content box, and the icon rail has none to give: at a
1198
+ * `3rem` rail it claims about a third of it and squeezes every icon in the region. There is nothing
1199
+ * to read on the rail anyway — the labels are gone — so the overflow is simply clipped there.
1200
+ */
1201
+ this.baseClass = computed(() => sidebarContentThemeVariants({ iconOnly: this.#sidebarService?.iconOnly() ?? false }), /* @ts-ignore */
1202
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1203
+ }
1204
+ #sidebarService;
1205
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1206
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarContentDirective, isStandalone: true, selector: "[monaSidebarContent]", host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1207
+ }
1208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarContentDirective, decorators: [{
1209
+ type: Directive,
1210
+ args: [{
1211
+ selector: "[monaSidebarContent]",
1212
+ host: {
1213
+ "[class]": "baseClass()"
1214
+ }
1215
+ }]
1216
+ }] });
1217
+
1218
+ /**
1219
+ * @description
1220
+ * The region at the bottom of the sidebar, below its content. Holds what stays put while the content
1221
+ * scrolls — a profile menu, a sign-out.
1222
+ */
1223
+ class SidebarFooterDirective {
1224
+ constructor() {
1225
+ this.baseClass = computed(() => twMerge(sidebarFooterThemeVariants(), this.userClass()), /* @ts-ignore */
1226
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1227
+ /**
1228
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1229
+ * @default ""
1230
+ */
1231
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1232
+ transform: value => classInputToClass(value) });
1233
+ }
1234
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1235
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarFooterDirective, isStandalone: true, selector: "[monaSidebarFooter]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1236
+ }
1237
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarFooterDirective, decorators: [{
1238
+ type: Directive,
1239
+ args: [{
1240
+ selector: "[monaSidebarFooter]",
1241
+ host: {
1242
+ "[class]": "baseClass()"
1243
+ }
1244
+ }]
1245
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1246
+
1247
+ /**
1248
+ * @description
1249
+ * A titled section of the sidebar, grouping a label and its menu. Stack as many as the sidebar
1250
+ * needs; each is an ordinary block, so a `monaSidebarSeparator` between them is optional.
1251
+ */
1252
+ class SidebarGroupDirective {
1253
+ constructor() {
1254
+ // The group owns the inset for everything it contains, and keeps it the same in both states.
1255
+ // A `3rem` icon rail less this padding leaves exactly the `2rem` square a menu button becomes.
1256
+ this.baseClass = sidebarGroupThemeVariants();
1257
+ }
1258
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1259
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarGroupDirective, isStandalone: true, selector: "[monaSidebarGroup]", host: { properties: { "class": "baseClass" } }, ngImport: i0 }); }
1260
+ }
1261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupDirective, decorators: [{
1262
+ type: Directive,
1263
+ args: [{
1264
+ selector: "[monaSidebarGroup]",
1265
+ host: {
1266
+ "[class]": "baseClass"
1267
+ }
1268
+ }]
1269
+ }] });
1270
+
1271
+ /**
1272
+ * @description
1273
+ * A control in the corner of a group header — an add or overflow button. Stands down on the icon
1274
+ * rail, where there is no header row left to sit in. Give it an `aria-label`: it is icon-only.
1275
+ */
1276
+ class SidebarGroupActionDirective {
1277
+ constructor() {
1278
+ this.#sidebarService = inject(SidebarService, { optional: true });
1279
+ // The group header it belongs to has no room on the rail.
1280
+ this.hidden = computed(() => this.#sidebarService?.iconOnly() ?? false, /* @ts-ignore */
1281
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
1282
+ }
1283
+ #sidebarService;
1284
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1285
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarGroupActionDirective, isStandalone: true, selector: "button[monaSidebarGroupAction]", host: { properties: { "attr.data-hidden": "hidden() ? 'true' : null" }, classAttribute: "\n p-1 w-auto h-auto\n visible opacity-100\n transition-[opacity,visibility] duration-(--mona-motion-standard) ease-out\n motion-reduce:transition-none\n data-[hidden=true]:invisible data-[hidden=true]:opacity-0\n" }, ngImport: i0 }); }
1286
+ }
1287
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupActionDirective, decorators: [{
1288
+ type: Directive,
1289
+ args: [{
1290
+ selector: "button[monaSidebarGroupAction]",
1291
+ host: {
1292
+ // Consumers commonly add `monaButton` to this element, which owns the `[class]` binding.
1293
+ // A data attribute lets the static sidebar recipe respond without competing with it.
1294
+ // Faded rather than removed: the header around it closes by height over the same interval, so
1295
+ // dropping it outright would empty the row before it had finished shrinking. `visibility`
1296
+ // transitions discretely, flipping only once the fade is done, and takes it out of tab order.
1297
+ "[attr.data-hidden]": "hidden() ? 'true' : null",
1298
+ class: sidebarGroupActionClasses
1299
+ }
1300
+ }]
1301
+ }] });
1302
+
1303
+ /**
1304
+ * @description
1305
+ * The body of a `monaSidebarGroup`, below its header. Wraps the menu itself.
1306
+ */
1307
+ class SidebarGroupContentDirective {
1308
+ constructor() {
1309
+ this.baseClass = sidebarGroupContentThemeVariants();
1310
+ }
1311
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1312
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarGroupContentDirective, isStandalone: true, selector: "[monaSidebarGroupContent]", host: { properties: { "class": "baseClass" } }, ngImport: i0 }); }
1313
+ }
1314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupContentDirective, decorators: [{
1315
+ type: Directive,
1316
+ args: [{
1317
+ selector: "[monaSidebarGroupContent]",
1318
+ host: {
1319
+ "[class]": "baseClass"
1320
+ }
1321
+ }]
1322
+ }] });
1323
+
1324
+ /**
1325
+ * @description
1326
+ * The row above a group's content, holding its label and any group action. Collapses to nothing on
1327
+ * the icon rail rather than leaving an empty gap where the label used to be.
1328
+ */
1329
+ class SidebarGroupHeaderDirective {
1330
+ constructor() {
1331
+ this.#sidebarService = inject(SidebarService, { optional: true });
1332
+ this.baseClass = computed(() => sidebarGroupHeaderThemeVariants({ iconOnly: this.#sidebarService?.iconOnly() ?? false }), /* @ts-ignore */
1333
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1334
+ }
1335
+ #sidebarService;
1336
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1337
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarGroupHeaderDirective, isStandalone: true, selector: "[monaSidebarGroupHeader]", host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1338
+ }
1339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupHeaderDirective, decorators: [{
1340
+ type: Directive,
1341
+ args: [{
1342
+ selector: "[monaSidebarGroupHeader]",
1343
+ host: {
1344
+ "[class]": "baseClass()"
1345
+ }
1346
+ }]
1347
+ }] });
1348
+
1349
+ /**
1350
+ * @description
1351
+ * The name of a group. Fades out on the icon rail, where there is no width to read it in.
1352
+ */
1353
+ class SidebarGroupLabelDirective {
1354
+ constructor() {
1355
+ this.#sidebarService = inject(SidebarService, { optional: true });
1356
+ // Faded rather than removed from the layout: the header around it closes by height over the same
1357
+ // interval, so dropping the label outright would empty the row before it had finished shrinking.
1358
+ // `visibility` follows the fade so it leaves the accessibility tree with it.
1359
+ this.baseClass = computed(() => sidebarGroupLabelThemeVariants({ hidden: this.#sidebarService?.iconOnly() ?? false }), /* @ts-ignore */
1360
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1361
+ }
1362
+ #sidebarService;
1363
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1364
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarGroupLabelDirective, isStandalone: true, selector: "[monaSidebarGroupLabel]", host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1365
+ }
1366
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarGroupLabelDirective, decorators: [{
1367
+ type: Directive,
1368
+ args: [{
1369
+ selector: "[monaSidebarGroupLabel]",
1370
+ host: {
1371
+ "[class]": "baseClass()"
1372
+ }
1373
+ }]
1374
+ }] });
1375
+
1376
+ /**
1377
+ * @description
1378
+ * The region at the top of the sidebar, above its content. Holds what stays put while the content
1379
+ * scrolls — a workspace switcher, a search box.
1380
+ */
1381
+ class SidebarHeaderDirective {
1382
+ constructor() {
1383
+ this.baseClass = computed(() => twMerge(sidebarHeaderThemeVariants(), this.userClass()), /* @ts-ignore */
1384
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1385
+ /**
1386
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1387
+ * @default ""
1388
+ */
1389
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1390
+ transform: value => classInputToClass(value) });
1391
+ }
1392
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1393
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarHeaderDirective, isStandalone: true, selector: "[monaSidebarHeader]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1394
+ }
1395
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarHeaderDirective, decorators: [{
1396
+ type: Directive,
1397
+ args: [{
1398
+ selector: "[monaSidebarHeader]",
1399
+ host: {
1400
+ "[class]": "baseClass()"
1401
+ }
1402
+ }]
1403
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1404
+
1405
+ /**
1406
+ * @description
1407
+ * A text input sized for the sidebar, composing `monaTextBox`. Stands down on the icon rail,
1408
+ * where there is no room to type.
1409
+ */
1410
+ class SidebarInputDirective {
1411
+ constructor() {
1412
+ this.#sidebarService = inject(SidebarService, { optional: true });
1413
+ this.baseClass = sidebarInputClasses();
1414
+ this.hidden = computed(() => this.#sidebarService?.iconOnly() ?? false, /* @ts-ignore */
1415
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
1416
+ }
1417
+ #sidebarService;
1418
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1419
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarInputDirective, isStandalone: true, selector: "input[monaSidebarInput]", host: { properties: { "attr.data-hidden": "hidden() ? 'true' : null", "class": "baseClass" } }, hostDirectives: [{ directive: i1$1.TextBoxDirective }], ngImport: i0 }); }
1420
+ }
1421
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarInputDirective, decorators: [{
1422
+ type: Directive,
1423
+ args: [{
1424
+ selector: "input[monaSidebarInput]",
1425
+ hostDirectives: [TextBoxDirective],
1426
+ host: {
1427
+ // `TextBoxDirective` owns `[class]`, so a data attribute drives the static sidebar recipe.
1428
+ "[attr.data-hidden]": "hidden() ? 'true' : null",
1429
+ "[class]": "baseClass"
1430
+ }
1431
+ }]
1432
+ }] });
1433
+
1434
+ /**
1435
+ * @description
1436
+ * Marks the main region beside the sidebar. Fills the remaining width and scrolls independently.
1437
+ * Takes on the raised surface when the sidebar uses the `inset` variant.
1438
+ *
1439
+ * It sits beside the sidebars rather than inside one, so it answers to the layout as a whole: it steps
1440
+ * out of the way for whichever drawer is open, and where a layout holds more than one sidebar it takes
1441
+ * its surface from the first one in author order.
1442
+ */
1443
+ class SidebarInsetDirective {
1444
+ constructor() {
1445
+ this.#layoutService = inject(SidebarLayoutService, { optional: true });
1446
+ this.baseClass = computed(() => {
1447
+ const variantClass = sidebarInsetThemeVariants({
1448
+ behindDrawer: this.behindDrawer(),
1449
+ variant: this.#layoutService?.primaryVariant() ?? "sidebar"
1450
+ });
1451
+ return twMerge(variantClass, this.userClass());
1452
+ }, /* @ts-ignore */
1453
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1454
+ this.behindDrawer = computed(() => this.#layoutService?.anyMobileOpen() ?? false, /* @ts-ignore */
1455
+ ...(ngDevMode ? [{ debugName: "behindDrawer" }] : /* istanbul ignore next */ []));
1456
+ /**
1457
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1458
+ * @default ""
1459
+ */
1460
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1461
+ transform: value => classInputToClass(value) });
1462
+ }
1463
+ #layoutService;
1464
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarInsetDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1465
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarInsetDirective, isStandalone: true, selector: "[monaSidebarInset]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()", "attr.inert": "behindDrawer() ? '' : null" } }, ngImport: i0 }); }
1466
+ }
1467
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarInsetDirective, decorators: [{
1468
+ type: Directive,
1469
+ args: [{
1470
+ selector: "[monaSidebarInset]",
1471
+ host: {
1472
+ "[class]": "baseClass()",
1473
+ // The drawer is modal, so everything behind it leaves the tab order and the accessibility
1474
+ // tree. `inert` covers both, and unlike `aria-hidden` it also stops pointer interaction.
1475
+ "[attr.inert]": "behindDrawer() ? '' : null"
1476
+ // The scoped equivalent of locking body scroll. The layout already clips its own overflow, so
1477
+ // this region is the only thing behind the drawer that scrolls; suppressing it here avoids
1478
+ // reaching out to mutate `document.body`, which a library has no business owning.
1479
+ }
1480
+ }]
1481
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1482
+
1483
+ /**
1484
+ * @description
1485
+ * A trailing control on a menu item — an overflow menu, a pin. Sits inside the row's highlight and
1486
+ * stands down on the icon rail. Give it an `aria-label`: it is icon-only.
1487
+ */
1488
+ class SidebarMenuActionDirective {
1489
+ #button;
1490
+ #sidebarService;
1491
+ constructor() {
1492
+ this.#button = inject(ButtonDirective);
1493
+ this.#sidebarService = inject(SidebarService, { optional: true });
1494
+ this.baseClass = sidebarMenuActionClasses();
1495
+ // A trailing action would crowd out the icon it belongs to on the rail.
1496
+ this.hidden = computed(() => this.#sidebarService?.iconOnly() ?? false, /* @ts-ignore */
1497
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
1498
+ effect(() => this.#button.look.set("ghost"));
1499
+ }
1500
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1501
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarMenuActionDirective, isStandalone: true, selector: "button[monaSidebarMenuAction]", host: { properties: { "attr.data-hidden": "hidden() ? 'true' : null", "class": "baseClass" } }, hostDirectives: [{ directive: i1$2.ButtonDirective }], ngImport: i0 }); }
1502
+ }
1503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuActionDirective, decorators: [{
1504
+ type: Directive,
1505
+ args: [{
1506
+ selector: "button[monaSidebarMenuAction]",
1507
+ hostDirectives: [ButtonDirective],
1508
+ host: {
1509
+ "[attr.data-hidden]": "hidden() ? 'true' : null",
1510
+ "[class]": "baseClass"
1511
+ }
1512
+ }]
1513
+ }], ctorParameters: () => [] });
1514
+
1515
+ /**
1516
+ * @description
1517
+ * A trailing count or status badge on a menu item. Stands down on the icon rail, where it would
1518
+ * crowd out the icon it belongs to.
1519
+ */
1520
+ class SidebarMenuBadgeDirective {
1521
+ constructor() {
1522
+ this.#sidebarService = inject(SidebarService, { optional: true });
1523
+ this.baseClass = computed(() => twMerge(sidebarMenuBadgeThemeVariants({ hidden: this.#sidebarService?.iconOnly() ?? false }), this.userClass()), /* @ts-ignore */
1524
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1525
+ /**
1526
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1527
+ * @default ""
1528
+ */
1529
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1530
+ transform: value => classInputToClass(value) });
1531
+ }
1532
+ #sidebarService;
1533
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuBadgeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1534
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarMenuBadgeDirective, isStandalone: true, selector: "[monaSidebarMenuBadge]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1535
+ }
1536
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuBadgeDirective, decorators: [{
1537
+ type: Directive,
1538
+ args: [{
1539
+ selector: "[monaSidebarMenuBadge]",
1540
+ host: {
1541
+ "[class]": "baseClass()"
1542
+ }
1543
+ }]
1544
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1545
+
1546
+ class SidebarMenuDirective {
1547
+ constructor() {
1548
+ this.baseClass = computed(() => twMerge(sidebarMenuThemeVariants(), this.userClass()), /* @ts-ignore */
1549
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1550
+ /**
1551
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1552
+ * @default ""
1553
+ */
1554
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1555
+ transform: value => classInputToClass(value) });
1556
+ }
1557
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1558
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarMenuDirective, isStandalone: true, selector: "ul[monaSidebarMenu]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1559
+ }
1560
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuDirective, decorators: [{
1561
+ type: Directive,
1562
+ args: [{
1563
+ selector: "ul[monaSidebarMenu]",
1564
+ host: {
1565
+ "[class]": "baseClass()"
1566
+ }
1567
+ }]
1568
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1569
+
1570
+ class SidebarMenuItemDirective {
1571
+ // Applying `monaCollapsible` to the same element turns the item into a disclosure row, so it has to
1572
+ // stack its trigger and submenu instead of laying them out side-by-side.
1573
+ #collapsible;
1574
+ #sidebarService;
1575
+ #expandedBeforeRail;
1576
+ constructor() {
1577
+ // Applying `monaCollapsible` to the same element turns the item into a disclosure row, so it has to
1578
+ // stack its trigger and submenu instead of laying them out side-by-side.
1579
+ this.#collapsible = inject(CollapsibleToken, { optional: true, self: true });
1580
+ this.#sidebarService = inject(SidebarService, { optional: true });
1581
+ this.#expandedBeforeRail = false;
1582
+ this.baseClass = computed(() => {
1583
+ const variantClass = sidebarMenuItemThemeVariants({
1584
+ collapsible: this.#collapsible !== null,
1585
+ iconOnly: this.#sidebarService?.iconOnly() ?? false
1586
+ });
1587
+ return twMerge(variantClass, this.userClass());
1588
+ }, /* @ts-ignore */
1589
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1590
+ /**
1591
+ * @description Marks this row as the current sidebar destination. The row owns the selected
1592
+ * surface so trailing badges and actions remain inside one continuous highlight.
1593
+ * @default false
1594
+ */
1595
+ this.active = input(false, /* @ts-ignore */
1596
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
1597
+ /**
1598
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1599
+ * @default ""
1600
+ */
1601
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1602
+ transform: value => classInputToClass(value) });
1603
+ this.#keepSubmenuClosedOnRail();
1604
+ }
1605
+ /**
1606
+ * A submenu cannot render in a rail one icon wide, and the previous answer — hiding it with
1607
+ * `display: none` — left the trigger still reporting `aria-expanded="true"` for something that was
1608
+ * not there. A screen reader user could open a submenu and be told it was open while nothing
1609
+ * appeared, and tab straight into its items.
1610
+ *
1611
+ * Closing the disclosure for real keeps the trigger's own state truthful and lets the collapsible
1612
+ * content directive apply the `inert` it already applies when closed. The prior state is restored
1613
+ * on the way back out, so collapsing and expanding the sidebar does not quietly discard it.
1614
+ */
1615
+ #keepSubmenuClosedOnRail() {
1616
+ const collapsible = this.#collapsible;
1617
+ if (!collapsible) {
1618
+ return;
1619
+ }
1620
+ // Tracked separately from the signal so the two cases can be told apart: arriving on the rail
1621
+ // with the submenu already open, which is worth restoring, and trying to open it while on the
1622
+ // rail, which is not.
1623
+ let onRail = false;
1624
+ effect(() => {
1625
+ const iconOnly = this.#sidebarService?.iconOnly() ?? false;
1626
+ const expanded = collapsible.expanded();
1627
+ untracked(() => {
1628
+ if (iconOnly) {
1629
+ if (expanded) {
1630
+ this.#expandedBeforeRail ||= !onRail;
1631
+ collapsible.collapse();
1632
+ }
1633
+ onRail = true;
1634
+ return;
1635
+ }
1636
+ if (!onRail) {
1637
+ return;
1638
+ }
1639
+ onRail = false;
1640
+ if (this.#expandedBeforeRail) {
1641
+ this.#expandedBeforeRail = false;
1642
+ collapsible.expand();
1643
+ }
1644
+ });
1645
+ });
1646
+ }
1647
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1648
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarMenuItemDirective, isStandalone: true, selector: "li[monaSidebarMenuItem]", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-active": "active() ? 'true' : null", "class": "baseClass()" } }, ngImport: i0 }); }
1649
+ }
1650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuItemDirective, decorators: [{
1651
+ type: Directive,
1652
+ args: [{
1653
+ selector: "li[monaSidebarMenuItem]",
1654
+ host: {
1655
+ "[attr.data-active]": "active() ? 'true' : null",
1656
+ "[class]": "baseClass()"
1657
+ }
1658
+ }]
1659
+ }], ctorParameters: () => [], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1660
+
1661
+ /**
1662
+ * @description
1663
+ * A row in a sidebar menu. Applies to a `button` for an action and to an `a` for a destination —
1664
+ * an anchor keeps `href`, `routerLink`, middle-click, open-in-new-tab and the browser's own status
1665
+ * bar preview, none of which a button can offer.
1666
+ *
1667
+ * On a compact viewport, following a link closes the drawer, since the destination it navigates to is
1668
+ * underneath it.
1669
+ */
1670
+ class SidebarMenuButtonDirective {
1671
+ constructor() {
1672
+ this.#element = inject(ElementRef).nativeElement;
1673
+ this.#menuItem = inject(SidebarMenuItemDirective, { optional: true });
1674
+ this.#nativeButton = this.#element.tagName === "BUTTON";
1675
+ this.#sidebarService = inject(SidebarService, { optional: true });
1676
+ this.ariaDisabled = computed(() => (this.disabled() ? "true" : null), /* @ts-ignore */
1677
+ ...(ngDevMode ? [{ debugName: "ariaDisabled" }] : /* istanbul ignore next */ []));
1678
+ this.baseClass = computed(() => {
1679
+ const variantClass = sidebarMenuButtonThemeVariants({
1680
+ active: this.#menuItem?.active() ?? false,
1681
+ disabled: this.disabled(),
1682
+ iconOnly: this.railBox(),
1683
+ size: this.size()
1684
+ });
1685
+ return twMerge(variantClass, this.userClass());
1686
+ }, /* @ts-ignore */
1687
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1688
+ /**
1689
+ * Marks the row the user is currently on. `page` regardless of host element: `active` describes a
1690
+ * destination, and a sidebar built from buttons rather than anchors is still a set of destinations
1691
+ * as far as a screen reader is concerned.
1692
+ */
1693
+ this.current = computed(() => ((this.#menuItem?.active() ?? false) ? "page" : null), /* @ts-ignore */
1694
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
1695
+ this.nativeDisabled = computed(() => (this.#nativeButton && this.disabled() ? "" : null), /* @ts-ignore */
1696
+ ...(ngDevMode ? [{ debugName: "nativeDisabled" }] : /* istanbul ignore next */ []));
1697
+ /**
1698
+ * On the rail the row becomes a square exactly the size of its leading visual and clips its own
1699
+ * overflow, so any label is pushed out of view instead of competing for space.
1700
+ */
1701
+ this.railBox = computed(() => this.#sidebarService?.iconOnly() ?? false, /* @ts-ignore */
1702
+ ...(ngDevMode ? [{ debugName: "railBox" }] : /* istanbul ignore next */ []));
1703
+ // The label is clipped away on the rail, so the leading visual needs something to identify it. Only
1704
+ // set there, so the title never duplicates a label that is already legible.
1705
+ this.railTitle = computed(() => {
1706
+ const tooltip = this.tooltip();
1707
+ return tooltip && this.railBox() ? tooltip : null;
1708
+ }, /* @ts-ignore */
1709
+ ...(ngDevMode ? [{ debugName: "railTitle" }] : /* istanbul ignore next */ []));
1710
+ this.tabIndex = computed(() => (this.disabled() && !this.#nativeButton ? 0 : null), /* @ts-ignore */
1711
+ ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
1712
+ // Guards against a sidebar row inside a form submitting it.
1713
+ this.type = this.#nativeButton ? "button" : null;
1714
+ /**
1715
+ * @description Closes the overlay drawer after this row is activated. Only applies on compact
1716
+ * viewports, where the drawer covers the destination being navigated to. Set to `false` for rows
1717
+ * that do not navigate, such as a disclosure trigger.
1718
+ * @default true
1719
+ */
1720
+ this.closeOnSelect = input(true, /* @ts-ignore */
1721
+ ...(ngDevMode ? [{ debugName: "closeOnSelect" }] : /* istanbul ignore next */ []));
1722
+ /**
1723
+ * @description Renders the row as unavailable and suppresses activation.
1724
+ * @default false
1725
+ */
1726
+ this.disabled = input(false, /* @ts-ignore */
1727
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
1728
+ /**
1729
+ * @description How much room this row's leading visual needs. Use `"large"` when it opens with
1730
+ * something avatar sized, so the icon rail lets it fill the square instead of insetting and clipping it.
1731
+ * @default "medium"
1732
+ */
1733
+ this.size = input("medium", /* @ts-ignore */
1734
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1735
+ /**
1736
+ * @description Text identifying this item while the sidebar is collapsed to its icon rail, where the
1737
+ * label is clipped away. Applied as the host's `title`, so the browser surfaces it on hover, and an
1738
+ * ancestor carrying `monaTooltip` with `mode="content"` renders it as a styled tooltip instead.
1739
+ * @default ""
1740
+ */
1741
+ this.tooltip = input("", /* @ts-ignore */
1742
+ ...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
1743
+ /**
1744
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1745
+ * @default ""
1746
+ */
1747
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1748
+ transform: value => classInputToClass(value) });
1749
+ }
1750
+ #element;
1751
+ #menuItem;
1752
+ #nativeButton;
1753
+ #sidebarService;
1754
+ onClick(event) {
1755
+ if (this.disabled()) {
1756
+ // `aria-disabled` is advisory: an anchor with an `href` still navigates without this.
1757
+ event.preventDefault();
1758
+ event.stopImmediatePropagation();
1759
+ return;
1760
+ }
1761
+ if (this.closeOnSelect() && this.#sidebarService?.mobileOpen()) {
1762
+ this.#sidebarService.collapse();
1763
+ }
1764
+ }
1765
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1766
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarMenuButtonDirective, isStandalone: true, selector: "a[monaSidebarMenuButton], button[monaSidebarMenuButton]", inputs: { closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)" }, properties: { "attr.aria-current": "current()", "attr.aria-disabled": "ariaDisabled()", "attr.disabled": "nativeDisabled()", "attr.tabindex": "tabIndex()", "attr.title": "railTitle()", "attr.type": "type", "class": "baseClass()" } }, exportAs: ["monaSidebarMenuButton"], ngImport: i0 }); }
1767
+ }
1768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuButtonDirective, decorators: [{
1769
+ type: Directive,
1770
+ args: [{
1771
+ selector: "a[monaSidebarMenuButton], button[monaSidebarMenuButton]",
1772
+ exportAs: "monaSidebarMenuButton",
1773
+ host: {
1774
+ "[attr.aria-current]": "current()",
1775
+ "[attr.aria-disabled]": "ariaDisabled()",
1776
+ "[attr.disabled]": "nativeDisabled()",
1777
+ // An anchor without an `href` is not focusable or clickable, so a disabled link has to be
1778
+ // given back a tab stop to stay announceable, and its activation suppressed instead.
1779
+ "[attr.tabindex]": "tabIndex()",
1780
+ "[attr.title]": "railTitle()",
1781
+ "[attr.type]": "type",
1782
+ "[class]": "baseClass()",
1783
+ "(click)": "onClick($event)"
1784
+ }
1785
+ }]
1786
+ }], propDecorators: { closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1787
+
1788
+ /**
1789
+ * @description
1790
+ * An indented submenu inside a `monaSidebarMenuItem` that is also a `monaCollapsible`.
1791
+ *
1792
+ * It carries no hiding of its own on the icon rail. `SidebarMenuItemDirective` closes the disclosure
1793
+ * there instead, which leaves the trigger's `aria-expanded` truthful and lets the collapsible content
1794
+ * directive apply its own `inert`. Hiding it here as well used to produce the opposite: a submenu the
1795
+ * trigger still described as expanded, with focusable items, that nobody could see.
1796
+ */
1797
+ class SidebarMenuSubDirective {
1798
+ constructor() {
1799
+ this.baseClass = computed(() => twMerge(sidebarMenuSubThemeVariants(), this.userClass()), /* @ts-ignore */
1800
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1801
+ /**
1802
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1803
+ * @default ""
1804
+ */
1805
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1806
+ transform: value => classInputToClass(value) });
1807
+ }
1808
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuSubDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1809
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarMenuSubDirective, isStandalone: true, selector: "ul[monaSidebarMenuSub]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1810
+ }
1811
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarMenuSubDirective, decorators: [{
1812
+ type: Directive,
1813
+ args: [{
1814
+ selector: "ul[monaSidebarMenuSub]",
1815
+ host: {
1816
+ "[class]": "baseClass()"
1817
+ }
1818
+ }]
1819
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1820
+
1821
+ /**
1822
+ * @description
1823
+ * A thin strip along the sidebar's inner edge that toggles it. Place it inside `mona-sidebar`,
1824
+ * which positions it. Hidden on narrow viewports, where the edge is too easy to hit by accident.
1825
+ *
1826
+ * Deliberately outside the tab order: it is a pointer shortcut for what `monaSidebarTrigger` already
1827
+ * does, and a second tab stop onto an unlabelled 4px strip would be noise for keyboard users. Provide
1828
+ * a `monaSidebarTrigger` as well — the rail is not a substitute for it — and give the rail an
1829
+ * `aria-label` so pointer users of assistive technology can still identify it.
1830
+ */
1831
+ class SidebarRailDirective {
1832
+ constructor() {
1833
+ this.#sidebarService = inject(SidebarService);
1834
+ this.baseClass = computed(() => sidebarRailThemeVariants({ side: this.#sidebarService.side() }), /* @ts-ignore */
1835
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1836
+ this.expanded = this.#sidebarService.expanded;
1837
+ this.sidebarId = this.#sidebarService.sidebarId;
1838
+ }
1839
+ #sidebarService;
1840
+ onClick() {
1841
+ this.#sidebarService.toggle();
1842
+ }
1843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarRailDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1844
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: SidebarRailDirective, isStandalone: true, selector: "button[monaSidebarRail]", host: { listeners: { "click": "onClick()" }, properties: { "attr.aria-controls": "sidebarId()", "attr.aria-expanded": "expanded()", "attr.tabindex": "-1", "attr.type": "'button'", "class": "baseClass()" } }, ngImport: i0 }); }
1845
+ }
1846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarRailDirective, decorators: [{
1847
+ type: Directive,
1848
+ args: [{
1849
+ selector: "button[monaSidebarRail]",
1850
+ host: {
1851
+ "[attr.aria-controls]": "sidebarId()",
1852
+ "[attr.aria-expanded]": "expanded()",
1853
+ "[attr.tabindex]": "-1",
1854
+ // Without this a rail inside a form submits it, because `submit` is a button's default type.
1855
+ "[attr.type]": "'button'",
1856
+ "[class]": "baseClass()",
1857
+ "(click)": "onClick()"
1858
+ }
1859
+ }]
1860
+ }] });
1861
+
1862
+ /**
1863
+ * @description
1864
+ * A horizontal rule between sidebar regions. Tightens its inset on the icon rail.
1865
+ */
1866
+ class SidebarSeparatorDirective {
1867
+ constructor() {
1868
+ this.#sidebarService = inject(SidebarService, { optional: true });
1869
+ this.baseClass = computed(() => {
1870
+ const variantClass = sidebarSeparatorThemeVariants({
1871
+ iconOnly: this.#sidebarService?.iconOnly() ?? false
1872
+ });
1873
+ return twMerge(variantClass, this.userClass());
1874
+ }, /* @ts-ignore */
1875
+ ...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
1876
+ /**
1877
+ * @description Additional CSS classes merged onto the host element via `tailwind-merge`.
1878
+ * @default ""
1879
+ */
1880
+ this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
1881
+ transform: value => classInputToClass(value) });
1882
+ }
1883
+ #sidebarService;
1884
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarSeparatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1885
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarSeparatorDirective, isStandalone: true, selector: "[monaSidebarSeparator]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0 }); }
1886
+ }
1887
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarSeparatorDirective, decorators: [{
1888
+ type: Directive,
1889
+ args: [{
1890
+ selector: "[monaSidebarSeparator]",
1891
+ host: {
1892
+ "[class]": "baseClass()"
1893
+ }
1894
+ }]
1895
+ }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1896
+
1897
+ /**
1898
+ * @description
1899
+ * Toggles a sidebar. Can be placed anywhere inside a `mona-sidebar-layout`, including in the inset,
1900
+ * and adds no styling of its own.
1901
+ *
1902
+ * By default it drives the sidebar it is written inside. Set `for` to the id of a `mona-sidebar` to
1903
+ * drive that one instead, which is what a trigger in a shared header needs when the layout holds more
1904
+ * than one sidebar. A `for` that matches no sidebar leaves the trigger inert rather than throwing, so
1905
+ * a sidebar that has not rendered yet does not take the page down with it.
1906
+ *
1907
+ * A `button` is the right host and needs nothing further. On any other element the directive supplies
1908
+ * the rest of the button contract — `role`, a tab stop, and Enter/Space activation — so a `div` or
1909
+ * `span` trigger is still operable by keyboard rather than silently mouse-only.
1910
+ */
1911
+ class SidebarTriggerDirective {
1912
+ constructor() {
1913
+ this.#element = inject(ElementRef).nativeElement;
1914
+ this.#layoutService = inject(SidebarLayoutService, { optional: true });
1915
+ this.#nativeAnchor = this.#element.tagName === "A";
1916
+ this.#nativeButton = this.#element.tagName === "BUTTON" || this.#element.tagName === "INPUT" || this.#element.tagName === "SUMMARY";
1917
+ // Optional, because a trigger aimed at a sidebar by id is usually written outside every sidebar.
1918
+ this.#sidebarService = inject(SidebarService, { optional: true });
1919
+ /**
1920
+ * The sidebar this trigger drives: the one named by `for`, else the one it is written inside, else
1921
+ * the layout's first. That last step is what lets a trigger in the inset or a header go on working
1922
+ * without naming anything, which is all a layout with a single sidebar ever needs.
1923
+ */
1924
+ this.#targetController = computed(() => {
1925
+ const targetId = this.for();
1926
+ if (targetId) {
1927
+ return this.#layoutService?.getController(targetId) ?? null;
1928
+ }
1929
+ return this.#sidebarService?.controller ?? this.#layoutService?.primaryController() ?? null;
1930
+ }, /* @ts-ignore */
1931
+ ...(ngDevMode ? [{ debugName: "#targetController" }] : /* istanbul ignore next */ []));
1932
+ // Null rather than false when nothing is targeted: a trigger that controls nothing has no
1933
+ // expanded state to report, and claiming one would announce a control that does not exist.
1934
+ this.expanded = computed(() => this.#targetController()?.expanded() ?? null, /* @ts-ignore */
1935
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1936
+ this.role = this.#nativeButton || this.#nativeAnchor ? null : "button";
1937
+ this.sidebarId = computed(() => this.#targetController()?.sidebarId() ?? null, /* @ts-ignore */
1938
+ ...(ngDevMode ? [{ debugName: "sidebarId" }] : /* istanbul ignore next */ []));
1939
+ this.tabIndex = this.#nativeButton || this.#nativeAnchor ? null : 0;
1940
+ this.type = this.#element.tagName === "BUTTON" ? "button" : null;
1941
+ /**
1942
+ * @description Id of the `mona-sidebar` to toggle. Leave unset to toggle the sidebar this trigger
1943
+ * is written inside.
1944
+ * @default ""
1945
+ */
1946
+ this.for = input("", /* @ts-ignore */
1947
+ ...(ngDevMode ? [{ debugName: "for" }] : /* istanbul ignore next */ []));
1948
+ }
1949
+ #element;
1950
+ #layoutService;
1951
+ #nativeAnchor;
1952
+ #nativeButton;
1953
+ // Optional, because a trigger aimed at a sidebar by id is usually written outside every sidebar.
1954
+ #sidebarService;
1955
+ /**
1956
+ * The sidebar this trigger drives: the one named by `for`, else the one it is written inside, else
1957
+ * the layout's first. That last step is what lets a trigger in the inset or a header go on working
1958
+ * without naming anything, which is all a layout with a single sidebar ever needs.
1959
+ */
1960
+ #targetController;
1961
+ onClick() {
1962
+ this.#targetController()?.toggle();
1963
+ }
1964
+ onEnterKeydown(event) {
1965
+ // Buttons and anchors already synthesize a click for Enter.
1966
+ if (this.#nativeButton || this.#nativeAnchor) {
1967
+ return;
1968
+ }
1969
+ event.preventDefault();
1970
+ this.#targetController()?.toggle();
1971
+ }
1972
+ onSpaceKeydown(event) {
1973
+ // Buttons already synthesize a click for Space; anchors scroll the page instead.
1974
+ if (this.#nativeButton) {
1975
+ return;
1976
+ }
1977
+ event.preventDefault();
1978
+ this.#targetController()?.toggle();
1979
+ }
1980
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1981
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: SidebarTriggerDirective, isStandalone: true, selector: "[monaSidebarTrigger]", inputs: { for: { classPropertyName: "for", publicName: "for", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick()", "keydown.enter": "onEnterKeydown($event)", "keydown.space": "onSpaceKeydown($event)" }, properties: { "attr.aria-controls": "sidebarId()", "attr.aria-expanded": "expanded()", "attr.role": "role", "attr.tabindex": "tabIndex", "attr.type": "type" } }, exportAs: ["monaSidebarTrigger"], ngImport: i0 }); }
1982
+ }
1983
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SidebarTriggerDirective, decorators: [{
1984
+ type: Directive,
1985
+ args: [{
1986
+ selector: "[monaSidebarTrigger]",
1987
+ exportAs: "monaSidebarTrigger",
1988
+ host: {
1989
+ "[attr.aria-controls]": "sidebarId()",
1990
+ "[attr.aria-expanded]": "expanded()",
1991
+ "[attr.role]": "role",
1992
+ "[attr.tabindex]": "tabIndex",
1993
+ // Without this a trigger inside a form submits it, because `submit` is a button's default type.
1994
+ "[attr.type]": "type",
1995
+ "(click)": "onClick()",
1996
+ "(keydown.enter)": "onEnterKeydown($event)",
1997
+ "(keydown.space)": "onSpaceKeydown($event)"
1998
+ }
1999
+ }]
2000
+ }], propDecorators: { for: [{ type: i0.Input, args: [{ isSignal: true, alias: "for", required: false }] }] } });
2001
+
2002
+ /**
2003
+ * Replaces where sidebars remember whether they were left open. The default is `localStorage`, which
2004
+ * the server cannot read; supply a cookie- or request-backed storage here to have a server-rendered
2005
+ * application render the remembered state rather than correcting it on hydration.
2006
+ */
2007
+ function provideSidebarStorage(storage) {
2008
+ return makeEnvironmentProviders([{ provide: SIDEBAR_STORAGE, useValue: storage }]);
2009
+ }
2010
+
2011
+ function injectSidebar(options) {
2012
+ const service = inject(SidebarService, { optional: options?.optional ?? false });
2013
+ return service?.controller ?? null;
2014
+ }
2015
+
2016
+ /*
2017
+ * Public API Surface of @nanahoshi/mona-ui/sidebar
2018
+ */
2019
+
2020
+ /**
2021
+ * Generated bundle index. Do not edit.
2022
+ */
2023
+
2024
+ export { LocalStorageSidebarStorage, SIDEBAR_STORAGE, SidebarComponent, SidebarContentDirective, SidebarFooterDirective, SidebarGroupActionDirective, SidebarGroupContentDirective, SidebarGroupDirective, SidebarGroupHeaderDirective, SidebarGroupLabelDirective, SidebarHeaderDirective, SidebarInputDirective, SidebarInsetDirective, SidebarLayoutComponent, SidebarMenuActionDirective, SidebarMenuBadgeDirective, SidebarMenuButtonDirective, SidebarMenuDirective, SidebarMenuItemDirective, SidebarMenuSkeletonComponent, SidebarMenuSubDirective, SidebarRailDirective, SidebarSeparatorDirective, SidebarTriggerDirective, injectSidebar, provideSidebarStorage };
2025
+ //# sourceMappingURL=nanahoshi-mona-ui-sidebar.mjs.map