@mk-kit/ui 0.34.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/block-editor/README.md +254 -0
  4. package/fesm2022/mk-kit-ui-block-editor.mjs +2158 -0
  5. package/fesm2022/mk-kit-ui-block-editor.mjs.map +1 -0
  6. package/fesm2022/mk-kit-ui-button.mjs +81 -0
  7. package/fesm2022/mk-kit-ui-button.mjs.map +1 -0
  8. package/fesm2022/mk-kit-ui-checkbox.mjs +136 -0
  9. package/fesm2022/mk-kit-ui-checkbox.mjs.map +1 -0
  10. package/fesm2022/mk-kit-ui-chip.mjs +122 -0
  11. package/fesm2022/mk-kit-ui-chip.mjs.map +1 -0
  12. package/fesm2022/mk-kit-ui-context-menu.mjs +144 -0
  13. package/fesm2022/mk-kit-ui-context-menu.mjs.map +1 -0
  14. package/fesm2022/mk-kit-ui-core.mjs +1576 -0
  15. package/fesm2022/mk-kit-ui-core.mjs.map +1 -0
  16. package/fesm2022/mk-kit-ui-data.mjs +6055 -0
  17. package/fesm2022/mk-kit-ui-data.mjs.map +1 -0
  18. package/fesm2022/mk-kit-ui-datetime.mjs +3409 -0
  19. package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -0
  20. package/fesm2022/mk-kit-ui-directives.mjs +1779 -0
  21. package/fesm2022/mk-kit-ui-directives.mjs.map +1 -0
  22. package/fesm2022/mk-kit-ui-dnd.mjs +1073 -0
  23. package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -0
  24. package/fesm2022/mk-kit-ui-feedback.mjs +2426 -0
  25. package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -0
  26. package/fesm2022/mk-kit-ui-forms.mjs +9208 -0
  27. package/fesm2022/mk-kit-ui-forms.mjs.map +1 -0
  28. package/fesm2022/mk-kit-ui-icon.mjs +470 -0
  29. package/fesm2022/mk-kit-ui-icon.mjs.map +1 -0
  30. package/fesm2022/mk-kit-ui-media.mjs +896 -0
  31. package/fesm2022/mk-kit-ui-media.mjs.map +1 -0
  32. package/fesm2022/mk-kit-ui-navigation.mjs +2542 -0
  33. package/fesm2022/mk-kit-ui-navigation.mjs.map +1 -0
  34. package/fesm2022/mk-kit-ui-rich-text.mjs +565 -0
  35. package/fesm2022/mk-kit-ui-rich-text.mjs.map +1 -0
  36. package/fesm2022/mk-kit-ui-table.mjs +1378 -0
  37. package/fesm2022/mk-kit-ui-table.mjs.map +1 -0
  38. package/fesm2022/mk-kit-ui.mjs +32 -0
  39. package/fesm2022/mk-kit-ui.mjs.map +1 -0
  40. package/package.json +130 -0
  41. package/schematics/collection.json +10 -0
  42. package/schematics/ng-add/index.js +113 -0
  43. package/schematics/ng-add/schema.json +22 -0
  44. package/schematics/package.json +3 -0
  45. package/styles/mk-kit.css +750 -0
  46. package/types/mk-kit-ui-block-editor.d.ts +292 -0
  47. package/types/mk-kit-ui-button.d.ts +40 -0
  48. package/types/mk-kit-ui-checkbox.d.ts +62 -0
  49. package/types/mk-kit-ui-chip.d.ts +59 -0
  50. package/types/mk-kit-ui-context-menu.d.ts +57 -0
  51. package/types/mk-kit-ui-core.d.ts +1105 -0
  52. package/types/mk-kit-ui-data.d.ts +2580 -0
  53. package/types/mk-kit-ui-datetime.d.ts +1171 -0
  54. package/types/mk-kit-ui-directives.d.ts +807 -0
  55. package/types/mk-kit-ui-dnd.d.ts +423 -0
  56. package/types/mk-kit-ui-feedback.d.ts +1270 -0
  57. package/types/mk-kit-ui-forms.d.ts +3586 -0
  58. package/types/mk-kit-ui-icon.d.ts +108 -0
  59. package/types/mk-kit-ui-media.d.ts +549 -0
  60. package/types/mk-kit-ui-navigation.d.ts +1169 -0
  61. package/types/mk-kit-ui-rich-text.d.ts +187 -0
  62. package/types/mk-kit-ui-table.d.ts +739 -0
  63. package/types/mk-kit-ui.d.ts +17 -0
@@ -0,0 +1,2542 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, signal, ChangeDetectionStrategy, Component, contentChildren, viewChildren, model, effect, inject, Directive, computed, numberAttribute, ElementRef, DOCUMENT, output, Injector, PLATFORM_ID, afterNextRender, viewChild, TemplateRef, linkedSignal } from '@angular/core';
3
+ import { mkUniqueId, MK_I18N, MkAnchoredPanel, MkFocusTrap } from '@mk-kit/ui/core';
4
+ import { NgTemplateOutlet, isPlatformBrowser, isPlatformServer } from '@angular/common';
5
+ import { MkIcon } from '@mk-kit/ui/icon';
6
+
7
+ /**
8
+ * A single tab + its panel. Declared inside `<mk-tabs>`; the projected content
9
+ * becomes the tab panel body while `label` is rendered in the tablist.
10
+ *
11
+ * ```html
12
+ * <mk-tabs>
13
+ * <mk-tab label="Overview">…panel content…</mk-tab>
14
+ * <mk-tab label="Settings" [disabled]="locked()">…</mk-tab>
15
+ * </mk-tabs>
16
+ * ```
17
+ */
18
+ class MkTab {
19
+ /** Text shown on the tab button in the tablist. */
20
+ label = input('', /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
22
+ /** Disable selection of this tab. */
23
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
24
+ /** Stable id for the tab button (wires `aria-labelledby`). */
25
+ tabId = mkUniqueId('mk-tab');
26
+ /** Stable id for the panel (wires `aria-controls`). */
27
+ panelId = mkUniqueId('mk-tabpanel');
28
+ /** Whether this tab is currently selected. Set by the parent `MkTabs`. */
29
+ active = signal(false, /* @ts-ignore */
30
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
31
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
32
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkTab, isStandalone: true, selector: "mk-tab", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tabpanel" }, properties: { "id": "panelId", "attr.aria-labelledby": "tabId", "attr.hidden": "active() ? null : ''", "attr.tabindex": "active() ? 0 : null" }, classAttribute: "mk-tab" }, ngImport: i0, template: "<ng-content />\n", styles: [":host{display:block;padding:var(--mk-space-4) var(--mk-space-1);color:var(--mk-text);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);animation:mk-tab-fade var(--mk-duration-normal) var(--mk-ease-out)}:host([hidden]){display:none}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-radius:var(--mk-radius-sm)}@keyframes mk-tab-fade{0%{opacity:0;transform:translateY(var(--mk-space-1))}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){:host{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33
+ }
34
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTab, decorators: [{
35
+ type: Component,
36
+ args: [{ selector: 'mk-tab', changeDetection: ChangeDetectionStrategy.OnPush, host: {
37
+ class: 'mk-tab',
38
+ role: 'tabpanel',
39
+ '[id]': 'panelId',
40
+ '[attr.aria-labelledby]': 'tabId',
41
+ '[attr.hidden]': "active() ? null : ''",
42
+ '[attr.tabindex]': 'active() ? 0 : null',
43
+ }, template: "<ng-content />\n", styles: [":host{display:block;padding:var(--mk-space-4) var(--mk-space-1);color:var(--mk-text);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);animation:mk-tab-fade var(--mk-duration-normal) var(--mk-ease-out)}:host([hidden]){display:none}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-radius:var(--mk-radius-sm)}@keyframes mk-tab-fade{0%{opacity:0;transform:translateY(var(--mk-space-1))}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){:host{animation:none}}\n"] }]
44
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
45
+
46
+ /**
47
+ * Accessible tabs implementing the ARIA tabs pattern: a `role="tablist"` with
48
+ * roving tabindex, Arrow/Home/End keyboard navigation and an animated active
49
+ * indicator. Selection is two-way bindable via `selectedIndex`.
50
+ *
51
+ * ```html
52
+ * <mk-tabs [(selectedIndex)]="tab" variant="line">
53
+ * <mk-tab label="Profile">…</mk-tab>
54
+ * <mk-tab label="Billing">…</mk-tab>
55
+ * </mk-tabs>
56
+ * ```
57
+ */
58
+ class MkTabs {
59
+ /** Projected tabs, kept live via a signal content query. */
60
+ tabs = contentChildren(MkTab, /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
62
+ /** Rendered tab buttons, used to move focus and measure the indicator. */
63
+ tabButtons = viewChildren('tabBtn', /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "tabButtons" }] : /* istanbul ignore next */ []));
65
+ /** Zero-based index of the active tab (two-way). */
66
+ selectedIndex = model(0, /* @ts-ignore */
67
+ ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
68
+ /** `line` shows an underline indicator; `pill` uses filled pills. */
69
+ variant = input('line', /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
71
+ /** Sliding indicator geometry (line variant). */
72
+ indicatorLeft = signal(0, /* @ts-ignore */
73
+ ...(ngDevMode ? [{ debugName: "indicatorLeft" }] : /* istanbul ignore next */ []));
74
+ indicatorWidth = signal(0, /* @ts-ignore */
75
+ ...(ngDevMode ? [{ debugName: "indicatorWidth" }] : /* istanbul ignore next */ []));
76
+ constructor() {
77
+ // Keep each tab's `active` state and the clamped selected index in sync.
78
+ effect(() => {
79
+ const tabs = this.tabs();
80
+ let index = this.selectedIndex();
81
+ if (tabs.length > 0 && index > tabs.length - 1) {
82
+ index = tabs.length - 1;
83
+ }
84
+ if (index < 0)
85
+ index = 0;
86
+ tabs.forEach((tab, i) => tab.active.set(i === index));
87
+ queueMicrotask(() => this.measureIndicator(index));
88
+ });
89
+ }
90
+ select(index) {
91
+ const tab = this.tabs()[index];
92
+ if (!tab || tab.disabled())
93
+ return;
94
+ this.selectedIndex.set(index);
95
+ // The tablist scrolls horizontally on narrow screens — keep the newly
96
+ // active tab visible. 'nearest' so activating a tab never jumps the page
97
+ // vertically. Optional-chained: jsdom has no scrollIntoView.
98
+ this.tabButtons()[index]?.nativeElement.scrollIntoView?.({
99
+ block: 'nearest',
100
+ inline: 'nearest',
101
+ });
102
+ }
103
+ focusTab(index) {
104
+ this.tabButtons()[index]?.nativeElement.focus();
105
+ }
106
+ onKeydown(event, index) {
107
+ const count = this.tabs().length;
108
+ let next = -1;
109
+ switch (event.key) {
110
+ case 'ArrowRight':
111
+ case 'ArrowDown':
112
+ next = this.nextEnabled(index, 1);
113
+ break;
114
+ case 'ArrowLeft':
115
+ case 'ArrowUp':
116
+ next = this.nextEnabled(index, -1);
117
+ break;
118
+ case 'Home':
119
+ next = this.nextEnabled(-1, 1);
120
+ break;
121
+ case 'End':
122
+ next = this.nextEnabled(count, -1);
123
+ break;
124
+ default:
125
+ return;
126
+ }
127
+ if (next >= 0) {
128
+ event.preventDefault();
129
+ this.select(next);
130
+ this.focusTab(next);
131
+ }
132
+ }
133
+ /** Next non-disabled tab index starting from `from`, wrapping around. */
134
+ nextEnabled(from, step) {
135
+ const tabs = this.tabs();
136
+ const count = tabs.length;
137
+ if (count === 0)
138
+ return -1;
139
+ for (let i = 1; i <= count; i++) {
140
+ const idx = (((from + step * i) % count) + count) % count;
141
+ if (!tabs[idx].disabled())
142
+ return idx;
143
+ }
144
+ return -1;
145
+ }
146
+ measureIndicator(index) {
147
+ const btn = this.tabButtons()[index]?.nativeElement;
148
+ if (!btn)
149
+ return;
150
+ this.indicatorLeft.set(btn.offsetLeft);
151
+ this.indicatorWidth.set(btn.offsetWidth);
152
+ }
153
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
154
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTabs, isStandalone: true, selector: "mk-tabs", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange" }, host: { properties: { "class.mk-tabs--line": "variant() === 'line'", "class.mk-tabs--pill": "variant() === 'pill'" }, classAttribute: "mk-tabs" }, queries: [{ propertyName: "tabs", predicate: MkTab, isSignal: true }], viewQueries: [{ propertyName: "tabButtons", predicate: ["tabBtn"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mk-tabs__list\" role=\"tablist\">\n @for (tab of tabs(); track tab.tabId; let i = $index) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"mk-tabs__tab\"\n [class.mk-tabs__tab--active]=\"i === selectedIndex()\"\n [id]=\"tab.tabId\"\n [attr.aria-controls]=\"tab.panelId\"\n [attr.aria-selected]=\"i === selectedIndex()\"\n [attr.tabindex]=\"i === selectedIndex() ? 0 : -1\"\n [disabled]=\"tab.disabled()\"\n (click)=\"select(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n {{ tab.label() }}\n </button>\n }\n\n @if (variant() === 'line') {\n <span\n class=\"mk-tabs__indicator\"\n aria-hidden=\"true\"\n [style.transform]=\"'translateX(' + indicatorLeft() + 'px)'\"\n [style.width.px]=\"indicatorWidth()\"\n ></span>\n }\n</div>\n\n<div class=\"mk-tabs__panels\">\n <ng-content />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.mk-tabs__list{position:relative;display:flex;align-items:stretch;gap:var(--mk-space-1);overflow-x:auto;scrollbar-width:none}.mk-tabs__list::-webkit-scrollbar{display:none}:host(.mk-tabs--line) .mk-tabs__list{border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-tabs__tab{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);min-height:44px;padding:var(--mk-space-2) var(--mk-space-4);background:transparent;border:var(--mk-border-width) solid transparent;color:var(--mk-text-muted);font-family:inherit;font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);line-height:1;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard),border-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tabs__tab:hover{color:var(--mk-text)}.mk-tabs__tab:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width));border-radius:var(--mk-radius-sm)}.mk-tabs__tab:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-tabs__tab--active{color:var(--mk-primary)}:host(.mk-tabs--line) .mk-tabs__tab{border-radius:var(--mk-radius-sm) var(--mk-radius-sm) 0 0}.mk-tabs__indicator{position:absolute;bottom:0;left:0;height:2px;background:var(--mk-primary);border-radius:var(--mk-radius-pill);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized),width var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-tabs--pill) .mk-tabs__list{gap:var(--mk-space-1);padding:var(--mk-space-1);background:var(--mk-surface-2);border-radius:var(--mk-radius-lg)}:host(.mk-tabs--pill) .mk-tabs__tab{border-radius:var(--mk-radius-md)}:host(.mk-tabs--pill) .mk-tabs__tab:hover:not(:disabled):not(.mk-tabs__tab--active){background:var(--mk-hover-overlay)}:host(.mk-tabs--pill) .mk-tabs__tab--active{color:var(--mk-primary-contrast);background:var(--mk-primary)}.mk-tabs__panels{display:block}@media(prefers-reduced-motion:reduce){.mk-tabs__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
155
+ }
156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTabs, decorators: [{
157
+ type: Component,
158
+ args: [{ selector: 'mk-tabs', changeDetection: ChangeDetectionStrategy.OnPush, host: {
159
+ class: 'mk-tabs',
160
+ '[class.mk-tabs--line]': "variant() === 'line'",
161
+ '[class.mk-tabs--pill]': "variant() === 'pill'",
162
+ }, template: "<div class=\"mk-tabs__list\" role=\"tablist\">\n @for (tab of tabs(); track tab.tabId; let i = $index) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"mk-tabs__tab\"\n [class.mk-tabs__tab--active]=\"i === selectedIndex()\"\n [id]=\"tab.tabId\"\n [attr.aria-controls]=\"tab.panelId\"\n [attr.aria-selected]=\"i === selectedIndex()\"\n [attr.tabindex]=\"i === selectedIndex() ? 0 : -1\"\n [disabled]=\"tab.disabled()\"\n (click)=\"select(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n {{ tab.label() }}\n </button>\n }\n\n @if (variant() === 'line') {\n <span\n class=\"mk-tabs__indicator\"\n aria-hidden=\"true\"\n [style.transform]=\"'translateX(' + indicatorLeft() + 'px)'\"\n [style.width.px]=\"indicatorWidth()\"\n ></span>\n }\n</div>\n\n<div class=\"mk-tabs__panels\">\n <ng-content />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.mk-tabs__list{position:relative;display:flex;align-items:stretch;gap:var(--mk-space-1);overflow-x:auto;scrollbar-width:none}.mk-tabs__list::-webkit-scrollbar{display:none}:host(.mk-tabs--line) .mk-tabs__list{border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-tabs__tab{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);min-height:44px;padding:var(--mk-space-2) var(--mk-space-4);background:transparent;border:var(--mk-border-width) solid transparent;color:var(--mk-text-muted);font-family:inherit;font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);line-height:1;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard),border-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tabs__tab:hover{color:var(--mk-text)}.mk-tabs__tab:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width));border-radius:var(--mk-radius-sm)}.mk-tabs__tab:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-tabs__tab--active{color:var(--mk-primary)}:host(.mk-tabs--line) .mk-tabs__tab{border-radius:var(--mk-radius-sm) var(--mk-radius-sm) 0 0}.mk-tabs__indicator{position:absolute;bottom:0;left:0;height:2px;background:var(--mk-primary);border-radius:var(--mk-radius-pill);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized),width var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-tabs--pill) .mk-tabs__list{gap:var(--mk-space-1);padding:var(--mk-space-1);background:var(--mk-surface-2);border-radius:var(--mk-radius-lg)}:host(.mk-tabs--pill) .mk-tabs__tab{border-radius:var(--mk-radius-md)}:host(.mk-tabs--pill) .mk-tabs__tab:hover:not(:disabled):not(.mk-tabs__tab--active){background:var(--mk-hover-overlay)}:host(.mk-tabs--pill) .mk-tabs__tab--active{color:var(--mk-primary-contrast);background:var(--mk-primary)}.mk-tabs__panels{display:block}@media(prefers-reduced-motion:reduce){.mk-tabs__indicator{transition:none}}\n"] }]
163
+ }], ctorParameters: () => [], propDecorators: { tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkTab), { isSignal: true }] }], tabButtons: [{ type: i0.ViewChildren, args: ['tabBtn', { isSignal: true }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
164
+
165
+ /**
166
+ * Groups `<mk-accordion-item>`s. By default only one item can be open at a
167
+ * time (single-open); set `multi` to allow several. Coordinates its children
168
+ * through their two-way `open` models.
169
+ *
170
+ * ```html
171
+ * <mk-accordion [multi]="false">
172
+ * <mk-accordion-item header="One">…</mk-accordion-item>
173
+ * <mk-accordion-item header="Two">…</mk-accordion-item>
174
+ * </mk-accordion>
175
+ * ```
176
+ */
177
+ class MkAccordion {
178
+ /** Allow multiple items open simultaneously. */
179
+ multi = input(false, { ...(ngDevMode ? { debugName: "multi" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
180
+ /** Registered items collapse siblings on open in single mode. */
181
+ items = new Set();
182
+ /** Called by an item just before its open state changes. */
183
+ onItemToggle(source, opening) {
184
+ this.items.add(source);
185
+ if (opening && !this.multi()) {
186
+ for (const item of this.items) {
187
+ if (item !== source && item.open())
188
+ item.open.set(false);
189
+ }
190
+ }
191
+ }
192
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordion, deps: [], target: i0.ɵɵFactoryTarget.Component });
193
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkAccordion, isStandalone: true, selector: "mk-accordion", inputs: { multi: { classPropertyName: "multi", publicName: "multi", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "mk-accordion" }, ngImport: i0, template: "<ng-content />\n", styles: [":host{display:block;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background:var(--mk-surface);overflow:hidden}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
194
+ }
195
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordion, decorators: [{
196
+ type: Component,
197
+ args: [{ selector: 'mk-accordion', changeDetection: ChangeDetectionStrategy.OnPush, host: {
198
+ class: 'mk-accordion',
199
+ }, template: "<ng-content />\n", styles: [":host{display:block;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background:var(--mk-surface);overflow:hidden}\n"] }]
200
+ }], propDecorators: { multi: [{ type: i0.Input, args: [{ isSignal: true, alias: "multi", required: false }] }] } });
201
+
202
+ /**
203
+ * A collapsible section within an `<mk-accordion>`. The header is a button that
204
+ * toggles a smoothly animated body region wired with `aria-expanded` /
205
+ * `aria-controls`. Open state is two-way bindable via `open`.
206
+ *
207
+ * ```html
208
+ * <mk-accordion-item header="Shipping" [(open)]="shippingOpen">
209
+ * …body…
210
+ * </mk-accordion-item>
211
+ * ```
212
+ */
213
+ class MkAccordionItem {
214
+ accordion = inject(MkAccordion, { optional: true });
215
+ /** Plain-text header (ignored when `[mkAccordionHeader]` is projected). */
216
+ header = input('', /* @ts-ignore */
217
+ ...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
218
+ /** Disable toggling of this item. */
219
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
220
+ /** Whether the item is expanded (two-way). */
221
+ open = model(false, /* @ts-ignore */
222
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
223
+ /** Stable id for the trigger button. */
224
+ triggerId = mkUniqueId('mk-acc-trigger');
225
+ /** Stable id for the panel region. */
226
+ panelId = mkUniqueId('mk-acc-panel');
227
+ toggle() {
228
+ if (this.disabled())
229
+ return;
230
+ const next = !this.open();
231
+ this.accordion?.onItemToggle(this, next);
232
+ this.open.set(next);
233
+ }
234
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordionItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
235
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkAccordionItem, isStandalone: true, selector: "mk-accordion-item", inputs: { header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { properties: { "class.mk-accordion-item--open": "open()", "class.mk-accordion-item--disabled": "disabled()" }, classAttribute: "mk-accordion-item" }, ngImport: i0, template: "<h3 class=\"mk-accordion-item__heading\">\n <button\n type=\"button\"\n class=\"mk-accordion-item__trigger\"\n [id]=\"triggerId\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-expanded]=\"open()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n >\n <span class=\"mk-accordion-item__label\">\n @if (header()) {\n {{ header() }}\n }\n <ng-content select=\"[mkAccordionHeader]\" />\n </span>\n <span class=\"mk-accordion-item__chevron\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n</h3>\n\n<div\n class=\"mk-accordion-item__panel\"\n role=\"region\"\n [id]=\"panelId\"\n [attr.aria-labelledby]=\"triggerId\"\n [attr.inert]=\"open() ? null : ''\"\n>\n <div class=\"mk-accordion-item__panel-inner\">\n <div class=\"mk-accordion-item__body\">\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [":host{display:block;border-bottom:var(--mk-border-width) solid var(--mk-border)}:host(:last-of-type){border-bottom:0}.mk-accordion-item__heading{margin:0;font:inherit}.mk-accordion-item__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--mk-space-3);width:100%;min-height:44px;padding:var(--mk-space-4);background:transparent;border:var(--mk-border-width) solid transparent;color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);text-align:start;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-accordion-item__trigger:hover:not(:disabled){background:var(--mk-hover-overlay)}.mk-accordion-item__trigger:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width));border-radius:var(--mk-radius-sm)}.mk-accordion-item__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-accordion-item__label{display:inline-flex;align-items:center;gap:var(--mk-space-2);min-width:0}.mk-accordion-item__chevron{display:inline-flex;flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-lg);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-accordion-item--open) .mk-accordion-item__chevron{transform:rotate(180deg)}.mk-accordion-item__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-accordion-item--open) .mk-accordion-item__panel{grid-template-rows:1fr}.mk-accordion-item__panel-inner{overflow:hidden}.mk-accordion-item__body{padding:0 var(--mk-space-4) var(--mk-space-4);color:var(--mk-text);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal)}@media(prefers-reduced-motion:reduce){.mk-accordion-item__panel,.mk-accordion-item__chevron{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
236
+ }
237
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordionItem, decorators: [{
238
+ type: Component,
239
+ args: [{ selector: 'mk-accordion-item', changeDetection: ChangeDetectionStrategy.OnPush, host: {
240
+ class: 'mk-accordion-item',
241
+ '[class.mk-accordion-item--open]': 'open()',
242
+ '[class.mk-accordion-item--disabled]': 'disabled()',
243
+ }, template: "<h3 class=\"mk-accordion-item__heading\">\n <button\n type=\"button\"\n class=\"mk-accordion-item__trigger\"\n [id]=\"triggerId\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-expanded]=\"open()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n >\n <span class=\"mk-accordion-item__label\">\n @if (header()) {\n {{ header() }}\n }\n <ng-content select=\"[mkAccordionHeader]\" />\n </span>\n <span class=\"mk-accordion-item__chevron\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n</h3>\n\n<div\n class=\"mk-accordion-item__panel\"\n role=\"region\"\n [id]=\"panelId\"\n [attr.aria-labelledby]=\"triggerId\"\n [attr.inert]=\"open() ? null : ''\"\n>\n <div class=\"mk-accordion-item__panel-inner\">\n <div class=\"mk-accordion-item__body\">\n <ng-content />\n </div>\n </div>\n</div>\n", styles: [":host{display:block;border-bottom:var(--mk-border-width) solid var(--mk-border)}:host(:last-of-type){border-bottom:0}.mk-accordion-item__heading{margin:0;font:inherit}.mk-accordion-item__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--mk-space-3);width:100%;min-height:44px;padding:var(--mk-space-4);background:transparent;border:var(--mk-border-width) solid transparent;color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);text-align:start;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-accordion-item__trigger:hover:not(:disabled){background:var(--mk-hover-overlay)}.mk-accordion-item__trigger:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width));border-radius:var(--mk-radius-sm)}.mk-accordion-item__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-accordion-item__label{display:inline-flex;align-items:center;gap:var(--mk-space-2);min-width:0}.mk-accordion-item__chevron{display:inline-flex;flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-lg);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-accordion-item--open) .mk-accordion-item__chevron{transform:rotate(180deg)}.mk-accordion-item__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-accordion-item--open) .mk-accordion-item__panel{grid-template-rows:1fr}.mk-accordion-item__panel-inner{overflow:hidden}.mk-accordion-item__body{padding:0 var(--mk-space-4) var(--mk-space-4);color:var(--mk-text);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal)}@media(prefers-reduced-motion:reduce){.mk-accordion-item__panel,.mk-accordion-item__chevron{transition:none}}\n"] }]
244
+ }], propDecorators: { header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
245
+
246
+ /**
247
+ * Marks projected content as the header of an `<mk-accordion-item>`. Use when
248
+ * the header needs rich markup instead of the plain-text `header` input.
249
+ *
250
+ * ```html
251
+ * <mk-accordion-item>
252
+ * <span mkAccordionHeader><mk-icon name="user" /> Account</span>
253
+ * …body…
254
+ * </mk-accordion-item>
255
+ * ```
256
+ */
257
+ class MkAccordionHeader {
258
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordionHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
259
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MkAccordionHeader, isStandalone: true, selector: "[mkAccordionHeader]", ngImport: i0 });
260
+ }
261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAccordionHeader, decorators: [{
262
+ type: Directive,
263
+ args: [{
264
+ selector: '[mkAccordionHeader]',
265
+ }]
266
+ }] });
267
+
268
+ /**
269
+ * A single crumb inside `<mk-breadcrumb>`. Renders a link when `href` is set,
270
+ * otherwise plain text. The last item is automatically marked
271
+ * `aria-current="page"` by the parent.
272
+ *
273
+ * ```html
274
+ * <mk-breadcrumb-item href="/">Home</mk-breadcrumb-item>
275
+ * <mk-breadcrumb-item>Current page</mk-breadcrumb-item>
276
+ * ```
277
+ */
278
+ class MkBreadcrumbItem {
279
+ /** Optional link target. Rendered as text when omitted or when it is last. */
280
+ href = input(/* @ts-ignore */
281
+ ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
282
+ /** Whether this is the final crumb. Set by the parent `MkBreadcrumb`. */
283
+ last = signal(false, /* @ts-ignore */
284
+ ...(ngDevMode ? [{ debugName: "last" }] : /* istanbul ignore next */ []));
285
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBreadcrumbItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
286
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkBreadcrumbItem, isStandalone: true, selector: "mk-breadcrumb-item", inputs: { href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listitem" }, classAttribute: "mk-breadcrumb-item" }, ngImport: i0, template: "<!-- A single ng-content captured in a template: the default projection slot\n belongs to exactly one ng-content, so placing one in each branch leaves\n whichever branch Angular didn't pick rendering empty. -->\n<ng-template #content><ng-content /></ng-template>\n\n@if (href() && !last()) {\n <a class=\"mk-breadcrumb-item__link\" [href]=\"href()\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <span\n class=\"mk-breadcrumb-item__current\"\n [attr.aria-current]=\"last() ? 'page' : null\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center;min-width:0}:host(:not(:first-of-type)):before{content:var(--mk-breadcrumb-separator, \"/\");margin:0 var(--mk-space-2);color:var(--mk-text-subtle);-webkit-user-select:none;user-select:none}.mk-breadcrumb-item__link{color:var(--mk-text-muted);text-decoration:none;border-radius:var(--mk-radius-xs);transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-breadcrumb-item__link:hover{color:var(--mk-text);text-decoration:underline}.mk-breadcrumb-item__link:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-breadcrumb-item__current{color:var(--mk-text);font-weight:var(--mk-font-weight-medium);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
287
+ }
288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBreadcrumbItem, decorators: [{
289
+ type: Component,
290
+ args: [{ selector: 'mk-breadcrumb-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
291
+ class: 'mk-breadcrumb-item',
292
+ role: 'listitem',
293
+ }, template: "<!-- A single ng-content captured in a template: the default projection slot\n belongs to exactly one ng-content, so placing one in each branch leaves\n whichever branch Angular didn't pick rendering empty. -->\n<ng-template #content><ng-content /></ng-template>\n\n@if (href() && !last()) {\n <a class=\"mk-breadcrumb-item__link\" [href]=\"href()\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <span\n class=\"mk-breadcrumb-item__current\"\n [attr.aria-current]=\"last() ? 'page' : null\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center;min-width:0}:host(:not(:first-of-type)):before{content:var(--mk-breadcrumb-separator, \"/\");margin:0 var(--mk-space-2);color:var(--mk-text-subtle);-webkit-user-select:none;user-select:none}.mk-breadcrumb-item__link{color:var(--mk-text-muted);text-decoration:none;border-radius:var(--mk-radius-xs);transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-breadcrumb-item__link:hover{color:var(--mk-text);text-decoration:underline}.mk-breadcrumb-item__link:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-breadcrumb-item__current{color:var(--mk-text);font-weight:var(--mk-font-weight-medium);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
294
+ }], propDecorators: { href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }] } });
295
+
296
+ /**
297
+ * Breadcrumb trail. Renders `nav[aria-label="Breadcrumb"] > ol` and marks the
298
+ * final `<mk-breadcrumb-item>` as the current page. The separator is drawn in
299
+ * CSS and can be customised via `separator`.
300
+ *
301
+ * ```html
302
+ * <mk-breadcrumb separator="›">
303
+ * <mk-breadcrumb-item href="/">Home</mk-breadcrumb-item>
304
+ * <mk-breadcrumb-item href="/users">Users</mk-breadcrumb-item>
305
+ * <mk-breadcrumb-item>Jane</mk-breadcrumb-item>
306
+ * </mk-breadcrumb>
307
+ * ```
308
+ */
309
+ class MkBreadcrumb {
310
+ i18n = inject(MK_I18N);
311
+ /** Accessible label for the navigation landmark. */
312
+ label = input(this.i18n.breadcrumbLabel, /* @ts-ignore */
313
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
314
+ /** Separator character rendered between crumbs. */
315
+ separator = input('/', /* @ts-ignore */
316
+ ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
317
+ items = contentChildren(MkBreadcrumbItem, /* @ts-ignore */
318
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
319
+ /** Wrap the separator in quotes for the CSS `content` property. */
320
+ separatorValue = computed(() => `"${this.separator().replace(/"/g, '\\"')}"`, /* @ts-ignore */
321
+ ...(ngDevMode ? [{ debugName: "separatorValue" }] : /* istanbul ignore next */ []));
322
+ constructor() {
323
+ // Flag the last crumb so it renders as the current page.
324
+ effect(() => {
325
+ const items = this.items();
326
+ items.forEach((item, i) => item.last.set(i === items.length - 1));
327
+ });
328
+ }
329
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBreadcrumb, deps: [], target: i0.ɵɵFactoryTarget.Component });
330
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkBreadcrumb, isStandalone: true, selector: "mk-breadcrumb", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--mk-breadcrumb-separator": "separatorValue()" }, classAttribute: "mk-breadcrumb" }, queries: [{ propertyName: "items", predicate: MkBreadcrumbItem, isSignal: true }], ngImport: i0, template: "<nav [attr.aria-label]=\"label()\">\n <ol class=\"mk-breadcrumb__list\" role=\"list\">\n <ng-content />\n </ol>\n</nav>\n", styles: [":host{display:block}.mk-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:0;margin:0;padding:0;list-style:none;font-size:var(--mk-font-size-sm);line-height:var(--mk-line-height-normal)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
331
+ }
332
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBreadcrumb, decorators: [{
333
+ type: Component,
334
+ args: [{ selector: 'mk-breadcrumb', changeDetection: ChangeDetectionStrategy.OnPush, host: {
335
+ class: 'mk-breadcrumb',
336
+ '[style.--mk-breadcrumb-separator]': 'separatorValue()',
337
+ }, template: "<nav [attr.aria-label]=\"label()\">\n <ol class=\"mk-breadcrumb__list\" role=\"list\">\n <ng-content />\n </ol>\n</nav>\n", styles: [":host{display:block}.mk-breadcrumb__list{display:flex;flex-wrap:wrap;align-items:center;gap:0;margin:0;padding:0;list-style:none;font-size:var(--mk-font-size-sm);line-height:var(--mk-line-height-normal)}\n"] }]
338
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkBreadcrumbItem), { isSignal: true }] }] } });
339
+
340
+ /**
341
+ * Page navigation with previous/next controls and numbered pages that collapse
342
+ * to ellipses for large ranges. Provide either `total` (+ `pageSize`) or a
343
+ * direct `pageCount`. `page` is 1-based and two-way bindable.
344
+ *
345
+ * ```html
346
+ * <mk-pagination
347
+ * [total]="240"
348
+ * [pageSize]="20"
349
+ * [(page)]="page"
350
+ * (pageChange)="load($event)"
351
+ * />
352
+ * ```
353
+ */
354
+ class MkPagination {
355
+ /** Localised strings (override globally via `provideMkI18n`). */
356
+ i18n = inject(MK_I18N);
357
+ /** Total number of items (used with `pageSize` when `pageCount` is unset). */
358
+ total = input(0, { ...(ngDevMode ? { debugName: "total" } : /* istanbul ignore next */ {}), transform: numberAttribute });
359
+ /** Items per page. */
360
+ pageSize = input(10, { ...(ngDevMode ? { debugName: "pageSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
361
+ /** Explicit page count; overrides `total`/`pageSize` when > 0. */
362
+ pageCount = input(0, { ...(ngDevMode ? { debugName: "pageCount" } : /* istanbul ignore next */ {}), transform: numberAttribute });
363
+ /** Current 1-based page (two-way). */
364
+ page = model(1, /* @ts-ignore */
365
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
366
+ /** Number of page buttons to keep on each side of the current page. */
367
+ siblingCount = input(1, { ...(ngDevMode ? { debugName: "siblingCount" } : /* istanbul ignore next */ {}), transform: numberAttribute });
368
+ /** Number of page buttons pinned at each end. */
369
+ boundaryCount = input(1, { ...(ngDevMode ? { debugName: "boundaryCount" } : /* istanbul ignore next */ {}), transform: numberAttribute });
370
+ /** Accessible label for the navigation landmark. */
371
+ label = input(this.i18n.paginationLabel, /* @ts-ignore */
372
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
373
+ /**
374
+ * `pageChange` is emitted automatically by the two-way `page` model whenever
375
+ * the page changes — bind it via `(pageChange)` or `[(page)]`.
376
+ */
377
+ /** Resolved total number of pages (at least 1). */
378
+ pages = computed(() => {
379
+ const explicit = this.pageCount();
380
+ if (explicit > 0)
381
+ return explicit;
382
+ const size = Math.max(1, this.pageSize());
383
+ return Math.max(1, Math.ceil(this.total() / size));
384
+ }, /* @ts-ignore */
385
+ ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
386
+ current = computed(() => Math.min(Math.max(1, this.page()), this.pages()), /* @ts-ignore */
387
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
388
+ hasPrev = computed(() => this.current() > 1, /* @ts-ignore */
389
+ ...(ngDevMode ? [{ debugName: "hasPrev" }] : /* istanbul ignore next */ []));
390
+ hasNext = computed(() => this.current() < this.pages(), /* @ts-ignore */
391
+ ...(ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []));
392
+ /** The sequence of page cells with ellipsis gaps. */
393
+ items = computed(() => {
394
+ const total = this.pages();
395
+ const boundary = Math.max(0, this.boundaryCount());
396
+ const sibling = Math.max(0, this.siblingCount());
397
+ const current = this.current();
398
+ // Total slots: boundaries + siblings + current + 2 ellipses + first/last.
399
+ const totalSlots = boundary * 2 + sibling * 2 + 3 + 2;
400
+ if (total <= totalSlots) {
401
+ return this.range(1, total);
402
+ }
403
+ const startPages = this.range(1, boundary);
404
+ const endPages = this.range(total - boundary + 1, total);
405
+ const siblingsStart = Math.max(Math.min(current - sibling, total - boundary - sibling * 2 - 1), boundary + 2);
406
+ const siblingsEnd = Math.min(Math.max(current + sibling, boundary + sibling * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : total - 1);
407
+ const result = [...startPages];
408
+ if (siblingsStart > boundary + 2) {
409
+ result.push('ellipsis');
410
+ }
411
+ else if (boundary + 1 < total - boundary) {
412
+ result.push(boundary + 1);
413
+ }
414
+ result.push(...this.range(siblingsStart, siblingsEnd));
415
+ if (siblingsEnd < total - boundary - 1) {
416
+ result.push('ellipsis');
417
+ }
418
+ else if (total - boundary > boundary) {
419
+ result.push(total - boundary);
420
+ }
421
+ result.push(...endPages);
422
+ return result;
423
+ }, /* @ts-ignore */
424
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
425
+ goTo(page) {
426
+ const target = Math.min(Math.max(1, page), this.pages());
427
+ if (target === this.current())
428
+ return;
429
+ this.page.set(target);
430
+ }
431
+ prev() {
432
+ if (this.hasPrev())
433
+ this.goTo(this.current() - 1);
434
+ }
435
+ next() {
436
+ if (this.hasNext())
437
+ this.goTo(this.current() + 1);
438
+ }
439
+ range(start, end) {
440
+ const length = end - start + 1;
441
+ return length > 0 ? Array.from({ length }, (_, i) => start + i) : [];
442
+ }
443
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkPagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
444
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkPagination, isStandalone: true, selector: "mk-pagination", inputs: { total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageCount: { classPropertyName: "pageCount", publicName: "pageCount", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, siblingCount: { classPropertyName: "siblingCount", publicName: "siblingCount", isSignal: true, isRequired: false, transformFunction: null }, boundaryCount: { classPropertyName: "boundaryCount", publicName: "boundaryCount", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { page: "pageChange" }, host: { classAttribute: "mk-pagination" }, ngImport: i0, template: "<nav [attr.aria-label]=\"label()\">\n <ul class=\"mk-pagination__list\" role=\"list\">\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn mk-pagination__btn--nav\"\n [attr.aria-label]=\"i18n.previousPage\"\n [disabled]=\"!hasPrev()\"\n (click)=\"prev()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n </button>\n </li>\n\n @for (item of items(); track $index) {\n @if (item === 'ellipsis') {\n <li>\n <span class=\"mk-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n </li>\n } @else {\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn\"\n [class.mk-pagination__btn--active]=\"item === current()\"\n [attr.aria-label]=\"i18n.goToPage(item)\"\n [attr.aria-current]=\"item === current() ? 'page' : null\"\n (click)=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n }\n }\n\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn mk-pagination__btn--nav\"\n [attr.aria-label]=\"i18n.nextPage\"\n [disabled]=\"!hasNext()\"\n (click)=\"next()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </button>\n </li>\n </ul>\n</nav>\n", styles: [":host{display:block}.mk-pagination__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-1);margin:0;padding:0;list-style:none}.mk-pagination__btn{display:inline-flex;align-items:center;justify-content:center;min-width:var(--mk-control-height-md);height:var(--mk-control-height-md);padding:0 var(--mk-space-2);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-medium);line-height:1;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard),border-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-pagination__btn:hover:not(:disabled):not(.mk-pagination__btn--active){background:var(--mk-hover-overlay)}.mk-pagination__btn:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-pagination__btn:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-pagination__btn--nav{color:var(--mk-text-muted)}:host(:dir(rtl)) .mk-pagination__btn--nav svg{transform:scaleX(-1)}.mk-pagination__btn--active{background:var(--mk-primary);color:var(--mk-primary-contrast);cursor:default}.mk-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:var(--mk-control-height-md);height:var(--mk-control-height-md);color:var(--mk-text-subtle);-webkit-user-select:none;user-select:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
445
+ }
446
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkPagination, decorators: [{
447
+ type: Component,
448
+ args: [{ selector: 'mk-pagination', changeDetection: ChangeDetectionStrategy.OnPush, host: {
449
+ class: 'mk-pagination',
450
+ }, template: "<nav [attr.aria-label]=\"label()\">\n <ul class=\"mk-pagination__list\" role=\"list\">\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn mk-pagination__btn--nav\"\n [attr.aria-label]=\"i18n.previousPage\"\n [disabled]=\"!hasPrev()\"\n (click)=\"prev()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"15 18 9 12 15 6\" />\n </svg>\n </button>\n </li>\n\n @for (item of items(); track $index) {\n @if (item === 'ellipsis') {\n <li>\n <span class=\"mk-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n </li>\n } @else {\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn\"\n [class.mk-pagination__btn--active]=\"item === current()\"\n [attr.aria-label]=\"i18n.goToPage(item)\"\n [attr.aria-current]=\"item === current() ? 'page' : null\"\n (click)=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n }\n }\n\n <li>\n <button\n type=\"button\"\n class=\"mk-pagination__btn mk-pagination__btn--nav\"\n [attr.aria-label]=\"i18n.nextPage\"\n [disabled]=\"!hasNext()\"\n (click)=\"next()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </button>\n </li>\n </ul>\n</nav>\n", styles: [":host{display:block}.mk-pagination__list{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-1);margin:0;padding:0;list-style:none}.mk-pagination__btn{display:inline-flex;align-items:center;justify-content:center;min-width:var(--mk-control-height-md);height:var(--mk-control-height-md);padding:0 var(--mk-space-2);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-medium);line-height:1;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard),border-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-pagination__btn:hover:not(:disabled):not(.mk-pagination__btn--active){background:var(--mk-hover-overlay)}.mk-pagination__btn:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-pagination__btn:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-pagination__btn--nav{color:var(--mk-text-muted)}:host(:dir(rtl)) .mk-pagination__btn--nav svg{transform:scaleX(-1)}.mk-pagination__btn--active{background:var(--mk-primary);color:var(--mk-primary-contrast);cursor:default}.mk-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:var(--mk-control-height-md);height:var(--mk-control-height-md);color:var(--mk-text-subtle);-webkit-user-select:none;user-select:none}\n"] }]
451
+ }], propDecorators: { total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageCount", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], boundaryCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundaryCount", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
452
+
453
+ /**
454
+ * An item within an `<mk-menu>`. Renders as an ARIA `menuitem` with an optional
455
+ * icon slot (`[mkMenuItemIcon]`), disabled and danger states, and either emits
456
+ * `action` or navigates when `href` is set. Activating closes the menu.
457
+ *
458
+ * ```html
459
+ * <mk-menu-item (action)="rename()">
460
+ * <svg mkMenuItemIcon>…</svg> Rename
461
+ * </mk-menu-item>
462
+ * <mk-menu-item danger (action)="remove()">Delete</mk-menu-item>
463
+ * <mk-menu-item href="/help">Help</mk-menu-item>
464
+ * ```
465
+ */
466
+ class MkMenuItem {
467
+ el = inject(ElementRef);
468
+ document = inject(DOCUMENT);
469
+ menu = inject(MkMenu, { optional: true });
470
+ /** Prevent selection and focus. */
471
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
472
+ /** Destructive styling (e.g. Delete). */
473
+ danger = input(false, { ...(ngDevMode ? { debugName: "danger" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
474
+ /** When set, activating the item navigates here. */
475
+ href = input(/* @ts-ignore */
476
+ ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
477
+ /** Emitted when the item is activated (not for disabled items). */
478
+ action = output();
479
+ activate(event) {
480
+ if (this.disabled()) {
481
+ event?.preventDefault();
482
+ event?.stopPropagation();
483
+ return;
484
+ }
485
+ this.action.emit();
486
+ const href = this.href();
487
+ this.menu?.close(true);
488
+ if (href) {
489
+ this.document.defaultView?.location.assign(href);
490
+ }
491
+ }
492
+ /** Move DOM focus to this item (roving focus). */
493
+ focusEl() {
494
+ this.el.nativeElement.focus();
495
+ }
496
+ /** Whether `node` lives inside this item. */
497
+ contains(node) {
498
+ return !!node && this.el.nativeElement.contains(node);
499
+ }
500
+ /** Lowercased text content, used for typeahead matching. */
501
+ text() {
502
+ return (this.el.nativeElement.textContent ?? '').trim().toLowerCase();
503
+ }
504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
505
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkMenuItem, isStandalone: true, selector: "mk-menu-item", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, danger: { classPropertyName: "danger", publicName: "danger", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, listeners: { "click": "activate($event)" }, properties: { "attr.aria-disabled": "disabled() || null", "class.mk-menu-item--danger": "danger()", "class.mk-menu-item--disabled": "disabled()" }, classAttribute: "mk-menu-item" }, ngImport: i0, template: "<span class=\"mk-menu-item__icon\" aria-hidden=\"true\">\n <ng-content select=\"[mkMenuItemIcon]\" />\n</span>\n<span class=\"mk-menu-item__label\">\n <ng-content />\n</span>\n", styles: [":host{display:flex;align-items:center;gap:var(--mk-space-2);min-height:36px;padding:var(--mk-space-2) var(--mk-space-3);border-radius:var(--mk-radius-sm);color:var(--mk-text);font-size:var(--mk-font-size-sm);line-height:var(--mk-line-height-snug);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}:host(:hover:not(.mk-menu-item--disabled)),:host(:focus){background:var(--mk-hover-overlay);outline:none}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}:host(.mk-menu-item--danger){color:var(--mk-danger-text)}:host(.mk-menu-item--danger:hover:not(.mk-menu-item--disabled)),:host(.mk-menu-item--danger:focus){background:var(--mk-danger-subtle);color:var(--mk-danger-subtle-text)}:host(.mk-menu-item--disabled){color:var(--mk-text-disabled);cursor:not-allowed;pointer-events:none}.mk-menu-item__icon{display:inline-flex;align-items:center;flex:none;font-size:var(--mk-font-size-md)}.mk-menu-item__icon:empty{display:none}.mk-menu-item__label{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenuItem, decorators: [{
508
+ type: Component,
509
+ args: [{ selector: 'mk-menu-item', changeDetection: ChangeDetectionStrategy.OnPush, host: {
510
+ class: 'mk-menu-item',
511
+ role: 'menuitem',
512
+ tabindex: '-1',
513
+ '[attr.aria-disabled]': 'disabled() || null',
514
+ '[class.mk-menu-item--danger]': 'danger()',
515
+ '[class.mk-menu-item--disabled]': 'disabled()',
516
+ '(click)': 'activate($event)',
517
+ }, template: "<span class=\"mk-menu-item__icon\" aria-hidden=\"true\">\n <ng-content select=\"[mkMenuItemIcon]\" />\n</span>\n<span class=\"mk-menu-item__label\">\n <ng-content />\n</span>\n", styles: [":host{display:flex;align-items:center;gap:var(--mk-space-2);min-height:36px;padding:var(--mk-space-2) var(--mk-space-3);border-radius:var(--mk-radius-sm);color:var(--mk-text);font-size:var(--mk-font-size-sm);line-height:var(--mk-line-height-snug);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}:host(:hover:not(.mk-menu-item--disabled)),:host(:focus){background:var(--mk-hover-overlay);outline:none}:host(:focus-visible){outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}:host(.mk-menu-item--danger){color:var(--mk-danger-text)}:host(.mk-menu-item--danger:hover:not(.mk-menu-item--disabled)),:host(.mk-menu-item--danger:focus){background:var(--mk-danger-subtle);color:var(--mk-danger-subtle-text)}:host(.mk-menu-item--disabled){color:var(--mk-text-disabled);cursor:not-allowed;pointer-events:none}.mk-menu-item__icon{display:inline-flex;align-items:center;flex:none;font-size:var(--mk-font-size-md)}.mk-menu-item__icon:empty{display:none}.mk-menu-item__label{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
518
+ }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], danger: [{ type: i0.Input, args: [{ isSignal: true, alias: "danger", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
519
+
520
+ /**
521
+ * Dropdown menu implementing the ARIA menu pattern (roving focus, Arrow / Home
522
+ * / End / typeahead, Enter/Space to activate). Attach it to a trigger with the
523
+ * `mkMenuTriggerFor` directive; the trigger controls opening and positioning.
524
+ *
525
+ * The panel is rendered in the browser top layer via {@link MkAnchoredPanel}, so
526
+ * it is never clipped by an ancestor's `overflow`/`transform` and always flips
527
+ * back on-screen near a viewport edge.
528
+ *
529
+ * ```html
530
+ * <button mkButton [mkMenuTriggerFor]="menu">Actions</button>
531
+ * <mk-menu #menu>
532
+ * <mk-menu-item (action)="edit()">Edit</mk-menu-item>
533
+ * <mk-menu-item danger (action)="del()">Delete</mk-menu-item>
534
+ * </mk-menu>
535
+ * ```
536
+ */
537
+ class MkMenu {
538
+ document = inject(DOCUMENT);
539
+ injector = inject(Injector);
540
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
541
+ items = contentChildren(MkMenuItem, /* @ts-ignore */
542
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
543
+ /** Stable id for `aria-controls` on the trigger. */
544
+ panelId = mkUniqueId('mk-menu');
545
+ _open = signal(false, /* @ts-ignore */
546
+ ...(ngDevMode ? [{ debugName: "_open" }] : /* istanbul ignore next */ []));
547
+ /** Whether the menu is currently open. */
548
+ opened = this._open.asReadonly();
549
+ /** Accessible label for the menu (defaults via the trigger if unset). */
550
+ ariaLabel = signal(undefined, /* @ts-ignore */
551
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
552
+ /** Element to anchor the panel to (element-anchored open). */
553
+ anchorEl = signal(undefined, /* @ts-ignore */
554
+ ...(ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []));
555
+ /** Viewport point to anchor to (`openAt` / context menu). */
556
+ anchorPoint = signal(undefined, /* @ts-ignore */
557
+ ...(ngDevMode ? [{ debugName: "anchorPoint" }] : /* istanbul ignore next */ []));
558
+ triggerEl = null;
559
+ typeahead = '';
560
+ typeaheadTimer;
561
+ /**
562
+ * Open the menu anchored to `trigger`. `focus` picks the item that receives
563
+ * focus once the panel is painted: `true`/`'first'` for the first enabled
564
+ * item, `'last'` for the last (ArrowUp on a menu button, per the APG
565
+ * menu-button pattern), `false` to leave focus where it is (mouse open).
566
+ */
567
+ open(trigger, focus = true) {
568
+ if (!this.isBrowser || this._open())
569
+ return;
570
+ this.triggerEl = trigger;
571
+ this.anchorEl.set(trigger);
572
+ this.anchorPoint.set(undefined);
573
+ this._open.set(true);
574
+ if (focus)
575
+ this.focusAfterOpen(focus === 'last' ? 'last' : 'first');
576
+ }
577
+ /**
578
+ * Open the menu at viewport coordinates — e.g. a right-click point from a
579
+ * `contextmenu` event. Positioning (and flip/shift back on-screen) is handled
580
+ * by {@link MkAnchoredPanel} via the point anchor. Focus moves to the first
581
+ * item; `restoreFocusEl` (when provided) regains focus on close.
582
+ */
583
+ openAt(x, y, restoreFocusEl) {
584
+ if (!this.isBrowser || this._open())
585
+ return;
586
+ this.triggerEl = restoreFocusEl ?? null;
587
+ this.anchorEl.set(undefined);
588
+ this.anchorPoint.set({ x, y });
589
+ this._open.set(true);
590
+ this.focusAfterOpen('first');
591
+ }
592
+ /** Close the menu; optionally restore focus to the trigger. */
593
+ close(restoreFocus = true) {
594
+ if (!this._open())
595
+ return;
596
+ this._open.set(false);
597
+ this.anchorEl.set(undefined);
598
+ this.anchorPoint.set(undefined);
599
+ const trigger = this.triggerEl;
600
+ this.triggerEl = null;
601
+ if (restoreFocus)
602
+ trigger?.focus();
603
+ }
604
+ /** Toggle open/closed from a trigger. */
605
+ toggle(trigger, focusFirst = true) {
606
+ if (this._open()) {
607
+ this.close(true);
608
+ }
609
+ else {
610
+ this.open(trigger, focusFirst);
611
+ }
612
+ }
613
+ /**
614
+ * Move focus to the first enabled item. Used by the trigger when the menu is
615
+ * already open (e.g. ArrowDown after a mouse open), so arrow keys always
616
+ * reach the items even though focus never left the trigger.
617
+ */
618
+ focusFirstItem() {
619
+ this.focusFirst();
620
+ }
621
+ /** Move focus to the last enabled item (ArrowUp from the trigger). */
622
+ focusLastItem() {
623
+ this.focusLast();
624
+ }
625
+ /** Focus an item once the panel is in the top layer and painted. */
626
+ focusAfterOpen(which) {
627
+ afterNextRender({
628
+ write: () => {
629
+ if (which === 'last')
630
+ this.focusLast();
631
+ else
632
+ this.focusFirst();
633
+ },
634
+ }, { injector: this.injector });
635
+ }
636
+ onPanelKeydown(event) {
637
+ switch (event.key) {
638
+ case 'ArrowDown':
639
+ event.preventDefault();
640
+ this.move(1);
641
+ break;
642
+ case 'ArrowUp':
643
+ event.preventDefault();
644
+ this.move(-1);
645
+ break;
646
+ case 'Home':
647
+ event.preventDefault();
648
+ this.focusFirst();
649
+ break;
650
+ case 'End':
651
+ event.preventDefault();
652
+ this.focusLast();
653
+ break;
654
+ case 'Enter':
655
+ case ' ':
656
+ event.preventDefault();
657
+ this.activeItem()?.activate();
658
+ break;
659
+ case 'Escape':
660
+ event.preventDefault();
661
+ this.close(true);
662
+ break;
663
+ case 'Tab':
664
+ event.preventDefault();
665
+ this.close(true);
666
+ break;
667
+ default:
668
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
669
+ this.onTypeahead(event.key);
670
+ }
671
+ }
672
+ }
673
+ enabled() {
674
+ return this.items().filter((i) => !i.disabled());
675
+ }
676
+ activeItem() {
677
+ const active = this.document.activeElement;
678
+ return this.enabled().find((i) => i.contains(active));
679
+ }
680
+ currentIndex() {
681
+ const active = this.document.activeElement;
682
+ return this.enabled().findIndex((i) => i.contains(active));
683
+ }
684
+ move(delta) {
685
+ const items = this.enabled();
686
+ if (items.length === 0)
687
+ return;
688
+ const current = this.currentIndex();
689
+ const next = current < 0
690
+ ? delta > 0
691
+ ? 0
692
+ : items.length - 1
693
+ : (current + delta + items.length) % items.length;
694
+ items[next].focusEl();
695
+ }
696
+ focusFirst() {
697
+ this.enabled()[0]?.focusEl();
698
+ }
699
+ focusLast() {
700
+ const items = this.enabled();
701
+ items[items.length - 1]?.focusEl();
702
+ }
703
+ onTypeahead(char) {
704
+ this.typeahead += char.toLowerCase();
705
+ if (this.typeaheadTimer)
706
+ clearTimeout(this.typeaheadTimer);
707
+ this.typeaheadTimer = setTimeout(() => (this.typeahead = ''), 500);
708
+ const items = this.enabled();
709
+ const start = Math.max(0, this.currentIndex());
710
+ for (let i = 1; i <= items.length; i++) {
711
+ const item = items[(start + i) % items.length];
712
+ if (item.text().startsWith(this.typeahead)) {
713
+ item.focusEl();
714
+ return;
715
+ }
716
+ }
717
+ }
718
+ ngOnDestroy() {
719
+ if (this.typeaheadTimer)
720
+ clearTimeout(this.typeaheadTimer);
721
+ }
722
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
723
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkMenu, isStandalone: true, selector: "mk-menu", host: { classAttribute: "mk-menu" }, queries: [{ propertyName: "items", predicate: MkMenuItem, isSignal: true }], exportAs: ["mkMenu"], ngImport: i0, template: "@if (opened()) {\n <div\n class=\"mk-menu__panel\"\n role=\"menu\"\n [id]=\"panelId\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"anchorEl()\"\n [anchorRect]=\"anchorPoint()\"\n placement=\"bottom-start\"\n (dismiss)=\"close(false)\"\n [attr.aria-label]=\"ariaLabel()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n <ng-content />\n </div>\n}\n", styles: [":host{display:contents}.mk-menu__panel{min-width:12rem;max-width:min(20rem,100vw - var(--mk-space-4));max-height:calc(100vh - var(--mk-space-8));max-height:calc(100dvh - var(--mk-space-8));overflow-y:auto;overscroll-behavior:contain;padding:var(--mk-space-1);background:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-lg);animation:mk-menu-in var(--mk-duration-fast) var(--mk-ease-out)}@keyframes mk-menu-in{0%{opacity:0;transform:translateY(calc(-1 * var(--mk-space-1))) scale(.98)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.mk-menu__panel{animation:none}}\n"], dependencies: [{ kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
724
+ }
725
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenu, decorators: [{
726
+ type: Component,
727
+ args: [{ selector: 'mk-menu', exportAs: 'mkMenu', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkAnchoredPanel], host: {
728
+ class: 'mk-menu',
729
+ }, template: "@if (opened()) {\n <div\n class=\"mk-menu__panel\"\n role=\"menu\"\n [id]=\"panelId\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"anchorEl()\"\n [anchorRect]=\"anchorPoint()\"\n placement=\"bottom-start\"\n (dismiss)=\"close(false)\"\n [attr.aria-label]=\"ariaLabel()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n <ng-content />\n </div>\n}\n", styles: [":host{display:contents}.mk-menu__panel{min-width:12rem;max-width:min(20rem,100vw - var(--mk-space-4));max-height:calc(100vh - var(--mk-space-8));max-height:calc(100dvh - var(--mk-space-8));overflow-y:auto;overscroll-behavior:contain;padding:var(--mk-space-1);background:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-lg);animation:mk-menu-in var(--mk-duration-fast) var(--mk-ease-out)}@keyframes mk-menu-in{0%{opacity:0;transform:translateY(calc(-1 * var(--mk-space-1))) scale(.98)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.mk-menu__panel{animation:none}}\n"] }]
730
+ }], propDecorators: { items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkMenuItem), { isSignal: true }] }] } });
731
+
732
+ /**
733
+ * Turns its host button into a trigger for an `<mk-menu>`. Wires
734
+ * `aria-haspopup="menu"` / `aria-expanded` / `aria-controls`, toggles on click,
735
+ * opens on ArrowDown / Enter / Space (focusing the first item) and on ArrowUp
736
+ * (focusing the last item, per the APG menu-button pattern), and closes on
737
+ * Escape. When the menu is already open — e.g. after a mouse click, which
738
+ * deliberately leaves focus on the trigger — ArrowDown moves focus to the
739
+ * first item and ArrowUp to the last, so the keyboard is never dead.
740
+ *
741
+ * ```html
742
+ * <button mkButton [mkMenuTriggerFor]="menu">Actions</button>
743
+ * <mk-menu #menu>…</mk-menu>
744
+ * ```
745
+ */
746
+ class MkMenuTrigger {
747
+ host = inject(ElementRef);
748
+ /** The menu instance to control. */
749
+ menu = input.required({ ...(ngDevMode ? { debugName: "menu" } : /* istanbul ignore next */ {}), alias: 'mkMenuTriggerFor' });
750
+ onClick(event) {
751
+ event.preventDefault();
752
+ this.menu().toggle(this.host.nativeElement, false);
753
+ }
754
+ onKeydown(event) {
755
+ const menu = this.menu();
756
+ switch (event.key) {
757
+ case 'ArrowDown':
758
+ case 'Enter':
759
+ case ' ':
760
+ event.preventDefault();
761
+ if (menu.opened()) {
762
+ // Already open (mouse open keeps focus on the trigger) — enter the
763
+ // menu at the top instead of swallowing the key.
764
+ menu.focusFirstItem();
765
+ }
766
+ else {
767
+ menu.open(this.host.nativeElement, 'first');
768
+ }
769
+ break;
770
+ case 'ArrowUp':
771
+ event.preventDefault();
772
+ if (menu.opened()) {
773
+ menu.focusLastItem();
774
+ }
775
+ else {
776
+ menu.open(this.host.nativeElement, 'last');
777
+ }
778
+ break;
779
+ case 'Escape':
780
+ if (menu.opened()) {
781
+ event.preventDefault();
782
+ menu.close(true);
783
+ }
784
+ break;
785
+ }
786
+ }
787
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
788
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: MkMenuTrigger, isStandalone: true, selector: "[mkMenuTriggerFor]", inputs: { menu: { classPropertyName: "menu", publicName: "mkMenuTriggerFor", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-haspopup": "menu" }, listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-expanded": "menu().opened()", "attr.aria-controls": "menu().panelId" } }, exportAs: ["mkMenuTrigger"], ngImport: i0 });
789
+ }
790
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMenuTrigger, decorators: [{
791
+ type: Directive,
792
+ args: [{
793
+ selector: '[mkMenuTriggerFor]',
794
+ exportAs: 'mkMenuTrigger',
795
+ host: {
796
+ 'aria-haspopup': 'menu',
797
+ '[attr.aria-expanded]': 'menu().opened()',
798
+ '[attr.aria-controls]': 'menu().panelId',
799
+ '(click)': 'onClick($event)',
800
+ '(keydown)': 'onKeydown($event)',
801
+ },
802
+ }]
803
+ }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkMenuTriggerFor", required: true }] }] } });
804
+
805
+ /** Viewport width (px) at/below which the sidebar becomes an off-canvas drawer. */
806
+ const MOBILE_BREAKPOINT = 1024;
807
+ /**
808
+ * Admin dashboard layout with a fixed header, a collapsible/responsive sidebar
809
+ * and a main content area. On small screens the sidebar becomes a focus-trapped
810
+ * off-canvas drawer with a scrim (Escape to close). Includes a skip-to-content
811
+ * link for keyboard users.
812
+ *
813
+ * ```html
814
+ * <mk-app-shell [(sidebarOpen)]="open" [(sidebarCollapsed)]="collapsed">
815
+ * <div mkAppHeader>
816
+ * <button mkButton iconOnly (click)="shell.toggleSidebar()">☰</button>
817
+ * </div>
818
+ * <mk-nav-list mkAppSidebar [collapsed]="collapsed()">…</mk-nav-list>
819
+ * <router-outlet />
820
+ * </mk-app-shell>
821
+ * ```
822
+ */
823
+ class MkAppShell {
824
+ document = inject(DOCUMENT);
825
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
826
+ i18n = inject(MK_I18N);
827
+ sidebarRef = viewChild('sidebar', /* @ts-ignore */
828
+ ...(ngDevMode ? [{ debugName: "sidebarRef" }] : /* istanbul ignore next */ []));
829
+ /** Whether the mobile drawer is open (two-way). */
830
+ sidebarOpen = model(false, /* @ts-ignore */
831
+ ...(ngDevMode ? [{ debugName: "sidebarOpen" }] : /* istanbul ignore next */ []));
832
+ /** Whether the desktop sidebar is collapsed to icons (two-way). */
833
+ sidebarCollapsed = model(false, /* @ts-ignore */
834
+ ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
835
+ /** True when the viewport is at/below the mobile breakpoint. */
836
+ isMobile = signal(false, /* @ts-ignore */
837
+ ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
838
+ focusTrap;
839
+ constructor() {
840
+ if (this.isBrowser) {
841
+ const mql = this.document.defaultView?.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`);
842
+ if (mql) {
843
+ this.isMobile.set(mql.matches);
844
+ this.mql = mql;
845
+ mql.addEventListener('change', this.onMediaChange);
846
+ }
847
+ }
848
+ // Trap focus inside the drawer while it is open on mobile.
849
+ effect(() => {
850
+ const el = this.sidebarRef()?.nativeElement;
851
+ const active = this.isMobile() && this.sidebarOpen();
852
+ this.focusTrap?.release();
853
+ this.focusTrap = undefined;
854
+ if (active && el) {
855
+ this.focusTrap = new MkFocusTrap(el);
856
+ this.focusTrap.activate();
857
+ }
858
+ });
859
+ }
860
+ mql;
861
+ onMediaChange = (e) => {
862
+ this.isMobile.set(e.matches);
863
+ if (!e.matches)
864
+ this.sidebarOpen.set(false);
865
+ };
866
+ ngOnDestroy() {
867
+ this.mql?.removeEventListener('change', this.onMediaChange);
868
+ this.focusTrap?.release();
869
+ this.focusTrap = undefined;
870
+ }
871
+ /** Toggle the mobile drawer open/closed. Handy for a hamburger button. */
872
+ toggleSidebar() {
873
+ this.sidebarOpen.update((v) => !v);
874
+ }
875
+ /** Open the mobile drawer. */
876
+ openSidebar() {
877
+ this.sidebarOpen.set(true);
878
+ }
879
+ /** Close the mobile drawer. */
880
+ closeSidebar() {
881
+ this.sidebarOpen.set(false);
882
+ }
883
+ /** Toggle the desktop collapsed (icon-only) state. */
884
+ toggleCollapsed() {
885
+ this.sidebarCollapsed.update((v) => !v);
886
+ }
887
+ onSidebarKeydown(event) {
888
+ if (event.key === 'Escape' && this.isMobile() && this.sidebarOpen()) {
889
+ event.preventDefault();
890
+ this.closeSidebar();
891
+ }
892
+ }
893
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAppShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
894
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkAppShell, isStandalone: true, selector: "mk-app-shell", inputs: { sidebarOpen: { classPropertyName: "sidebarOpen", publicName: "sidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, sidebarCollapsed: { classPropertyName: "sidebarCollapsed", publicName: "sidebarCollapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sidebarOpen: "sidebarOpenChange", sidebarCollapsed: "sidebarCollapsedChange" }, host: { properties: { "class.mk-app-shell--collapsed": "sidebarCollapsed() && !isMobile()", "class.mk-app-shell--mobile": "isMobile()", "class.mk-app-shell--drawer-open": "isMobile() && sidebarOpen()" }, classAttribute: "mk-app-shell" }, viewQueries: [{ propertyName: "sidebarRef", first: true, predicate: ["sidebar"], descendants: true, isSignal: true }], ngImport: i0, template: "<a class=\"mk-app-shell__skip\" href=\"#mk-app-main\">{{ i18n.skipToContent }}</a>\n\n<header class=\"mk-app-shell__header\" role=\"banner\">\n <ng-content select=\"[mkAppHeader]\" />\n</header>\n\n<div class=\"mk-app-shell__body\">\n <aside\n #sidebar\n class=\"mk-app-shell__sidebar\"\n [attr.role]=\"isMobile() ? 'dialog' : null\"\n [attr.aria-modal]=\"isMobile() && sidebarOpen() ? 'true' : null\"\n [attr.aria-label]=\"isMobile() ? 'Sidebar navigation' : null\"\n [attr.inert]=\"isMobile() && !sidebarOpen() ? '' : null\"\n (keydown)=\"onSidebarKeydown($event)\"\n >\n <nav class=\"mk-app-shell__nav\" [attr.aria-label]=\"i18n.primaryNav\">\n <ng-content select=\"[mkAppSidebar]\" />\n </nav>\n </aside>\n\n @if (isMobile() && sidebarOpen()) {\n <div\n class=\"mk-app-shell__scrim\"\n aria-hidden=\"true\"\n (click)=\"closeSidebar()\"\n ></div>\n }\n\n <main\n id=\"mk-app-main\"\n class=\"mk-app-shell__main\"\n role=\"main\"\n tabindex=\"-1\"\n >\n <ng-content />\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;min-height:100vh;min-height:100dvh;background:var(--mk-bg);color:var(--mk-text)}.mk-app-shell__header{position:sticky;top:0;z-index:var(--mk-z-sticky);display:flex;align-items:center;height:var(--mk-header-height);padding:0 calc(var(--mk-space-4) + env(safe-area-inset-right,0px)) 0 calc(var(--mk-space-4) + env(safe-area-inset-left,0px));background:var(--mk-surface);border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-app-shell__body{display:flex;align-items:stretch;min-height:calc(100vh - var(--mk-header-height));min-height:calc(100dvh - var(--mk-header-height))}.mk-app-shell__sidebar{flex:none;width:var(--mk-sidebar-width);background:var(--mk-surface);border-inline-end:var(--mk-border-width) solid var(--mk-border);align-self:flex-start;position:sticky;top:var(--mk-header-height);height:calc(100vh - var(--mk-header-height));height:calc(100dvh - var(--mk-header-height));overflow-y:auto;transition:width var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-app-shell--collapsed) .mk-app-shell__sidebar{width:var(--mk-sidebar-width-collapsed)}.mk-app-shell__nav{padding:var(--mk-space-3);padding-inline-start:calc(var(--mk-space-3) + env(safe-area-inset-left,0px))}.mk-app-shell__main{flex:1 1 auto;min-width:0;padding:var(--mk-space-6);overflow-x:auto}@media(max-width:640px){.mk-app-shell__main{padding:var(--mk-space-4)}}.mk-app-shell__main:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-app-shell__scrim{position:fixed;inset:var(--mk-header-height) 0 0;z-index:var(--mk-z-drawer);background:var(--mk-overlay-scrim);animation:mk-shell-fade var(--mk-duration-fast) var(--mk-ease-out)}.mk-app-shell__skip{position:fixed;top:var(--mk-space-2);inset-inline-start:var(--mk-space-2);z-index:var(--mk-z-toast);padding:var(--mk-space-2) var(--mk-space-4);background:var(--mk-surface);color:var(--mk-text);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-md);transform:translateY(-150%);transition:transform var(--mk-duration-fast) var(--mk-ease-out)}.mk-app-shell__skip:focus{transform:translateY(0)}:host(.mk-app-shell--mobile) .mk-app-shell__sidebar{position:fixed;top:var(--mk-header-height);bottom:0;inset-inline-start:0;z-index:calc(var(--mk-z-drawer) + 1);width:var(--mk-sidebar-width);transform:translate(-100%);box-shadow:var(--mk-shadow-xl);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-app-shell--mobile:dir(rtl)) .mk-app-shell__sidebar{transform:translate(100%)}:host(.mk-app-shell--mobile.mk-app-shell--drawer-open) .mk-app-shell__sidebar{transform:translate(0)}@keyframes mk-shell-fade{0%{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.mk-app-shell__sidebar,.mk-app-shell__skip,.mk-app-shell__scrim{transition:none;animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
895
+ }
896
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkAppShell, decorators: [{
897
+ type: Component,
898
+ args: [{ selector: 'mk-app-shell', changeDetection: ChangeDetectionStrategy.OnPush, host: {
899
+ class: 'mk-app-shell',
900
+ '[class.mk-app-shell--collapsed]': 'sidebarCollapsed() && !isMobile()',
901
+ '[class.mk-app-shell--mobile]': 'isMobile()',
902
+ '[class.mk-app-shell--drawer-open]': 'isMobile() && sidebarOpen()',
903
+ }, template: "<a class=\"mk-app-shell__skip\" href=\"#mk-app-main\">{{ i18n.skipToContent }}</a>\n\n<header class=\"mk-app-shell__header\" role=\"banner\">\n <ng-content select=\"[mkAppHeader]\" />\n</header>\n\n<div class=\"mk-app-shell__body\">\n <aside\n #sidebar\n class=\"mk-app-shell__sidebar\"\n [attr.role]=\"isMobile() ? 'dialog' : null\"\n [attr.aria-modal]=\"isMobile() && sidebarOpen() ? 'true' : null\"\n [attr.aria-label]=\"isMobile() ? 'Sidebar navigation' : null\"\n [attr.inert]=\"isMobile() && !sidebarOpen() ? '' : null\"\n (keydown)=\"onSidebarKeydown($event)\"\n >\n <nav class=\"mk-app-shell__nav\" [attr.aria-label]=\"i18n.primaryNav\">\n <ng-content select=\"[mkAppSidebar]\" />\n </nav>\n </aside>\n\n @if (isMobile() && sidebarOpen()) {\n <div\n class=\"mk-app-shell__scrim\"\n aria-hidden=\"true\"\n (click)=\"closeSidebar()\"\n ></div>\n }\n\n <main\n id=\"mk-app-main\"\n class=\"mk-app-shell__main\"\n role=\"main\"\n tabindex=\"-1\"\n >\n <ng-content />\n </main>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;min-height:100vh;min-height:100dvh;background:var(--mk-bg);color:var(--mk-text)}.mk-app-shell__header{position:sticky;top:0;z-index:var(--mk-z-sticky);display:flex;align-items:center;height:var(--mk-header-height);padding:0 calc(var(--mk-space-4) + env(safe-area-inset-right,0px)) 0 calc(var(--mk-space-4) + env(safe-area-inset-left,0px));background:var(--mk-surface);border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-app-shell__body{display:flex;align-items:stretch;min-height:calc(100vh - var(--mk-header-height));min-height:calc(100dvh - var(--mk-header-height))}.mk-app-shell__sidebar{flex:none;width:var(--mk-sidebar-width);background:var(--mk-surface);border-inline-end:var(--mk-border-width) solid var(--mk-border);align-self:flex-start;position:sticky;top:var(--mk-header-height);height:calc(100vh - var(--mk-header-height));height:calc(100dvh - var(--mk-header-height));overflow-y:auto;transition:width var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-app-shell--collapsed) .mk-app-shell__sidebar{width:var(--mk-sidebar-width-collapsed)}.mk-app-shell__nav{padding:var(--mk-space-3);padding-inline-start:calc(var(--mk-space-3) + env(safe-area-inset-left,0px))}.mk-app-shell__main{flex:1 1 auto;min-width:0;padding:var(--mk-space-6);overflow-x:auto}@media(max-width:640px){.mk-app-shell__main{padding:var(--mk-space-4)}}.mk-app-shell__main:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-app-shell__scrim{position:fixed;inset:var(--mk-header-height) 0 0;z-index:var(--mk-z-drawer);background:var(--mk-overlay-scrim);animation:mk-shell-fade var(--mk-duration-fast) var(--mk-ease-out)}.mk-app-shell__skip{position:fixed;top:var(--mk-space-2);inset-inline-start:var(--mk-space-2);z-index:var(--mk-z-toast);padding:var(--mk-space-2) var(--mk-space-4);background:var(--mk-surface);color:var(--mk-text);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-md);transform:translateY(-150%);transition:transform var(--mk-duration-fast) var(--mk-ease-out)}.mk-app-shell__skip:focus{transform:translateY(0)}:host(.mk-app-shell--mobile) .mk-app-shell__sidebar{position:fixed;top:var(--mk-header-height);bottom:0;inset-inline-start:0;z-index:calc(var(--mk-z-drawer) + 1);width:var(--mk-sidebar-width);transform:translate(-100%);box-shadow:var(--mk-shadow-xl);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-app-shell--mobile:dir(rtl)) .mk-app-shell__sidebar{transform:translate(100%)}:host(.mk-app-shell--mobile.mk-app-shell--drawer-open) .mk-app-shell__sidebar{transform:translate(0)}@keyframes mk-shell-fade{0%{opacity:0}to{opacity:1}}@media(prefers-reduced-motion:reduce){.mk-app-shell__sidebar,.mk-app-shell__skip,.mk-app-shell__scrim{transition:none;animation:none}}\n"] }]
904
+ }], ctorParameters: () => [], propDecorators: { sidebarRef: [{ type: i0.ViewChild, args: ['sidebar', { isSignal: true }] }], sidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarOpen", required: false }] }, { type: i0.Output, args: ["sidebarOpenChange"] }], sidebarCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarCollapsed", required: false }] }, { type: i0.Output, args: ["sidebarCollapsedChange"] }] } });
905
+
906
+ /**
907
+ * Sidebar navigation list. Hosts `<mk-nav-item>`s and, when `collapsed`, drives
908
+ * them into an icon-only rail (labels hidden, exposed via `title` tooltips).
909
+ * Designed to sit inside the `mkAppSidebar` slot of `<mk-app-shell>`.
910
+ *
911
+ * ```html
912
+ * <mk-nav-list [collapsed]="collapsed()">
913
+ * <mk-nav-item label="Dashboard" href="/" active>
914
+ * <svg mkNavIcon>…</svg>
915
+ * </mk-nav-item>
916
+ * </mk-nav-list>
917
+ * ```
918
+ */
919
+ class MkNavList {
920
+ /** Collapse items to icon-only. Read by descendant `MkNavItem`s. */
921
+ collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
922
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavList, deps: [], target: i0.ɵɵFactoryTarget.Component });
923
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkNavList, isStandalone: true, selector: "mk-nav-list", inputs: { collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.mk-nav-list--collapsed": "collapsed()" }, classAttribute: "mk-nav-list" }, ngImport: i0, template: "<ul class=\"mk-nav-list__items\" role=\"list\">\n <ng-content />\n</ul>\n", styles: [":host{display:block}.mk-nav-list__items{display:flex;flex-direction:column;gap:var(--mk-space-1);margin:0;padding:0;list-style:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
924
+ }
925
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavList, decorators: [{
926
+ type: Component,
927
+ args: [{ selector: 'mk-nav-list', changeDetection: ChangeDetectionStrategy.OnPush, host: {
928
+ class: 'mk-nav-list',
929
+ '[class.mk-nav-list--collapsed]': 'collapsed()',
930
+ }, template: "<ul class=\"mk-nav-list__items\" role=\"list\">\n <ng-content />\n</ul>\n", styles: [":host{display:block}.mk-nav-list__items{display:flex;flex-direction:column;gap:var(--mk-space-1);margin:0;padding:0;list-style:none}\n"] }]
931
+ }], propDecorators: { collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }] } });
932
+
933
+ /**
934
+ * A single sidebar entry: icon slot (`[mkNavIcon]`), label, optional badge and
935
+ * active state. Renders a link when `href` is set, otherwise a button that
936
+ * emits `action`. If it contains nested `<mk-nav-item>`s it becomes an
937
+ * expandable disclosure (`aria-expanded`). Collapses to an icon in a collapsed
938
+ * list, exposing the label as a tooltip — and because a sub-list can't be shown
939
+ * at rail width, a parent item there emits `action` (navigate) instead of
940
+ * toggling, so it never becomes a dead button.
941
+ *
942
+ * ```html
943
+ * <mk-nav-item label="Reports" active href="/reports" badge="3">
944
+ * <svg mkNavIcon>…</svg>
945
+ * </mk-nav-item>
946
+ * <mk-nav-item label="Settings">
947
+ * <svg mkNavIcon>…</svg>
948
+ * <mk-nav-item label="Team" href="/team" />
949
+ * </mk-nav-item>
950
+ * ```
951
+ */
952
+ class MkNavItem {
953
+ list = inject(MkNavList, { optional: true });
954
+ /** Visible label. Also used as the tooltip when collapsed. */
955
+ label = input('', /* @ts-ignore */
956
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
957
+ /** Link target. Renders an anchor instead of a button. */
958
+ href = input(/* @ts-ignore */
959
+ ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
960
+ /** Marks the current page (`aria-current="page"`). */
961
+ active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
962
+ /** Disable interaction. */
963
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
964
+ /** Optional badge (count or short text). */
965
+ badge = input(/* @ts-ignore */
966
+ ...(ngDevMode ? [undefined, { debugName: "badge" }] : /* istanbul ignore next */ []));
967
+ /** Whether nested children are expanded (two-way). */
968
+ expanded = model(false, /* @ts-ignore */
969
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
970
+ /** Emitted when a button-style item is activated. */
971
+ action = output();
972
+ /** Nested items projected inside this one. */
973
+ children = contentChildren(MkNavItem, /* @ts-ignore */
974
+ ...(ngDevMode ? [{ debugName: "children" }] : /* istanbul ignore next */ []));
975
+ hasChildren = computed(() => this.children().length > 0, /* @ts-ignore */
976
+ ...(ngDevMode ? [{ debugName: "hasChildren" }] : /* istanbul ignore next */ []));
977
+ /** Collapsed state inherited from the enclosing `MkNavList`. */
978
+ collapsed = computed(() => this.list?.collapsed() ?? false, /* @ts-ignore */
979
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
980
+ /** Id wiring the disclosure button to its sub-list. */
981
+ subId = mkUniqueId('mk-nav-sub');
982
+ toggle() {
983
+ if (this.disabled())
984
+ return;
985
+ // In a collapsed (icon-rail) list the sub-list cannot be shown, so flipping
986
+ // `expanded` would be an invisible no-op and the item would read as a dead
987
+ // button. Emit `action` instead, letting the host navigate to the section —
988
+ // the same thing a leaf item does. Hosts that don't bind `(action)` on
989
+ // parents simply keep the previous (no-op) behaviour.
990
+ if (this.collapsed()) {
991
+ this.action.emit();
992
+ return;
993
+ }
994
+ this.expanded.update((v) => !v);
995
+ }
996
+ activate() {
997
+ if (this.disabled())
998
+ return;
999
+ this.action.emit();
1000
+ }
1001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
1002
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkNavItem, isStandalone: true, selector: "mk-nav-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange", action: "action" }, host: { attributes: { "role": "listitem" }, properties: { "class.mk-nav-item--active": "active()", "class.mk-nav-item--collapsed": "collapsed()", "class.mk-nav-item--has-children": "hasChildren()" }, classAttribute: "mk-nav-item" }, queries: [{ propertyName: "children", predicate: MkNavItem, isSignal: true }], ngImport: i0, template: "<!-- Projected slots captured once: a projection slot belongs to exactly one\n ng-content, so referencing it directly from several @if branches leaves\n all but one branch empty. -->\n<ng-template #iconTpl>\n <span class=\"mk-nav-item__icon\" aria-hidden=\"true\">\n <ng-content select=\"[mkNavIcon]\" />\n </span>\n</ng-template>\n\n@if (hasChildren()) {\n <button\n type=\"button\"\n class=\"mk-nav-item__link\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"subId\"\n [attr.title]=\"collapsed() ? label() : null\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n <span class=\"mk-nav-item__chevron\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n </span>\n </button>\n\n <ul\n class=\"mk-nav-item__sub\"\n role=\"list\"\n [id]=\"subId\"\n [attr.hidden]=\"expanded() && !collapsed() ? null : ''\"\n >\n <ng-content select=\"mk-nav-item\" />\n </ul>\n} @else if (href()) {\n <a\n class=\"mk-nav-item__link\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"collapsed() ? label() : null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"mk-nav-item__link\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"collapsed() ? label() : null\"\n [disabled]=\"disabled()\"\n (click)=\"activate()\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n </button>\n}\n", styles: [":host{display:block}.mk-nav-item__link{display:flex;align-items:center;gap:var(--mk-space-3);width:100%;min-height:40px;padding:var(--mk-space-2) var(--mk-space-3);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);color:var(--mk-text-muted);font-family:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-medium);text-align:start;text-decoration:none;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-nav-item__link:hover{background:var(--mk-hover-overlay);color:var(--mk-text)}.mk-nav-item__link:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-nav-item__link:disabled,.mk-nav-item__link[aria-disabled=true]{color:var(--mk-text-disabled);cursor:not-allowed;pointer-events:none}:host(.mk-nav-item--active)>.mk-nav-item__link{background:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-nav-item__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1.25rem;height:1.25rem;font-size:var(--mk-font-size-lg)}.mk-nav-item__icon:empty{display:none}.mk-nav-item__label{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mk-nav-item__badge{flex:none;min-width:1.25rem;padding:0 var(--mk-space-2);background:var(--mk-primary-subtle);color:var(--mk-primary-subtle-text);border-radius:var(--mk-radius-pill);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);line-height:1.25rem;text-align:center}.mk-nav-item__chevron{display:inline-flex;flex:none;color:var(--mk-text-subtle);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-nav-item--has-children)>.mk-nav-item__link[aria-expanded=true] .mk-nav-item__chevron{transform:rotate(90deg)}:host(:dir(rtl)) .mk-nav-item__chevron{transform:scaleX(-1)}:host(.mk-nav-item--has-children:dir(rtl))>.mk-nav-item__link[aria-expanded=true] .mk-nav-item__chevron{transform:rotate(-90deg) scaleX(-1)}.mk-nav-item__sub[hidden]{display:none}.mk-nav-item__sub{display:flex;flex-direction:column;gap:var(--mk-space-1);margin-block:var(--mk-space-1);margin-inline:var(--mk-space-5) 0;padding-block:0;padding-inline:var(--mk-space-2) 0;border-inline-start:var(--mk-border-width) solid var(--mk-border);list-style:none}:host(.mk-nav-item--collapsed) .mk-nav-item__link{justify-content:center;padding:var(--mk-space-2)}:host(.mk-nav-item--collapsed) .mk-nav-item__label,:host(.mk-nav-item--collapsed) .mk-nav-item__badge,:host(.mk-nav-item--collapsed) .mk-nav-item__chevron{display:none}:host(.mk-nav-item--collapsed) .mk-nav-item__sub{display:none}@media(prefers-reduced-motion:reduce){.mk-nav-item__link,.mk-nav-item__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1003
+ }
1004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavItem, decorators: [{
1005
+ type: Component,
1006
+ args: [{ selector: 'mk-nav-item', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
1007
+ class: 'mk-nav-item',
1008
+ role: 'listitem',
1009
+ '[class.mk-nav-item--active]': 'active()',
1010
+ '[class.mk-nav-item--collapsed]': 'collapsed()',
1011
+ '[class.mk-nav-item--has-children]': 'hasChildren()',
1012
+ }, template: "<!-- Projected slots captured once: a projection slot belongs to exactly one\n ng-content, so referencing it directly from several @if branches leaves\n all but one branch empty. -->\n<ng-template #iconTpl>\n <span class=\"mk-nav-item__icon\" aria-hidden=\"true\">\n <ng-content select=\"[mkNavIcon]\" />\n </span>\n</ng-template>\n\n@if (hasChildren()) {\n <button\n type=\"button\"\n class=\"mk-nav-item__link\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"subId\"\n [attr.title]=\"collapsed() ? label() : null\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n <span class=\"mk-nav-item__chevron\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" width=\"1em\" height=\"1em\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n </span>\n </button>\n\n <ul\n class=\"mk-nav-item__sub\"\n role=\"list\"\n [id]=\"subId\"\n [attr.hidden]=\"expanded() && !collapsed() ? null : ''\"\n >\n <ng-content select=\"mk-nav-item\" />\n </ul>\n} @else if (href()) {\n <a\n class=\"mk-nav-item__link\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"collapsed() ? label() : null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.tabindex]=\"disabled() ? -1 : null\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"mk-nav-item__link\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"collapsed() ? label() : null\"\n [disabled]=\"disabled()\"\n (click)=\"activate()\"\n >\n <ng-container [ngTemplateOutlet]=\"iconTpl\" />\n <span class=\"mk-nav-item__label\">{{ label() }}</span>\n @if (badge() !== undefined) {\n <span class=\"mk-nav-item__badge\">{{ badge() }}</span>\n }\n </button>\n}\n", styles: [":host{display:block}.mk-nav-item__link{display:flex;align-items:center;gap:var(--mk-space-3);width:100%;min-height:40px;padding:var(--mk-space-2) var(--mk-space-3);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);color:var(--mk-text-muted);font-family:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-medium);text-align:start;text-decoration:none;cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-nav-item__link:hover{background:var(--mk-hover-overlay);color:var(--mk-text)}.mk-nav-item__link:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-nav-item__link:disabled,.mk-nav-item__link[aria-disabled=true]{color:var(--mk-text-disabled);cursor:not-allowed;pointer-events:none}:host(.mk-nav-item--active)>.mk-nav-item__link{background:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-nav-item__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1.25rem;height:1.25rem;font-size:var(--mk-font-size-lg)}.mk-nav-item__icon:empty{display:none}.mk-nav-item__label{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mk-nav-item__badge{flex:none;min-width:1.25rem;padding:0 var(--mk-space-2);background:var(--mk-primary-subtle);color:var(--mk-primary-subtle-text);border-radius:var(--mk-radius-pill);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);line-height:1.25rem;text-align:center}.mk-nav-item__chevron{display:inline-flex;flex:none;color:var(--mk-text-subtle);transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host(.mk-nav-item--has-children)>.mk-nav-item__link[aria-expanded=true] .mk-nav-item__chevron{transform:rotate(90deg)}:host(:dir(rtl)) .mk-nav-item__chevron{transform:scaleX(-1)}:host(.mk-nav-item--has-children:dir(rtl))>.mk-nav-item__link[aria-expanded=true] .mk-nav-item__chevron{transform:rotate(-90deg) scaleX(-1)}.mk-nav-item__sub[hidden]{display:none}.mk-nav-item__sub{display:flex;flex-direction:column;gap:var(--mk-space-1);margin-block:var(--mk-space-1);margin-inline:var(--mk-space-5) 0;padding-block:0;padding-inline:var(--mk-space-2) 0;border-inline-start:var(--mk-border-width) solid var(--mk-border);list-style:none}:host(.mk-nav-item--collapsed) .mk-nav-item__link{justify-content:center;padding:var(--mk-space-2)}:host(.mk-nav-item--collapsed) .mk-nav-item__label,:host(.mk-nav-item--collapsed) .mk-nav-item__badge,:host(.mk-nav-item--collapsed) .mk-nav-item__chevron{display:none}:host(.mk-nav-item--collapsed) .mk-nav-item__sub{display:none}@media(prefers-reduced-motion:reduce){.mk-nav-item__link,.mk-nav-item__chevron{transition:none}}\n"] }]
1013
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], action: [{ type: i0.Output, args: ["action"] }], children: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkNavItem), { isSignal: true }] }] } });
1014
+
1015
+ /**
1016
+ * A titled section within an `<mk-nav-list>` — the "MAIN" / "SETTINGS" groupings
1017
+ * common in admin/CMS sidebars. Static by default; set `collapsible` to make
1018
+ * the label a toggle that expands/collapses its items (`aria-expanded` +
1019
+ * region). In a collapsed icon rail the label becomes a divider.
1020
+ *
1021
+ * ```html
1022
+ * <mk-nav-list>
1023
+ * <mk-nav-group label="Main">
1024
+ * <mk-nav-item label="Dashboard" href="/" />
1025
+ * </mk-nav-group>
1026
+ * <mk-nav-group label="Settings" collapsible>
1027
+ * <mk-nav-item label="Team" href="/team" />
1028
+ * </mk-nav-group>
1029
+ * </mk-nav-list>
1030
+ * ```
1031
+ */
1032
+ class MkNavGroup {
1033
+ list = inject(MkNavList, { optional: true });
1034
+ /** Section title. */
1035
+ label = input('', /* @ts-ignore */
1036
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1037
+ /** Make the label a toggle that shows/hides the items. */
1038
+ collapsible = input(false, { ...(ngDevMode ? { debugName: "collapsible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1039
+ /** Whether the section is expanded (two-way; only meaningful when collapsible). */
1040
+ expanded = model(true, /* @ts-ignore */
1041
+ ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1042
+ /** Id of the items region (for `aria-controls`). */
1043
+ regionId = mkUniqueId('mk-nav-group');
1044
+ /** Whether the enclosing list is a collapsed icon rail. */
1045
+ railCollapsed = computed(() => this.list?.collapsed() ?? false, /* @ts-ignore */
1046
+ ...(ngDevMode ? [{ debugName: "railCollapsed" }] : /* istanbul ignore next */ []));
1047
+ /** Items are visible unless collapsible-and-closed. */
1048
+ itemsVisible = computed(() => !this.collapsible() || this.expanded(), /* @ts-ignore */
1049
+ ...(ngDevMode ? [{ debugName: "itemsVisible" }] : /* istanbul ignore next */ []));
1050
+ toggle() {
1051
+ if (this.collapsible())
1052
+ this.expanded.update((v) => !v);
1053
+ }
1054
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
1055
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkNavGroup, isStandalone: true, selector: "mk-nav-group", inputs: { label: { classPropertyName: "label", publicName: "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 } }, outputs: { expanded: "expandedChange" }, host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "label() || null", "class.mk-nav-group--collapsed-rail": "railCollapsed()", "class.mk-nav-group--closed": "collapsible() && !expanded()" }, classAttribute: "mk-nav-group" }, ngImport: i0, template: "@if (collapsible() && !railCollapsed()) {\n <button\n type=\"button\"\n class=\"mk-nav-group__header mk-nav-group__header--toggle\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"regionId\"\n (click)=\"toggle()\"\n >\n <span class=\"mk-nav-group__label\">{{ label() }}</span>\n <span class=\"mk-nav-group__chevron\" aria-hidden=\"true\"></span>\n </button>\n} @else if (!railCollapsed()) {\n <p class=\"mk-nav-group__header\">\n <span class=\"mk-nav-group__label\">{{ label() }}</span>\n </p>\n}\n\n<div\n class=\"mk-nav-group__items\"\n [id]=\"regionId\"\n [attr.hidden]=\"itemsVisible() ? null : ''\"\n>\n <ng-content />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host+:host{margin-top:var(--mk-space-3)}.mk-nav-group__header{display:flex;align-items:center;justify-content:space-between;gap:var(--mk-space-2);width:100%;margin:0;padding:var(--mk-space-1) var(--mk-space-3);background:transparent;border:0;color:var(--mk-text-subtle);font-family:inherit;font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase}.mk-nav-group__header--toggle{cursor:pointer;border-radius:var(--mk-radius-sm)}.mk-nav-group__header--toggle:hover{color:var(--mk-text-muted);background-color:var(--mk-hover-overlay)}.mk-nav-group__header--toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-nav-group__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-nav-group__chevron{flex:none;width:.4rem;height:.4rem;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg);transition:transform var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-nav-group--closed) .mk-nav-group__chevron{transform:rotate(-45deg)}:host(.mk-nav-group--closed:dir(rtl)) .mk-nav-group__chevron{transform:rotate(135deg)}.mk-nav-group__items{display:flex;flex-direction:column;gap:2px}.mk-nav-group__items[hidden]{display:none}:host(.mk-nav-group--collapsed-rail)+:host(.mk-nav-group--collapsed-rail){margin-top:var(--mk-space-2);padding-top:var(--mk-space-2);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}@media(prefers-reduced-motion:reduce){.mk-nav-group__chevron{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1056
+ }
1057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkNavGroup, decorators: [{
1058
+ type: Component,
1059
+ args: [{ selector: 'mk-nav-group', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1060
+ class: 'mk-nav-group',
1061
+ role: 'group',
1062
+ '[attr.aria-label]': 'label() || null',
1063
+ '[class.mk-nav-group--collapsed-rail]': 'railCollapsed()',
1064
+ '[class.mk-nav-group--closed]': 'collapsible() && !expanded()',
1065
+ }, template: "@if (collapsible() && !railCollapsed()) {\n <button\n type=\"button\"\n class=\"mk-nav-group__header mk-nav-group__header--toggle\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"regionId\"\n (click)=\"toggle()\"\n >\n <span class=\"mk-nav-group__label\">{{ label() }}</span>\n <span class=\"mk-nav-group__chevron\" aria-hidden=\"true\"></span>\n </button>\n} @else if (!railCollapsed()) {\n <p class=\"mk-nav-group__header\">\n <span class=\"mk-nav-group__label\">{{ label() }}</span>\n </p>\n}\n\n<div\n class=\"mk-nav-group__items\"\n [id]=\"regionId\"\n [attr.hidden]=\"itemsVisible() ? null : ''\"\n>\n <ng-content />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host+:host{margin-top:var(--mk-space-3)}.mk-nav-group__header{display:flex;align-items:center;justify-content:space-between;gap:var(--mk-space-2);width:100%;margin:0;padding:var(--mk-space-1) var(--mk-space-3);background:transparent;border:0;color:var(--mk-text-subtle);font-family:inherit;font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase}.mk-nav-group__header--toggle{cursor:pointer;border-radius:var(--mk-radius-sm)}.mk-nav-group__header--toggle:hover{color:var(--mk-text-muted);background-color:var(--mk-hover-overlay)}.mk-nav-group__header--toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-nav-group__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-nav-group__chevron{flex:none;width:.4rem;height:.4rem;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg);transition:transform var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-nav-group--closed) .mk-nav-group__chevron{transform:rotate(-45deg)}:host(.mk-nav-group--closed:dir(rtl)) .mk-nav-group__chevron{transform:rotate(135deg)}.mk-nav-group__items{display:flex;flex-direction:column;gap:2px}.mk-nav-group__items[hidden]{display:none}:host(.mk-nav-group--collapsed-rail)+:host(.mk-nav-group--collapsed-rail){margin-top:var(--mk-space-2);padding-top:var(--mk-space-2);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}@media(prefers-reduced-motion:reduce){.mk-nav-group__chevron{transition:none}}\n"] }]
1066
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "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"] }] } });
1067
+
1068
+ /**
1069
+ * A single step + its content panel. Declared inside `<mk-stepper>`; the
1070
+ * projected content becomes the step body while `label` (and optional
1071
+ * `description`) render in the step header.
1072
+ *
1073
+ * `completed` is a two-way model so a parent can gate a `linear` stepper:
1074
+ * `<mk-step label="Details" [(completed)]="detailsValid()">`.
1075
+ *
1076
+ * ```html
1077
+ * <mk-stepper linear>
1078
+ * <mk-step label="Account" [completed]="form.valid">…</mk-step>
1079
+ * <mk-step label="Payment" optional>…</mk-step>
1080
+ * </mk-stepper>
1081
+ * ```
1082
+ */
1083
+ class MkStep {
1084
+ /** Title shown in the step header. */
1085
+ label = input('', /* @ts-ignore */
1086
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1087
+ /** Optional secondary line under the label. */
1088
+ description = input('', /* @ts-ignore */
1089
+ ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
1090
+ /** Marks the step optional (skippable in a `linear` stepper). */
1091
+ optional = input(false, { ...(ngDevMode ? { debugName: "optional" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1092
+ /** Whether the step is complete (two-way; drives `linear` gating + the ✓). */
1093
+ completed = model(false, /* @ts-ignore */
1094
+ ...(ngDevMode ? [{ debugName: "completed" }] : /* istanbul ignore next */ []));
1095
+ /** Show the error state in the header (e.g. failed validation). */
1096
+ hasError = input(false, { ...(ngDevMode ? { debugName: "hasError" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1097
+ /** Allow returning to this step after it is completed. */
1098
+ editable = input(true, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1099
+ /** Stable id for the header button (wires `aria-labelledby`). */
1100
+ headerId = mkUniqueId('mk-step');
1101
+ /** Stable id for the panel (wires `aria-controls`). */
1102
+ panelId = mkUniqueId('mk-step-panel');
1103
+ /** Whether this step is currently selected. Set by the parent `MkStepper`. */
1104
+ active = signal(false, /* @ts-ignore */
1105
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
1106
+ /**
1107
+ * The step body, captured so `MkStepper` can render it in the position the
1108
+ * orientation calls for. The host element itself stays empty — the panel
1109
+ * wrapper (and its role/aria) belongs to the stepper, which is the only
1110
+ * place that knows whether this is a tab panel or a disclosure region.
1111
+ */
1112
+ content = viewChild.required(TemplateRef, /* @ts-ignore */
1113
+ ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
1114
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkStep, deps: [], target: i0.ɵɵFactoryTarget.Component });
1115
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MkStep, isStandalone: true, selector: "mk-step", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, optional: { classPropertyName: "optional", publicName: "optional", isSignal: true, isRequired: false, transformFunction: null }, completed: { classPropertyName: "completed", publicName: "completed", isSignal: true, isRequired: false, transformFunction: null }, hasError: { classPropertyName: "hasError", publicName: "hasError", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { completed: "completedChange" }, host: { classAttribute: "mk-step" }, viewQueries: [{ propertyName: "content", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n The body is captured as a TEMPLATE rather than projected in place, so the\n parent `MkStepper` can render it wherever the orientation requires \u2014 beneath\n the header rail when horizontal, or interleaved directly under its own header\n when vertical. Projecting it here would pin every panel below every header.\n-->\n<ng-template>\n <ng-content />\n</ng-template>\n", styles: [":host{display:block}:host([hidden]){display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1116
+ }
1117
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkStep, decorators: [{
1118
+ type: Component,
1119
+ args: [{ selector: 'mk-step', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1120
+ class: 'mk-step',
1121
+ }, template: "<!--\n The body is captured as a TEMPLATE rather than projected in place, so the\n parent `MkStepper` can render it wherever the orientation requires \u2014 beneath\n the header rail when horizontal, or interleaved directly under its own header\n when vertical. Projecting it here would pin every panel below every header.\n-->\n<ng-template>\n <ng-content />\n</ng-template>\n", styles: [":host{display:block}:host([hidden]){display:none}\n"] }]
1122
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], optional: [{ type: i0.Input, args: [{ isSignal: true, alias: "optional", required: false }] }], completed: [{ type: i0.Input, args: [{ isSignal: true, alias: "completed", required: false }] }, { type: i0.Output, args: ["completedChange"] }], hasError: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasError", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], content: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
1123
+
1124
+ /**
1125
+ * Stepper — walks a user through an ordered sequence of `<mk-step>`s with an
1126
+ * accessible header rail (ARIA `tablist` + roving tabindex, Arrow/Home/End
1127
+ * navigation) and a single visible panel.
1128
+ *
1129
+ * In `linear` mode a step is only reachable once every preceding required
1130
+ * (non-`optional`) step reports `completed`. Drive completion from your form
1131
+ * state and move with the `next()` / `previous()` / `reset()` API or by
1132
+ * clicking a reachable header.
1133
+ *
1134
+ * ```html
1135
+ * <mk-stepper #s linear [(selectedIndex)]="index">
1136
+ * <mk-step label="Account" [completed]="accountValid()">…</mk-step>
1137
+ * <mk-step label="Address" [completed]="addressValid()">…</mk-step>
1138
+ * <mk-step label="Review">…</mk-step>
1139
+ * </mk-stepper>
1140
+ * <button mkButton (click)="s.previous()">Back</button>
1141
+ * <button mkButton (click)="s.next()">Next</button>
1142
+ * ```
1143
+ */
1144
+ class MkStepper {
1145
+ i18n = inject(MK_I18N);
1146
+ /** Step hosts are projected on the server only — see stepper.html. */
1147
+ isServer = isPlatformServer(inject(PLATFORM_ID));
1148
+ /** Projected steps, kept live via a signal content query. */
1149
+ steps = contentChildren(MkStep, /* @ts-ignore */
1150
+ ...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
1151
+ /** Rendered header buttons, used to move focus. */
1152
+ headerButtons = viewChildren('stepBtn', /* @ts-ignore */
1153
+ ...(ngDevMode ? [{ debugName: "headerButtons" }] : /* istanbul ignore next */ []));
1154
+ /** Zero-based index of the active step (two-way). */
1155
+ selectedIndex = model(0, /* @ts-ignore */
1156
+ ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
1157
+ /** Header rail orientation. */
1158
+ orientation = input('horizontal', /* @ts-ignore */
1159
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
1160
+ /** Enforce sequential completion before a step becomes reachable. */
1161
+ linear = input(false, { ...(ngDevMode ? { debugName: "linear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1162
+ isHorizontal = computed(() => this.orientation() === 'horizontal', /* @ts-ignore */
1163
+ ...(ngDevMode ? [{ debugName: "isHorizontal" }] : /* istanbul ignore next */ []));
1164
+ /** Emits the newly selected index whenever the active step changes. */
1165
+ selectionChange = output();
1166
+ constructor() {
1167
+ // Keep each step's `active` flag in sync with the clamped selected index.
1168
+ effect(() => {
1169
+ const steps = this.steps();
1170
+ const index = this.clampedIndex();
1171
+ steps.forEach((step, i) => step.active.set(i === index));
1172
+ });
1173
+ }
1174
+ clampedIndex = computed(() => {
1175
+ const count = this.steps().length;
1176
+ if (count === 0)
1177
+ return 0;
1178
+ return Math.min(Math.max(this.selectedIndex(), 0), count - 1);
1179
+ }, /* @ts-ignore */
1180
+ ...(ngDevMode ? [{ debugName: "clampedIndex" }] : /* istanbul ignore next */ []));
1181
+ /** 1-based label for the header circle. */
1182
+ stepNumber(index) {
1183
+ return index + 1;
1184
+ }
1185
+ /**
1186
+ * Whether a step can be selected. In linear mode every earlier required step
1187
+ * must be completed; a completed step is reachable only when `editable`.
1188
+ */
1189
+ isReachable(index) {
1190
+ const steps = this.steps();
1191
+ if (index < 0 || index >= steps.length)
1192
+ return false;
1193
+ if (!this.linear())
1194
+ return true;
1195
+ if (index <= this.clampedIndex()) {
1196
+ // Going back is allowed only to editable steps.
1197
+ return steps[index].editable() || index === this.clampedIndex();
1198
+ }
1199
+ for (let i = 0; i < index; i++) {
1200
+ if (!steps[i].optional() && !steps[i].completed())
1201
+ return false;
1202
+ }
1203
+ return true;
1204
+ }
1205
+ select(index) {
1206
+ if (!this.isReachable(index))
1207
+ return;
1208
+ if (index === this.clampedIndex())
1209
+ return;
1210
+ this.selectedIndex.set(index);
1211
+ this.selectionChange.emit(index);
1212
+ }
1213
+ focusHeader(index) {
1214
+ this.headerButtons()[index]?.nativeElement.focus();
1215
+ }
1216
+ /** Advance to the next reachable step, if any. */
1217
+ next() {
1218
+ const from = this.clampedIndex();
1219
+ const target = from + 1;
1220
+ if (target < this.steps().length && this.isReachable(target)) {
1221
+ this.select(target);
1222
+ }
1223
+ }
1224
+ /** Return to the previous step, if any. */
1225
+ previous() {
1226
+ const target = this.clampedIndex() - 1;
1227
+ if (target >= 0)
1228
+ this.select(target);
1229
+ }
1230
+ /** Reset to the first step and clear every step's completed flag. */
1231
+ reset() {
1232
+ this.steps().forEach((s) => s.completed.set(false));
1233
+ this.selectedIndex.set(0);
1234
+ this.selectionChange.emit(0);
1235
+ }
1236
+ onKeydown(event, index) {
1237
+ const count = this.steps().length;
1238
+ let next = -1;
1239
+ switch (event.key) {
1240
+ case 'ArrowRight':
1241
+ case 'ArrowDown':
1242
+ next = this.nextFocusable(index, 1);
1243
+ break;
1244
+ case 'ArrowLeft':
1245
+ case 'ArrowUp':
1246
+ next = this.nextFocusable(index, -1);
1247
+ break;
1248
+ case 'Home':
1249
+ next = this.nextFocusable(-1, 1);
1250
+ break;
1251
+ case 'End':
1252
+ next = this.nextFocusable(count, -1);
1253
+ break;
1254
+ default:
1255
+ return;
1256
+ }
1257
+ if (next >= 0) {
1258
+ event.preventDefault();
1259
+ // Automatic activation (as in `mk-tabs`): selection follows focus for
1260
+ // reachable steps. `nextFocusable` already skips unreachable ones, so the
1261
+ // guard only matters if the step became unreachable in between.
1262
+ this.focusHeader(next);
1263
+ if (this.isReachable(next))
1264
+ this.select(next);
1265
+ }
1266
+ }
1267
+ /** Next header index (wrapping) whose step is reachable. */
1268
+ nextFocusable(from, step) {
1269
+ const count = this.steps().length;
1270
+ if (!count)
1271
+ return -1;
1272
+ for (let i = 1; i <= count; i++) {
1273
+ const idx = (((from + step * i) % count) + count) % count;
1274
+ if (this.isReachable(idx))
1275
+ return idx;
1276
+ }
1277
+ return -1;
1278
+ }
1279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkStepper, deps: [], target: i0.ɵɵFactoryTarget.Component });
1280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkStepper, isStandalone: true, selector: "mk-stepper", inputs: { selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, linear: { classPropertyName: "linear", publicName: "linear", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedIndex: "selectedIndexChange", selectionChange: "selectionChange" }, host: { properties: { "class.mk-stepper--horizontal": "orientation() === 'horizontal'", "class.mk-stepper--vertical": "orientation() === 'vertical'" }, classAttribute: "mk-stepper" }, queries: [{ propertyName: "steps", predicate: MkStep, isSignal: true }], viewQueries: [{ propertyName: "headerButtons", predicate: ["stepBtn"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Steps are content children whose bodies are TEMPLATES (see MkStep), so this\n view decides where each one lands. Horizontal keeps the classic split: one\n header rail, one panel area below it. Vertical INTERLEAVES header and body,\n the way a vertical stepper is expected to read.\n\n The ARIA model differs with it, deliberately. Horizontal is a tablist: tabs\n must be direct children of their tablist, which an interleaved layout cannot\n satisfy. Vertical is therefore a disclosure pattern instead \u2014 plain buttons\n with aria-expanded driving labelled regions. Same visuals, both valid.\n\n On the server the step hosts still have to be projected somewhere or the\n serializer has nowhere to put the projected nodes and hydration fails. They\n render empty (the body lives in a template), so this costs nothing visually.\n-->\n@if (isServer) {\n <ng-content />\n}\n\n@if (orientation() === 'horizontal') {\n <div class=\"mk-stepper__header\" role=\"tablist\" aria-orientation=\"horizontal\">\n @for (\n step of steps();\n track step.headerId;\n let i = $index;\n let last = $last\n ) {\n <ng-container\n [ngTemplateOutlet]=\"headerTpl\"\n [ngTemplateOutletContext]=\"{ step: step, i: i }\"\n />\n @if (!last) {\n <span class=\"mk-stepper__connector\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n\n <div class=\"mk-stepper__panels\">\n @for (step of steps(); track step.headerId) {\n <div\n class=\"mk-stepper__panel\"\n role=\"tabpanel\"\n [id]=\"step.panelId\"\n [attr.aria-labelledby]=\"step.headerId\"\n [attr.tabindex]=\"step.active() ? 0 : null\"\n [attr.inert]=\"step.active() ? null : ''\"\n [hidden]=\"!step.active()\"\n >\n <ng-container [ngTemplateOutlet]=\"step.content()\" />\n </div>\n }\n </div>\n} @else {\n @for (\n step of steps();\n track step.headerId;\n let i = $index;\n let last = $last\n ) {\n <div class=\"mk-stepper__row\">\n <ng-container\n [ngTemplateOutlet]=\"headerTpl\"\n [ngTemplateOutletContext]=\"{ step: step, i: i }\"\n />\n <!-- The rule down the side is this wrapper's left border, so it spans the\n expanded body and stops at the last step. -->\n <div\n class=\"mk-stepper__vline\"\n [class.mk-stepper__vline--last]=\"last\"\n [attr.inert]=\"step.active() ? null : ''\"\n >\n <div\n class=\"mk-stepper__panel\"\n role=\"region\"\n [id]=\"step.panelId\"\n [attr.aria-labelledby]=\"step.headerId\"\n [hidden]=\"!step.active()\"\n >\n <ng-container [ngTemplateOutlet]=\"step.content()\" />\n </div>\n </div>\n </div>\n }\n}\n\n<ng-template #headerTpl let-step=\"step\" let-i=\"i\">\n <button\n #stepBtn\n type=\"button\"\n class=\"mk-stepper__step\"\n [class.mk-stepper__step--active]=\"step.active()\"\n [class.mk-stepper__step--completed]=\"step.completed()\"\n [class.mk-stepper__step--error]=\"step.hasError()\"\n [id]=\"step.headerId\"\n [attr.role]=\"isHorizontal() ? 'tab' : null\"\n [attr.aria-controls]=\"step.panelId\"\n [attr.aria-selected]=\"isHorizontal() ? step.active() : null\"\n [attr.aria-posinset]=\"isHorizontal() ? i + 1 : null\"\n [attr.aria-setsize]=\"isHorizontal() ? steps().length : null\"\n [attr.aria-expanded]=\"isHorizontal() ? null : step.active()\"\n [attr.aria-current]=\"!isHorizontal() && step.active() ? 'step' : null\"\n [attr.aria-disabled]=\"!isReachable(i) || null\"\n [attr.tabindex]=\"step.active() ? 0 : -1\"\n [disabled]=\"!isReachable(i)\"\n (click)=\"select(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span class=\"mk-stepper__marker\" aria-hidden=\"true\">\n @if (step.hasError()) {\n <span class=\"mk-stepper__bang\">!</span>\n } @else if (step.completed()) {\n <span class=\"mk-stepper__check\"></span>\n } @else {\n {{ stepNumber(i) }}\n }\n </span>\n <span class=\"mk-stepper__labels\">\n <span class=\"mk-stepper__label\">\n {{ step.label() }}\n @if (step.optional()) {\n <span class=\"mk-stepper__optional\">{{ i18n.optional }}</span>\n }\n </span>\n @if (step.description()) {\n <span class=\"mk-stepper__description\">{{ step.description() }}</span>\n }\n </span>\n <!-- The marker above is aria-hidden, so the completed/error state needs a\n textual counterpart for assistive tech. -->\n @if (step.hasError()) {\n <span class=\"mk-visually-hidden\">{{ i18n.stepError }}</span>\n } @else if (step.completed()) {\n <span class=\"mk-visually-hidden\">{{ i18n.stepCompleted }}</span>\n }\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-stepper__header{display:flex;align-items:center;overflow-x:auto;scrollbar-width:none;padding-block:calc(var(--mk-focus-ring-width) + var(--mk-focus-ring-offset))}.mk-stepper__header::-webkit-scrollbar{display:none}.mk-stepper__step{display:inline-flex;align-items:center;gap:var(--mk-space-2);flex:none;padding:var(--mk-space-2) var(--mk-space-3);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);font-family:inherit;color:var(--mk-text-muted);text-align:start;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-stepper__step:hover:not(:disabled){color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-stepper__step:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-stepper__step:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-stepper__step--active{color:var(--mk-text)}.mk-stepper__marker{display:grid;place-items:center;flex:none;width:1.75rem;height:1.75rem;border-radius:var(--mk-radius-circle);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-semibold);transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-stepper__step--active .mk-stepper__marker{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-stepper__step--completed:not(.mk-stepper__step--active) .mk-stepper__marker{background-color:var(--mk-primary-subtle);color:var(--mk-primary-subtle-text)}.mk-stepper__step--error .mk-stepper__marker{background-color:var(--mk-danger-text);color:var(--mk-danger-contrast)}.mk-stepper__bang{line-height:1}.mk-stepper__check{width:.4rem;height:.7rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg) translateY(-1px)}.mk-stepper__labels{display:flex;flex-direction:column;gap:2px;min-width:0}.mk-stepper__label{display:inline-flex;align-items:center;gap:var(--mk-space-2);font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium)}.mk-stepper__step--error .mk-stepper__label{color:var(--mk-danger-text)}.mk-stepper__optional{font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-normal);color:var(--mk-text-subtle)}.mk-stepper__description{font-size:var(--mk-font-size-sm);color:var(--mk-text-subtle)}.mk-stepper__connector{flex:1 1 auto;min-width:var(--mk-space-4);height:var(--mk-border-width);margin:0 var(--mk-space-2);background-color:var(--mk-border)}.mk-stepper__panels{padding-top:var(--mk-space-4)}:host(.mk-stepper--vertical) .mk-stepper__row{display:flex;flex-direction:column;align-items:stretch}:host(.mk-stepper--vertical) .mk-stepper__step{align-self:stretch;justify-content:flex-start;text-align:start}:host(.mk-stepper--vertical) .mk-stepper__vline{margin-inline-start:calc(var(--mk-space-3) + .875rem);border-inline-start:var(--mk-border-width) solid var(--mk-border);padding-inline-start:calc(var(--mk-space-4) - var(--mk-border-width));min-height:var(--mk-space-4)}:host(.mk-stepper--vertical) .mk-stepper__vline--last{border-inline-start-color:transparent;min-height:0}:host(.mk-stepper--vertical) .mk-stepper__panel:not([hidden]){padding-block:var(--mk-space-3)}@media(prefers-reduced-motion:reduce){.mk-stepper__step,.mk-stepper__marker{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1281
+ }
1282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkStepper, decorators: [{
1283
+ type: Component,
1284
+ args: [{ selector: 'mk-stepper', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1285
+ class: 'mk-stepper',
1286
+ '[class.mk-stepper--horizontal]': "orientation() === 'horizontal'",
1287
+ '[class.mk-stepper--vertical]': "orientation() === 'vertical'",
1288
+ }, template: "<!--\n Steps are content children whose bodies are TEMPLATES (see MkStep), so this\n view decides where each one lands. Horizontal keeps the classic split: one\n header rail, one panel area below it. Vertical INTERLEAVES header and body,\n the way a vertical stepper is expected to read.\n\n The ARIA model differs with it, deliberately. Horizontal is a tablist: tabs\n must be direct children of their tablist, which an interleaved layout cannot\n satisfy. Vertical is therefore a disclosure pattern instead \u2014 plain buttons\n with aria-expanded driving labelled regions. Same visuals, both valid.\n\n On the server the step hosts still have to be projected somewhere or the\n serializer has nowhere to put the projected nodes and hydration fails. They\n render empty (the body lives in a template), so this costs nothing visually.\n-->\n@if (isServer) {\n <ng-content />\n}\n\n@if (orientation() === 'horizontal') {\n <div class=\"mk-stepper__header\" role=\"tablist\" aria-orientation=\"horizontal\">\n @for (\n step of steps();\n track step.headerId;\n let i = $index;\n let last = $last\n ) {\n <ng-container\n [ngTemplateOutlet]=\"headerTpl\"\n [ngTemplateOutletContext]=\"{ step: step, i: i }\"\n />\n @if (!last) {\n <span class=\"mk-stepper__connector\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n\n <div class=\"mk-stepper__panels\">\n @for (step of steps(); track step.headerId) {\n <div\n class=\"mk-stepper__panel\"\n role=\"tabpanel\"\n [id]=\"step.panelId\"\n [attr.aria-labelledby]=\"step.headerId\"\n [attr.tabindex]=\"step.active() ? 0 : null\"\n [attr.inert]=\"step.active() ? null : ''\"\n [hidden]=\"!step.active()\"\n >\n <ng-container [ngTemplateOutlet]=\"step.content()\" />\n </div>\n }\n </div>\n} @else {\n @for (\n step of steps();\n track step.headerId;\n let i = $index;\n let last = $last\n ) {\n <div class=\"mk-stepper__row\">\n <ng-container\n [ngTemplateOutlet]=\"headerTpl\"\n [ngTemplateOutletContext]=\"{ step: step, i: i }\"\n />\n <!-- The rule down the side is this wrapper's left border, so it spans the\n expanded body and stops at the last step. -->\n <div\n class=\"mk-stepper__vline\"\n [class.mk-stepper__vline--last]=\"last\"\n [attr.inert]=\"step.active() ? null : ''\"\n >\n <div\n class=\"mk-stepper__panel\"\n role=\"region\"\n [id]=\"step.panelId\"\n [attr.aria-labelledby]=\"step.headerId\"\n [hidden]=\"!step.active()\"\n >\n <ng-container [ngTemplateOutlet]=\"step.content()\" />\n </div>\n </div>\n </div>\n }\n}\n\n<ng-template #headerTpl let-step=\"step\" let-i=\"i\">\n <button\n #stepBtn\n type=\"button\"\n class=\"mk-stepper__step\"\n [class.mk-stepper__step--active]=\"step.active()\"\n [class.mk-stepper__step--completed]=\"step.completed()\"\n [class.mk-stepper__step--error]=\"step.hasError()\"\n [id]=\"step.headerId\"\n [attr.role]=\"isHorizontal() ? 'tab' : null\"\n [attr.aria-controls]=\"step.panelId\"\n [attr.aria-selected]=\"isHorizontal() ? step.active() : null\"\n [attr.aria-posinset]=\"isHorizontal() ? i + 1 : null\"\n [attr.aria-setsize]=\"isHorizontal() ? steps().length : null\"\n [attr.aria-expanded]=\"isHorizontal() ? null : step.active()\"\n [attr.aria-current]=\"!isHorizontal() && step.active() ? 'step' : null\"\n [attr.aria-disabled]=\"!isReachable(i) || null\"\n [attr.tabindex]=\"step.active() ? 0 : -1\"\n [disabled]=\"!isReachable(i)\"\n (click)=\"select(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span class=\"mk-stepper__marker\" aria-hidden=\"true\">\n @if (step.hasError()) {\n <span class=\"mk-stepper__bang\">!</span>\n } @else if (step.completed()) {\n <span class=\"mk-stepper__check\"></span>\n } @else {\n {{ stepNumber(i) }}\n }\n </span>\n <span class=\"mk-stepper__labels\">\n <span class=\"mk-stepper__label\">\n {{ step.label() }}\n @if (step.optional()) {\n <span class=\"mk-stepper__optional\">{{ i18n.optional }}</span>\n }\n </span>\n @if (step.description()) {\n <span class=\"mk-stepper__description\">{{ step.description() }}</span>\n }\n </span>\n <!-- The marker above is aria-hidden, so the completed/error state needs a\n textual counterpart for assistive tech. -->\n @if (step.hasError()) {\n <span class=\"mk-visually-hidden\">{{ i18n.stepError }}</span>\n } @else if (step.completed()) {\n <span class=\"mk-visually-hidden\">{{ i18n.stepCompleted }}</span>\n }\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-stepper__header{display:flex;align-items:center;overflow-x:auto;scrollbar-width:none;padding-block:calc(var(--mk-focus-ring-width) + var(--mk-focus-ring-offset))}.mk-stepper__header::-webkit-scrollbar{display:none}.mk-stepper__step{display:inline-flex;align-items:center;gap:var(--mk-space-2);flex:none;padding:var(--mk-space-2) var(--mk-space-3);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);font-family:inherit;color:var(--mk-text-muted);text-align:start;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-stepper__step:hover:not(:disabled){color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-stepper__step:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-stepper__step:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-stepper__step--active{color:var(--mk-text)}.mk-stepper__marker{display:grid;place-items:center;flex:none;width:1.75rem;height:1.75rem;border-radius:var(--mk-radius-circle);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-semibold);transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-stepper__step--active .mk-stepper__marker{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-stepper__step--completed:not(.mk-stepper__step--active) .mk-stepper__marker{background-color:var(--mk-primary-subtle);color:var(--mk-primary-subtle-text)}.mk-stepper__step--error .mk-stepper__marker{background-color:var(--mk-danger-text);color:var(--mk-danger-contrast)}.mk-stepper__bang{line-height:1}.mk-stepper__check{width:.4rem;height:.7rem;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg) translateY(-1px)}.mk-stepper__labels{display:flex;flex-direction:column;gap:2px;min-width:0}.mk-stepper__label{display:inline-flex;align-items:center;gap:var(--mk-space-2);font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium)}.mk-stepper__step--error .mk-stepper__label{color:var(--mk-danger-text)}.mk-stepper__optional{font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-normal);color:var(--mk-text-subtle)}.mk-stepper__description{font-size:var(--mk-font-size-sm);color:var(--mk-text-subtle)}.mk-stepper__connector{flex:1 1 auto;min-width:var(--mk-space-4);height:var(--mk-border-width);margin:0 var(--mk-space-2);background-color:var(--mk-border)}.mk-stepper__panels{padding-top:var(--mk-space-4)}:host(.mk-stepper--vertical) .mk-stepper__row{display:flex;flex-direction:column;align-items:stretch}:host(.mk-stepper--vertical) .mk-stepper__step{align-self:stretch;justify-content:flex-start;text-align:start}:host(.mk-stepper--vertical) .mk-stepper__vline{margin-inline-start:calc(var(--mk-space-3) + .875rem);border-inline-start:var(--mk-border-width) solid var(--mk-border);padding-inline-start:calc(var(--mk-space-4) - var(--mk-border-width));min-height:var(--mk-space-4)}:host(.mk-stepper--vertical) .mk-stepper__vline--last{border-inline-start-color:transparent;min-height:0}:host(.mk-stepper--vertical) .mk-stepper__panel:not([hidden]){padding-block:var(--mk-space-3)}@media(prefers-reduced-motion:reduce){.mk-stepper__step,.mk-stepper__marker{transition:none}}\n"] }]
1289
+ }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkStep), { isSignal: true }] }], headerButtons: [{ type: i0.ViewChildren, args: ['stepBtn', { isSignal: true }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }, { type: i0.Output, args: ["selectedIndexChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], linear: [{ type: i0.Input, args: [{ isSignal: true, alias: "linear", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
1290
+
1291
+ /**
1292
+ * Tree — renders hierarchical data following the ARIA tree pattern
1293
+ * (`role="tree"` / `treeitem` with `aria-level`, `aria-expanded`,
1294
+ * `aria-selected`). Supply `nodes`; expansion is managed internally.
1295
+ *
1296
+ * Keyboard: Up/Down move between visible rows, Right expands (or steps into the
1297
+ * first child), Left collapses (or steps out to the parent), Home/End jump, and
1298
+ * Enter/Space toggles expansion and selects (when `selectable`). A single
1299
+ * roving tabindex keeps the whole tree one tab stop.
1300
+ *
1301
+ * ```html
1302
+ * <mk-tree selectable [nodes]="tree" [(selected)]="picked"
1303
+ * (selectionChange)="onPick($event)" />
1304
+ * ```
1305
+ */
1306
+ class MkTree {
1307
+ /** The hierarchical data to render. */
1308
+ nodes = input([], /* @ts-ignore */
1309
+ ...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
1310
+ /** Allow choosing a node (adds selection styling + `aria-selected`). */
1311
+ selectable = input(false, { ...(ngDevMode ? { debugName: "selectable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1312
+ /** Accessible name for the tree. */
1313
+ ariaLabel = input('', { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
1314
+ /** Two-way selected value (a node's `value`, or the node itself if unset). */
1315
+ selected = model(null, /* @ts-ignore */
1316
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
1317
+ /** Emits the selected value whenever the selection changes. */
1318
+ selectionChange = output();
1319
+ /** Emits a node whenever it is expanded or collapsed. */
1320
+ nodeToggle = output();
1321
+ rowRefs = viewChildren('row', /* @ts-ignore */
1322
+ ...(ngDevMode ? [{ debugName: "rowRefs" }] : /* istanbul ignore next */ []));
1323
+ /**
1324
+ * Expanded nodes. Survives `nodes` identity changes (a consumer rebuilding
1325
+ * the array keeps the user's expansion state for the node objects it
1326
+ * reuses); a node's `expanded` flag only seeds nodes NOT present in the
1327
+ * previous array — so it applies on first render and to newly added nodes,
1328
+ * without reverting branches the user has since collapsed.
1329
+ */
1330
+ expandedSet = linkedSignal({ ...(ngDevMode ? { debugName: "expandedSet" } : /* istanbul ignore next */ {}), source: this.nodes,
1331
+ computation: (nodes, previous) => {
1332
+ const prevExpanded = previous?.value;
1333
+ // Lazily built set of nodes from the previous input — only needed when
1334
+ // a node carries `expanded: true` and we must decide whether it is new.
1335
+ let prevNodes = null;
1336
+ const isNewNode = (node) => {
1337
+ if (!previous)
1338
+ return true;
1339
+ if (prevNodes === null) {
1340
+ prevNodes = new Set();
1341
+ const collect = (list) => {
1342
+ for (const n of list) {
1343
+ prevNodes.add(n);
1344
+ if (n.children?.length)
1345
+ collect(n.children);
1346
+ }
1347
+ };
1348
+ collect(previous.source);
1349
+ }
1350
+ return !prevNodes.has(node);
1351
+ };
1352
+ const next = new Set();
1353
+ const walk = (list) => {
1354
+ for (const node of list) {
1355
+ if (node.children?.length) {
1356
+ if (prevExpanded?.has(node) || (node.expanded && isNewNode(node))) {
1357
+ next.add(node);
1358
+ }
1359
+ walk(node.children);
1360
+ }
1361
+ }
1362
+ };
1363
+ walk(nodes);
1364
+ return next;
1365
+ } });
1366
+ activeIndex = signal(0, /* @ts-ignore */
1367
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
1368
+ /** Flattened list of currently visible rows (respects expansion). */
1369
+ rows = computed(() => {
1370
+ const expanded = this.expandedSet();
1371
+ const out = [];
1372
+ const walk = (list, level) => {
1373
+ list.forEach((node, i) => {
1374
+ const expandable = !!node.children?.length;
1375
+ const isExpanded = expandable && expanded.has(node);
1376
+ out.push({
1377
+ node,
1378
+ level,
1379
+ posinset: i + 1,
1380
+ setsize: list.length,
1381
+ expandable,
1382
+ expanded: isExpanded,
1383
+ });
1384
+ if (isExpanded)
1385
+ walk(node.children, level + 1);
1386
+ });
1387
+ };
1388
+ walk(this.nodes(), 1);
1389
+ return out;
1390
+ }, /* @ts-ignore */
1391
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1392
+ valueOf(node) {
1393
+ return node.value !== undefined ? node.value : node;
1394
+ }
1395
+ isSelected(node) {
1396
+ return this.selectable() && this.selected() === this.valueOf(node);
1397
+ }
1398
+ isExpanded(node) {
1399
+ return this.expandedSet().has(node);
1400
+ }
1401
+ toggle(node) {
1402
+ if (node.disabled || !node.children?.length)
1403
+ return;
1404
+ const next = new Set(this.expandedSet());
1405
+ if (next.has(node))
1406
+ next.delete(node);
1407
+ else
1408
+ next.add(node);
1409
+ this.expandedSet.set(next);
1410
+ this.nodeToggle.emit(node);
1411
+ }
1412
+ expand(node) {
1413
+ if (node.disabled || !node.children?.length || this.isExpanded(node))
1414
+ return;
1415
+ const next = new Set(this.expandedSet());
1416
+ next.add(node);
1417
+ this.expandedSet.set(next);
1418
+ this.nodeToggle.emit(node);
1419
+ }
1420
+ collapse(node) {
1421
+ if (!this.isExpanded(node))
1422
+ return;
1423
+ const next = new Set(this.expandedSet());
1424
+ next.delete(node);
1425
+ this.expandedSet.set(next);
1426
+ this.nodeToggle.emit(node);
1427
+ }
1428
+ select(node) {
1429
+ if (!this.selectable() || node.disabled)
1430
+ return;
1431
+ const value = this.valueOf(node);
1432
+ this.selected.set(value);
1433
+ this.selectionChange.emit(value);
1434
+ }
1435
+ /**
1436
+ * Click on a parent row's chevron: toggle expansion WITHOUT selecting, so
1437
+ * mouse users can open a branch without committing it (hosts like
1438
+ * `mk-tree-select` close on any selection, which would make children
1439
+ * unreachable by mouse otherwise). On leaf rows the click falls through to
1440
+ * the row handler and behaves as a normal row click.
1441
+ */
1442
+ onToggleClick(event, index) {
1443
+ const row = this.rows()[index];
1444
+ if (!row?.expandable)
1445
+ return;
1446
+ event.stopPropagation();
1447
+ this.activeIndex.set(index);
1448
+ this.toggle(row.node);
1449
+ }
1450
+ /** Click a row: toggle expansion for parents, select when selectable. */
1451
+ onRowClick(index) {
1452
+ const row = this.rows()[index];
1453
+ if (!row)
1454
+ return;
1455
+ this.activeIndex.set(index);
1456
+ if (row.expandable)
1457
+ this.toggle(row.node);
1458
+ this.select(row.node);
1459
+ }
1460
+ onKeydown(event, index) {
1461
+ const rows = this.rows();
1462
+ const row = rows[index];
1463
+ if (!row)
1464
+ return;
1465
+ switch (event.key) {
1466
+ case 'ArrowDown':
1467
+ event.preventDefault();
1468
+ this.focusRow(Math.min(index + 1, rows.length - 1));
1469
+ break;
1470
+ case 'ArrowUp':
1471
+ event.preventDefault();
1472
+ this.focusRow(Math.max(index - 1, 0));
1473
+ break;
1474
+ case 'ArrowRight':
1475
+ event.preventDefault();
1476
+ if (row.expandable && !row.expanded)
1477
+ this.expand(row.node);
1478
+ else if (row.expanded)
1479
+ this.focusRow(index + 1);
1480
+ break;
1481
+ case 'ArrowLeft':
1482
+ event.preventDefault();
1483
+ if (row.expandable && row.expanded)
1484
+ this.collapse(row.node);
1485
+ else
1486
+ this.focusRow(this.parentIndex(index));
1487
+ break;
1488
+ case 'Home':
1489
+ event.preventDefault();
1490
+ this.focusRow(0);
1491
+ break;
1492
+ case 'End':
1493
+ event.preventDefault();
1494
+ this.focusRow(rows.length - 1);
1495
+ break;
1496
+ case 'Enter':
1497
+ case ' ':
1498
+ event.preventDefault();
1499
+ if (row.expandable)
1500
+ this.toggle(row.node);
1501
+ this.select(row.node);
1502
+ break;
1503
+ }
1504
+ }
1505
+ /** Index of the nearest preceding row one level up (or the row itself). */
1506
+ parentIndex(index) {
1507
+ const rows = this.rows();
1508
+ const level = rows[index]?.level ?? 1;
1509
+ for (let i = index - 1; i >= 0; i--) {
1510
+ if (rows[i].level < level)
1511
+ return i;
1512
+ }
1513
+ return index;
1514
+ }
1515
+ focusRow(index) {
1516
+ const rows = this.rows();
1517
+ if (index < 0 || index >= rows.length)
1518
+ return;
1519
+ this.activeIndex.set(index);
1520
+ this.rowRefs()[index]?.nativeElement.focus();
1521
+ }
1522
+ tabIndexFor(index) {
1523
+ const active = Math.min(this.activeIndex(), this.rows().length - 1);
1524
+ return index === Math.max(active, 0) ? 0 : -1;
1525
+ }
1526
+ indent(level) {
1527
+ return `calc(${level - 1} * var(--mk-space-5))`;
1528
+ }
1529
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
1530
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTree, isStandalone: true, selector: "mk-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", selectionChange: "selectionChange", nodeToggle: "nodeToggle" }, host: { classAttribute: "mk-tree" }, viewQueries: [{ propertyName: "rowRefs", predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"mk-tree__root\"\n role=\"tree\"\n [attr.aria-label]=\"ariaLabel() || null\"\n>\n @for (row of rows(); track row.node; let i = $index) {\n <div\n #row\n class=\"mk-tree__item\"\n role=\"treeitem\"\n [class.mk-tree__item--selected]=\"isSelected(row.node)\"\n [class.mk-tree__item--disabled]=\"row.node.disabled\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-posinset]=\"row.posinset\"\n [attr.aria-setsize]=\"row.setsize\"\n [attr.aria-expanded]=\"row.expandable ? row.expanded : null\"\n [attr.aria-selected]=\"selectable() ? isSelected(row.node) : null\"\n [attr.aria-disabled]=\"row.node.disabled || null\"\n [attr.tabindex]=\"tabIndexFor(i)\"\n [style.padding-inline-start]=\"indent(row.level)\"\n (click)=\"onRowClick(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span\n class=\"mk-tree__toggle\"\n aria-hidden=\"true\"\n (click)=\"onToggleClick($event, i)\"\n >\n @if (row.expandable) {\n <span\n class=\"mk-tree__caret\"\n [class.mk-tree__caret--open]=\"row.expanded\"\n ></span>\n }\n </span>\n @if (row.node.icon) {\n <span class=\"mk-tree__icon\" aria-hidden=\"true\">{{ row.node.icon }}</span>\n }\n <span class=\"mk-tree__label\">{{ row.node.label }}</span>\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-tree__root{display:flex;flex-direction:column}.mk-tree__item{display:flex;align-items:center;gap:var(--mk-space-1);min-height:2rem;padding-inline-end:var(--mk-space-2);border-radius:var(--mk-radius-sm);color:var(--mk-text);font-size:var(--mk-font-size-md);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tree__item:hover{background-color:var(--mk-hover-overlay)}.mk-tree__item:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-tree__item--selected{background-color:var(--mk-selected-bg);color:var(--mk-selected-text);font-weight:var(--mk-font-weight-medium)}.mk-tree__item--selected:hover{background-color:var(--mk-selected-bg)}.mk-tree__item--disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-tree__item--disabled:hover{background-color:transparent}.mk-tree__toggle{display:grid;place-items:center;flex:none;width:1.25rem;height:1.25rem}@media(pointer:coarse){.mk-tree__toggle{position:relative}.mk-tree__toggle:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-tree__caret{width:.4rem;height:.4rem;border-right:2px solid var(--mk-text-muted);border-bottom:2px solid var(--mk-text-muted);transform:rotate(-45deg);transition:transform var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tree__caret--open{transform:rotate(45deg)}:host(:dir(rtl)) .mk-tree__caret:not(.mk-tree__caret--open){transform:rotate(135deg)}.mk-tree__icon{flex:none;display:inline-flex;line-height:1}.mk-tree__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(prefers-reduced-motion:reduce){.mk-tree__item,.mk-tree__caret{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1531
+ }
1532
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTree, decorators: [{
1533
+ type: Component,
1534
+ args: [{ selector: 'mk-tree', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1535
+ class: 'mk-tree',
1536
+ }, template: "<div\n class=\"mk-tree__root\"\n role=\"tree\"\n [attr.aria-label]=\"ariaLabel() || null\"\n>\n @for (row of rows(); track row.node; let i = $index) {\n <div\n #row\n class=\"mk-tree__item\"\n role=\"treeitem\"\n [class.mk-tree__item--selected]=\"isSelected(row.node)\"\n [class.mk-tree__item--disabled]=\"row.node.disabled\"\n [attr.aria-level]=\"row.level\"\n [attr.aria-posinset]=\"row.posinset\"\n [attr.aria-setsize]=\"row.setsize\"\n [attr.aria-expanded]=\"row.expandable ? row.expanded : null\"\n [attr.aria-selected]=\"selectable() ? isSelected(row.node) : null\"\n [attr.aria-disabled]=\"row.node.disabled || null\"\n [attr.tabindex]=\"tabIndexFor(i)\"\n [style.padding-inline-start]=\"indent(row.level)\"\n (click)=\"onRowClick(i)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span\n class=\"mk-tree__toggle\"\n aria-hidden=\"true\"\n (click)=\"onToggleClick($event, i)\"\n >\n @if (row.expandable) {\n <span\n class=\"mk-tree__caret\"\n [class.mk-tree__caret--open]=\"row.expanded\"\n ></span>\n }\n </span>\n @if (row.node.icon) {\n <span class=\"mk-tree__icon\" aria-hidden=\"true\">{{ row.node.icon }}</span>\n }\n <span class=\"mk-tree__label\">{{ row.node.label }}</span>\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-tree__root{display:flex;flex-direction:column}.mk-tree__item{display:flex;align-items:center;gap:var(--mk-space-1);min-height:2rem;padding-inline-end:var(--mk-space-2);border-radius:var(--mk-radius-sm);color:var(--mk-text);font-size:var(--mk-font-size-md);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tree__item:hover{background-color:var(--mk-hover-overlay)}.mk-tree__item:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-tree__item--selected{background-color:var(--mk-selected-bg);color:var(--mk-selected-text);font-weight:var(--mk-font-weight-medium)}.mk-tree__item--selected:hover{background-color:var(--mk-selected-bg)}.mk-tree__item--disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-tree__item--disabled:hover{background-color:transparent}.mk-tree__toggle{display:grid;place-items:center;flex:none;width:1.25rem;height:1.25rem}@media(pointer:coarse){.mk-tree__toggle{position:relative}.mk-tree__toggle:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-tree__caret{width:.4rem;height:.4rem;border-right:2px solid var(--mk-text-muted);border-bottom:2px solid var(--mk-text-muted);transform:rotate(-45deg);transition:transform var(--mk-duration-fast) var(--mk-ease-standard)}.mk-tree__caret--open{transform:rotate(45deg)}:host(:dir(rtl)) .mk-tree__caret:not(.mk-tree__caret--open){transform:rotate(135deg)}.mk-tree__icon{flex:none;display:inline-flex;line-height:1}.mk-tree__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(prefers-reduced-motion:reduce){.mk-tree__item,.mk-tree__caret{transition:none}}\n"] }]
1537
+ }], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], nodeToggle: [{ type: i0.Output, args: ["nodeToggle"] }], rowRefs: [{ type: i0.ViewChildren, args: ['row', { isSignal: true }] }] } });
1538
+
1539
+ /**
1540
+ * Drawer — a declarative slide-out panel anchored to a viewport edge, driven by
1541
+ * a two-way `open`. Ideal for admin/CMS side panels: filters, entity details,
1542
+ * settings, quick-create forms. Traps focus while open, closes on backdrop
1543
+ * click or Escape, restores focus on close, and locks body scroll when modal.
1544
+ *
1545
+ * Slots: `[mkDrawerHeader]` (extra header content), default content (body),
1546
+ * `[mkDrawerFooter]` (sticky footer).
1547
+ *
1548
+ * ```html
1549
+ * <button mkButton (click)="filters.set(true)">Filters</button>
1550
+ * <mk-drawer [(open)]="filters" side="end" heading="Filters">
1551
+ * …filter form…
1552
+ * <div mkDrawerFooter>
1553
+ * <button mkButton variant="ghost" (click)="filters.set(false)">Close</button>
1554
+ * <button mkButton (click)="apply()">Apply</button>
1555
+ * </div>
1556
+ * </mk-drawer>
1557
+ * ```
1558
+ */
1559
+ class MkDrawer {
1560
+ document = inject(DOCUMENT);
1561
+ /** Localised strings (override globally via `provideMkI18n`). */
1562
+ i18n = inject(MK_I18N);
1563
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1564
+ panelRef = viewChild.required('panel', /* @ts-ignore */
1565
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1566
+ /** Whether the drawer is open (two-way). */
1567
+ open = model(false, /* @ts-ignore */
1568
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1569
+ /** Viewport edge to anchor to. */
1570
+ side = input('end', /* @ts-ignore */
1571
+ ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
1572
+ /** Panel width (start/end) or height (top/bottom) — any CSS length. */
1573
+ size = input('20rem', /* @ts-ignore */
1574
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1575
+ /** Render a dimmed backdrop and lock body scroll (modal). */
1576
+ hasBackdrop = input(true, { ...(ngDevMode ? { debugName: "hasBackdrop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1577
+ /** Close when the backdrop is clicked. */
1578
+ closeOnBackdrop = input(true, { ...(ngDevMode ? { debugName: "closeOnBackdrop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1579
+ /** Close on the Escape key. */
1580
+ closeOnEscape = input(true, { ...(ngDevMode ? { debugName: "closeOnEscape" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1581
+ /** Trap Tab focus inside the panel while open. */
1582
+ trapFocus = input(true, { ...(ngDevMode ? { debugName: "trapFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1583
+ /** Optional heading rendered in the header. */
1584
+ heading = input('', /* @ts-ignore */
1585
+ ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
1586
+ /** Hide the header close button. */
1587
+ hideClose = input(false, { ...(ngDevMode ? { debugName: "hideClose" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1588
+ /** Accessible name when no `heading` is provided. */
1589
+ ariaLabel = input('', { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
1590
+ titleId = mkUniqueId('mk-drawer-title');
1591
+ focusTrap;
1592
+ previouslyFocused = null;
1593
+ scrollLocked = false;
1594
+ constructor() {
1595
+ effect(() => {
1596
+ const isOpen = this.open();
1597
+ if (!this.isBrowser)
1598
+ return;
1599
+ // Defer so the panel has rendered/settled before focus moves into it.
1600
+ queueMicrotask(() => this.sync(isOpen));
1601
+ });
1602
+ }
1603
+ sync(isOpen) {
1604
+ const panel = this.panelRef().nativeElement;
1605
+ if (isOpen) {
1606
+ this.previouslyFocused = this.document.activeElement;
1607
+ if (this.trapFocus()) {
1608
+ this.focusTrap = new MkFocusTrap(panel);
1609
+ this.focusTrap.activate();
1610
+ }
1611
+ if (this.hasBackdrop() && !this.scrollLocked) {
1612
+ this.document.body.style.setProperty('overflow', 'hidden');
1613
+ this.scrollLocked = true;
1614
+ }
1615
+ }
1616
+ else {
1617
+ this.focusTrap?.release();
1618
+ this.focusTrap = undefined;
1619
+ if (this.scrollLocked) {
1620
+ this.document.body.style.removeProperty('overflow');
1621
+ this.scrollLocked = false;
1622
+ }
1623
+ this.previouslyFocused?.focus?.();
1624
+ }
1625
+ }
1626
+ /** Release the focus trap + scroll lock if destroyed while open. */
1627
+ ngOnDestroy() {
1628
+ this.focusTrap?.release();
1629
+ this.focusTrap = undefined;
1630
+ if (this.scrollLocked) {
1631
+ this.document.body.style.removeProperty('overflow');
1632
+ this.scrollLocked = false;
1633
+ }
1634
+ }
1635
+ /** Close the drawer. */
1636
+ close() {
1637
+ this.open.set(false);
1638
+ }
1639
+ onBackdrop() {
1640
+ if (this.closeOnBackdrop())
1641
+ this.close();
1642
+ }
1643
+ onEscape(event) {
1644
+ if (this.open() && this.closeOnEscape()) {
1645
+ event.stopPropagation();
1646
+ this.close();
1647
+ }
1648
+ }
1649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1650
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkDrawer, isStandalone: true, selector: "mk-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdrop: { classPropertyName: "closeOnBackdrop", publicName: "closeOnBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, trapFocus: { classPropertyName: "trapFocus", publicName: "trapFocus", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, hideClose: { classPropertyName: "hideClose", publicName: "hideClose", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "keydown.escape": "onEscape($event)" }, properties: { "class.mk-drawer--open": "open()", "attr.data-side": "side()", "attr.inert": "open() ? null : ''" }, classAttribute: "mk-drawer" }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (hasBackdrop()) {\n <div class=\"mk-drawer__backdrop\" (click)=\"onBackdrop()\"></div>\n}\n\n<aside\n #panel\n class=\"mk-drawer__panel\"\n role=\"dialog\"\n [attr.aria-modal]=\"hasBackdrop() ? 'true' : null\"\n [attr.aria-label]=\"!heading() ? ariaLabel() || null : null\"\n [attr.aria-labelledby]=\"heading() ? titleId : null\"\n [style.--_size]=\"size()\"\n tabindex=\"-1\"\n>\n @if (heading() || !hideClose()) {\n <header class=\"mk-drawer__header\">\n @if (heading(); as h) {\n <h2 class=\"mk-drawer__title\" [id]=\"titleId\">{{ h }}</h2>\n }\n <ng-content select=\"[mkDrawerHeader]\" />\n @if (!hideClose()) {\n <button\n type=\"button\"\n class=\"mk-drawer__close\"\n [attr.aria-label]=\"i18n.close\"\n (click)=\"close()\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" focusable=\"false\">\n <path\n d=\"M4 4l8 8M12 4l-8 8\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </header>\n }\n\n <div class=\"mk-drawer__body\">\n <ng-content />\n </div>\n\n <footer class=\"mk-drawer__footer\">\n <ng-content select=\"[mkDrawerFooter]\" />\n </footer>\n</aside>\n", styles: [":host{position:fixed;inset:0;z-index:var(--mk-z-drawer);pointer-events:none;font-family:var(--mk-font-sans)}:host(.mk-drawer--open){pointer-events:auto}.mk-drawer__backdrop{position:absolute;inset:0;background-color:var(--mk-overlay-scrim);opacity:0;transition:opacity var(--mk-duration-normal) var(--mk-ease-standard)}:host(.mk-drawer--open) .mk-drawer__backdrop{opacity:1}.mk-drawer__panel{position:absolute;display:flex;flex-direction:column;max-width:100%;max-height:100%;background-color:var(--mk-surface);color:var(--mk-text);box-shadow:var(--mk-shadow-xl);outline:none;transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host([data-side=end]) .mk-drawer__panel{top:0;inset-inline-end:0;bottom:0;width:var(--_size, 20rem);border-inline-start:var(--mk-border-width) solid var(--mk-border-subtle);transform:translate(100%)}:host([data-side=start]) .mk-drawer__panel{top:0;inset-inline-start:0;bottom:0;width:var(--_size, 20rem);border-inline-end:var(--mk-border-width) solid var(--mk-border-subtle);transform:translate(-100%)}:host([data-side=end]:dir(rtl):not(.mk-drawer--open)) .mk-drawer__panel{transform:translate(-100%)}:host([data-side=start]:dir(rtl):not(.mk-drawer--open)) .mk-drawer__panel{transform:translate(100%)}:host([data-side=top]) .mk-drawer__panel{top:0;left:0;right:0;height:var(--_size, 20rem);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);transform:translateY(-100%)}:host([data-side=bottom]) .mk-drawer__panel{bottom:0;left:0;right:0;height:var(--_size, 20rem);border-top:var(--mk-border-width) solid var(--mk-border-subtle);transform:translateY(100%)}:host(.mk-drawer--open) .mk-drawer__panel{transform:none}.mk-drawer__header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--mk-space-3);flex-shrink:0;padding:var(--mk-space-4) var(--mk-space-5);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);background-color:var(--mk-surface)}.mk-drawer__title{flex:1;min-width:0;margin:0;overflow-wrap:anywhere;font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);line-height:var(--mk-line-height-snug);color:var(--mk-text)}.mk-drawer__close{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;background:transparent;border:0;border-radius:var(--mk-radius-sm);color:var(--mk-text-muted);cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-drawer__close:hover{color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-drawer__close:focus-visible{color:var(--mk-text);outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-drawer__body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:var(--mk-space-5);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);color:var(--mk-text)}.mk-drawer__footer{position:sticky;bottom:0;flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;gap:var(--mk-space-2);padding:var(--mk-space-4) var(--mk-space-5);border-top:var(--mk-border-width) solid var(--mk-border-subtle);background-color:var(--mk-surface)}.mk-drawer__footer:empty{display:none}@media(prefers-reduced-motion:reduce){.mk-drawer__backdrop,.mk-drawer__panel,.mk-drawer__close{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1651
+ }
1652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDrawer, decorators: [{
1653
+ type: Component,
1654
+ args: [{ selector: 'mk-drawer', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1655
+ class: 'mk-drawer',
1656
+ '[class.mk-drawer--open]': 'open()',
1657
+ '[attr.data-side]': 'side()',
1658
+ '[attr.inert]': "open() ? null : ''",
1659
+ '(keydown.escape)': 'onEscape($event)',
1660
+ }, template: "@if (hasBackdrop()) {\n <div class=\"mk-drawer__backdrop\" (click)=\"onBackdrop()\"></div>\n}\n\n<aside\n #panel\n class=\"mk-drawer__panel\"\n role=\"dialog\"\n [attr.aria-modal]=\"hasBackdrop() ? 'true' : null\"\n [attr.aria-label]=\"!heading() ? ariaLabel() || null : null\"\n [attr.aria-labelledby]=\"heading() ? titleId : null\"\n [style.--_size]=\"size()\"\n tabindex=\"-1\"\n>\n @if (heading() || !hideClose()) {\n <header class=\"mk-drawer__header\">\n @if (heading(); as h) {\n <h2 class=\"mk-drawer__title\" [id]=\"titleId\">{{ h }}</h2>\n }\n <ng-content select=\"[mkDrawerHeader]\" />\n @if (!hideClose()) {\n <button\n type=\"button\"\n class=\"mk-drawer__close\"\n [attr.aria-label]=\"i18n.close\"\n (click)=\"close()\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\" focusable=\"false\">\n <path\n d=\"M4 4l8 8M12 4l-8 8\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n }\n </header>\n }\n\n <div class=\"mk-drawer__body\">\n <ng-content />\n </div>\n\n <footer class=\"mk-drawer__footer\">\n <ng-content select=\"[mkDrawerFooter]\" />\n </footer>\n</aside>\n", styles: [":host{position:fixed;inset:0;z-index:var(--mk-z-drawer);pointer-events:none;font-family:var(--mk-font-sans)}:host(.mk-drawer--open){pointer-events:auto}.mk-drawer__backdrop{position:absolute;inset:0;background-color:var(--mk-overlay-scrim);opacity:0;transition:opacity var(--mk-duration-normal) var(--mk-ease-standard)}:host(.mk-drawer--open) .mk-drawer__backdrop{opacity:1}.mk-drawer__panel{position:absolute;display:flex;flex-direction:column;max-width:100%;max-height:100%;background-color:var(--mk-surface);color:var(--mk-text);box-shadow:var(--mk-shadow-xl);outline:none;transition:transform var(--mk-duration-normal) var(--mk-ease-emphasized)}:host([data-side=end]) .mk-drawer__panel{top:0;inset-inline-end:0;bottom:0;width:var(--_size, 20rem);border-inline-start:var(--mk-border-width) solid var(--mk-border-subtle);transform:translate(100%)}:host([data-side=start]) .mk-drawer__panel{top:0;inset-inline-start:0;bottom:0;width:var(--_size, 20rem);border-inline-end:var(--mk-border-width) solid var(--mk-border-subtle);transform:translate(-100%)}:host([data-side=end]:dir(rtl):not(.mk-drawer--open)) .mk-drawer__panel{transform:translate(-100%)}:host([data-side=start]:dir(rtl):not(.mk-drawer--open)) .mk-drawer__panel{transform:translate(100%)}:host([data-side=top]) .mk-drawer__panel{top:0;left:0;right:0;height:var(--_size, 20rem);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);transform:translateY(-100%)}:host([data-side=bottom]) .mk-drawer__panel{bottom:0;left:0;right:0;height:var(--_size, 20rem);border-top:var(--mk-border-width) solid var(--mk-border-subtle);transform:translateY(100%)}:host(.mk-drawer--open) .mk-drawer__panel{transform:none}.mk-drawer__header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--mk-space-3);flex-shrink:0;padding:var(--mk-space-4) var(--mk-space-5);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);background-color:var(--mk-surface)}.mk-drawer__title{flex:1;min-width:0;margin:0;overflow-wrap:anywhere;font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);line-height:var(--mk-line-height-snug);color:var(--mk-text)}.mk-drawer__close{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;background:transparent;border:0;border-radius:var(--mk-radius-sm);color:var(--mk-text-muted);cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-drawer__close:hover{color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-drawer__close:focus-visible{color:var(--mk-text);outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-drawer__body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:var(--mk-space-5);font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);color:var(--mk-text)}.mk-drawer__footer{position:sticky;bottom:0;flex-shrink:0;display:flex;align-items:center;justify-content:flex-end;gap:var(--mk-space-2);padding:var(--mk-space-4) var(--mk-space-5);border-top:var(--mk-border-width) solid var(--mk-border-subtle);background-color:var(--mk-surface)}.mk-drawer__footer:empty{display:none}@media(prefers-reduced-motion:reduce){.mk-drawer__backdrop,.mk-drawer__panel,.mk-drawer__close{transition:none}}\n"] }]
1661
+ }], ctorParameters: () => [], propDecorators: { panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hasBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasBackdrop", required: false }] }], closeOnBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdrop", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], trapFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "trapFocus", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], hideClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideClose", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }] } });
1662
+
1663
+ /**
1664
+ * Toolbar — a horizontal action bar: content flows on the start side, anything
1665
+ * marked `mkToolbarEnd` is pushed to the end. Handy above tables and lists for
1666
+ * a title/search on the left and actions on the right.
1667
+ *
1668
+ * The projected controls are ordinary tab stops (no roving focus), so this is a
1669
+ * layout container, not an ARIA `toolbar` widget.
1670
+ *
1671
+ * ```html
1672
+ * <mk-toolbar bordered aria-label="Users actions">
1673
+ * <strong>Users</strong>
1674
+ * <span mkToolbarEnd>
1675
+ * <button mkButton variant="outline">Export</button>
1676
+ * <button mkButton>New user</button>
1677
+ * </span>
1678
+ * </mk-toolbar>
1679
+ * ```
1680
+ */
1681
+ class MkToolbar {
1682
+ /** Wrap the bar in a themed, bordered surface. */
1683
+ bordered = input(false, { ...(ngDevMode ? { debugName: "bordered" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1684
+ /** Accessible name (also promotes the bar to an ARIA `group`). */
1685
+ ariaLabel = input('', { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
1686
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1687
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkToolbar, isStandalone: true, selector: "mk-toolbar", inputs: { bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.mk-toolbar--bordered": "bordered()", "attr.role": "ariaLabel() ? 'group' : null", "attr.aria-label": "ariaLabel() || null" }, classAttribute: "mk-toolbar" }, ngImport: i0, template: "<div class=\"mk-toolbar__start\">\n <ng-content />\n</div>\n<div class=\"mk-toolbar__end\">\n <ng-content select=\"[mkToolbarEnd]\" />\n</div>\n", styles: [":host{display:flex;align-items:center;gap:var(--mk-space-2);padding:var(--mk-space-2) var(--mk-space-3);font-family:var(--mk-font-sans);color:var(--mk-text)}:host(.mk-toolbar--bordered){background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border-subtle);border-radius:var(--mk-radius-md)}.mk-toolbar__start{display:flex;align-items:center;gap:var(--mk-space-2);flex:1 1 auto;min-width:0;flex-wrap:wrap}.mk-toolbar__end{display:flex;align-items:center;gap:var(--mk-space-2);flex:none}.mk-toolbar__end:empty{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1688
+ }
1689
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkToolbar, decorators: [{
1690
+ type: Component,
1691
+ args: [{ selector: 'mk-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1692
+ class: 'mk-toolbar',
1693
+ '[class.mk-toolbar--bordered]': 'bordered()',
1694
+ '[attr.role]': "ariaLabel() ? 'group' : null",
1695
+ '[attr.aria-label]': 'ariaLabel() || null',
1696
+ }, template: "<div class=\"mk-toolbar__start\">\n <ng-content />\n</div>\n<div class=\"mk-toolbar__end\">\n <ng-content select=\"[mkToolbarEnd]\" />\n</div>\n", styles: [":host{display:flex;align-items:center;gap:var(--mk-space-2);padding:var(--mk-space-2) var(--mk-space-3);font-family:var(--mk-font-sans);color:var(--mk-text)}:host(.mk-toolbar--bordered){background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border-subtle);border-radius:var(--mk-radius-md)}.mk-toolbar__start{display:flex;align-items:center;gap:var(--mk-space-2);flex:1 1 auto;min-width:0;flex-wrap:wrap}.mk-toolbar__end{display:flex;align-items:center;gap:var(--mk-space-2);flex:none}.mk-toolbar__end:empty{display:none}\n"] }]
1697
+ }], propDecorators: { bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }] } });
1698
+
1699
+ /**
1700
+ * PageHeader — the standard heading block at the top of an admin/CMS page: an
1701
+ * optional breadcrumb, a page title with optional description and inline meta
1702
+ * (badges/status), a right-aligned actions cluster, and an optional tab row
1703
+ * beneath. Everything but the title/description is projected, so it composes
1704
+ * with `mk-breadcrumb`, `mk-tabs`, buttons, badges, etc.
1705
+ *
1706
+ * Slots: `[mkPageHeaderBreadcrumb]`, `[mkPageHeaderMeta]` (beside the title),
1707
+ * `[mkPageHeaderActions]` (right), `[mkPageHeaderTabs]` (below).
1708
+ *
1709
+ * ```html
1710
+ * <mk-page-header heading="Articles" description="Manage your published posts.">
1711
+ * <mk-breadcrumb mkPageHeaderBreadcrumb …></mk-breadcrumb>
1712
+ * <mk-badge mkPageHeaderMeta tone="success">Live</mk-badge>
1713
+ * <div mkPageHeaderActions>
1714
+ * <button mkButton variant="outline">Import</button>
1715
+ * <button mkButton>New article</button>
1716
+ * </div>
1717
+ * </mk-page-header>
1718
+ * ```
1719
+ */
1720
+ class MkPageHeader {
1721
+ /** The page title (rendered as an `<h1>`). */
1722
+ heading = input('', /* @ts-ignore */
1723
+ ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
1724
+ /** Supporting description under the title. */
1725
+ description = input('', /* @ts-ignore */
1726
+ ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
1727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkPageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
1728
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkPageHeader, isStandalone: true, selector: "mk-page-header", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "mk-page-header" }, ngImport: i0, template: "<div class=\"mk-page-header__breadcrumb\">\n <ng-content select=\"[mkPageHeaderBreadcrumb]\" />\n</div>\n\n<div class=\"mk-page-header__main\">\n <div class=\"mk-page-header__media\">\n <ng-content select=\"[mkPageHeaderMedia]\" />\n </div>\n <div class=\"mk-page-header__heading\">\n <div class=\"mk-page-header__title-row\">\n @if (heading(); as h) {\n <h1 class=\"mk-page-header__title\">{{ h }}</h1>\n }\n <div class=\"mk-page-header__meta\">\n <ng-content select=\"[mkPageHeaderMeta]\" />\n </div>\n </div>\n @if (description(); as d) {\n <p class=\"mk-page-header__description\">{{ d }}</p>\n }\n </div>\n\n <div class=\"mk-page-header__actions\">\n <ng-content select=\"[mkPageHeaderActions]\" />\n </div>\n</div>\n\n<div class=\"mk-page-header__tabs\">\n <ng-content select=\"[mkPageHeaderTabs]\" />\n</div>\n", styles: [":host{display:block;font-family:var(--mk-font-sans);color:var(--mk-text)}.mk-page-header__breadcrumb{margin-bottom:var(--mk-space-2)}.mk-page-header__breadcrumb:empty{display:none}.mk-page-header__main{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--mk-space-4);flex-wrap:wrap}.mk-page-header__heading{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:var(--mk-space-1)}.mk-page-header__title-row{display:flex;align-items:center;gap:var(--mk-space-3);flex-wrap:wrap}.mk-page-header__title{margin:0;font-size:var(--mk-font-size-2xl);font-weight:var(--mk-font-weight-bold);line-height:var(--mk-line-height-tight);letter-spacing:var(--mk-letter-spacing-tight);color:var(--mk-text)}.mk-page-header__meta{display:inline-flex;align-items:center;gap:var(--mk-space-2)}.mk-page-header__meta:empty{display:none}.mk-page-header__description{margin:0;max-width:68ch;font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);color:var(--mk-text-muted)}.mk-page-header__actions{flex:none;display:flex;align-items:center;gap:var(--mk-space-2);flex-wrap:wrap}.mk-page-header__actions:empty{display:none}.mk-page-header__tabs{margin-top:var(--mk-space-4)}.mk-page-header__tabs:empty{display:none}.mk-page-header__media{display:flex;align-items:center;flex-shrink:0}.mk-page-header__media:empty{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1729
+ }
1730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkPageHeader, decorators: [{
1731
+ type: Component,
1732
+ args: [{ selector: 'mk-page-header', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'mk-page-header' }, template: "<div class=\"mk-page-header__breadcrumb\">\n <ng-content select=\"[mkPageHeaderBreadcrumb]\" />\n</div>\n\n<div class=\"mk-page-header__main\">\n <div class=\"mk-page-header__media\">\n <ng-content select=\"[mkPageHeaderMedia]\" />\n </div>\n <div class=\"mk-page-header__heading\">\n <div class=\"mk-page-header__title-row\">\n @if (heading(); as h) {\n <h1 class=\"mk-page-header__title\">{{ h }}</h1>\n }\n <div class=\"mk-page-header__meta\">\n <ng-content select=\"[mkPageHeaderMeta]\" />\n </div>\n </div>\n @if (description(); as d) {\n <p class=\"mk-page-header__description\">{{ d }}</p>\n }\n </div>\n\n <div class=\"mk-page-header__actions\">\n <ng-content select=\"[mkPageHeaderActions]\" />\n </div>\n</div>\n\n<div class=\"mk-page-header__tabs\">\n <ng-content select=\"[mkPageHeaderTabs]\" />\n</div>\n", styles: [":host{display:block;font-family:var(--mk-font-sans);color:var(--mk-text)}.mk-page-header__breadcrumb{margin-bottom:var(--mk-space-2)}.mk-page-header__breadcrumb:empty{display:none}.mk-page-header__main{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--mk-space-4);flex-wrap:wrap}.mk-page-header__heading{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:var(--mk-space-1)}.mk-page-header__title-row{display:flex;align-items:center;gap:var(--mk-space-3);flex-wrap:wrap}.mk-page-header__title{margin:0;font-size:var(--mk-font-size-2xl);font-weight:var(--mk-font-weight-bold);line-height:var(--mk-line-height-tight);letter-spacing:var(--mk-letter-spacing-tight);color:var(--mk-text)}.mk-page-header__meta{display:inline-flex;align-items:center;gap:var(--mk-space-2)}.mk-page-header__meta:empty{display:none}.mk-page-header__description{margin:0;max-width:68ch;font-size:var(--mk-font-size-md);line-height:var(--mk-line-height-normal);color:var(--mk-text-muted)}.mk-page-header__actions{flex:none;display:flex;align-items:center;gap:var(--mk-space-2);flex-wrap:wrap}.mk-page-header__actions:empty{display:none}.mk-page-header__tabs{margin-top:var(--mk-space-4)}.mk-page-header__tabs:empty{display:none}.mk-page-header__media{display:flex;align-items:center;flex-shrink:0}.mk-page-header__media:empty{display:none}\n"] }]
1733
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
1734
+
1735
+ /**
1736
+ * CommandPalette — a modal ⌘K/Ctrl+K menu for fast, keyboard-first actions and
1737
+ * navigation. Supply a flat `commands` list (optionally grouped); the palette
1738
+ * filters as you type, groups results, and runs the chosen command. Implements
1739
+ * the ARIA combobox + listbox pattern with full keyboard support and focus
1740
+ * trapping.
1741
+ *
1742
+ * Mount it once near your app root and bind `open` (the built-in hotkey toggles
1743
+ * it, or open it yourself).
1744
+ *
1745
+ * ```html
1746
+ * <mk-command-palette [(open)]="paletteOpen" [commands]="commands"
1747
+ * (commandSelected)="run($event)" />
1748
+ * ```
1749
+ */
1750
+ class MkCommandPalette {
1751
+ document = inject(DOCUMENT);
1752
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1753
+ i18n = inject(MK_I18N);
1754
+ panelRef = viewChild('panel', /* @ts-ignore */
1755
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1756
+ inputRef = viewChild('input', /* @ts-ignore */
1757
+ ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
1758
+ /** Whether the palette is open (two-way). */
1759
+ open = model(false, /* @ts-ignore */
1760
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1761
+ /** The commands to search and run. */
1762
+ commands = input([], /* @ts-ignore */
1763
+ ...(ngDevMode ? [{ debugName: "commands" }] : /* istanbul ignore next */ []));
1764
+ /** Search field placeholder. */
1765
+ placeholder = input(this.i18n.commandPalettePlaceholder, /* @ts-ignore */
1766
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1767
+ /** Message shown when nothing matches. */
1768
+ emptyMessage = input(this.i18n.noResults, /* @ts-ignore */
1769
+ ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
1770
+ /** Toggle open with ⌘K / Ctrl+K anywhere. */
1771
+ hotkey = input(true, { ...(ngDevMode ? { debugName: "hotkey" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1772
+ /** Emits the chosen command (its own `run` callback also fires). */
1773
+ commandSelected = output();
1774
+ query = signal('', /* @ts-ignore */
1775
+ ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
1776
+ activeIndex = signal(0, /* @ts-ignore */
1777
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
1778
+ listId = mkUniqueId('mk-cmdk-list');
1779
+ focusTrap;
1780
+ scrollLocked = false;
1781
+ previouslyFocused = null;
1782
+ /** Commands matching the current query (label + keywords + group). */
1783
+ filtered = computed(() => {
1784
+ const q = this.query().trim().toLowerCase();
1785
+ const list = this.commands();
1786
+ if (!q)
1787
+ return list;
1788
+ return list.filter((c) => `${c.label} ${c.keywords ?? ''} ${c.group ?? ''}`
1789
+ .toLowerCase()
1790
+ .includes(q));
1791
+ }, /* @ts-ignore */
1792
+ ...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
1793
+ /** Filtered commands bucketed into ordered groups (flat index preserved). */
1794
+ grouped = computed(() => {
1795
+ const groups = [];
1796
+ this.filtered().forEach((cmd, index) => {
1797
+ const name = cmd.group ?? '';
1798
+ let g = groups.find((x) => x.name === name);
1799
+ if (!g) {
1800
+ g = { name, items: [] };
1801
+ groups.push(g);
1802
+ }
1803
+ g.items.push({ cmd, index });
1804
+ });
1805
+ return groups;
1806
+ }, /* @ts-ignore */
1807
+ ...(ngDevMode ? [{ debugName: "grouped" }] : /* istanbul ignore next */ []));
1808
+ constructor() {
1809
+ effect(() => {
1810
+ const isOpen = this.open();
1811
+ if (!this.isBrowser)
1812
+ return;
1813
+ queueMicrotask(() => this.sync(isOpen));
1814
+ });
1815
+ // Keep the active option visible while the list scrolls under 70vh.
1816
+ // Synchronous on purpose: on an index change the option elements already
1817
+ // exist, and the getElementById chain no-ops for the open-with-index-0
1818
+ // case where the list has not rendered yet. (A queueMicrotask here raced
1819
+ // test schedulers and bought nothing.)
1820
+ effect(() => {
1821
+ const index = this.activeIndex();
1822
+ if (!this.isBrowser || !this.open() || index < 0)
1823
+ return;
1824
+ this.document
1825
+ .getElementById(this.optionId(index))
1826
+ ?.scrollIntoView?.({ block: 'nearest' });
1827
+ });
1828
+ }
1829
+ optionId(index) {
1830
+ return `${this.listId}-opt-${index}`;
1831
+ }
1832
+ groupId(index) {
1833
+ return `${this.listId}-group-${index}`;
1834
+ }
1835
+ sync(isOpen) {
1836
+ const panel = this.panelRef()?.nativeElement;
1837
+ if (isOpen) {
1838
+ this.previouslyFocused = this.document.activeElement;
1839
+ this.query.set('');
1840
+ this.activeIndex.set(this.firstEnabled());
1841
+ this.document.body.style.setProperty('overflow', 'hidden');
1842
+ this.scrollLocked = true;
1843
+ if (panel) {
1844
+ this.focusTrap = new MkFocusTrap(panel);
1845
+ this.focusTrap.activate();
1846
+ }
1847
+ this.inputRef()?.nativeElement.focus();
1848
+ }
1849
+ else {
1850
+ this.focusTrap?.release();
1851
+ this.focusTrap = undefined;
1852
+ if (this.scrollLocked) {
1853
+ this.document.body.style.removeProperty('overflow');
1854
+ this.scrollLocked = false;
1855
+ }
1856
+ this.previouslyFocused?.focus?.();
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Release the scroll lock + focus trap if the palette is destroyed while
1861
+ * open (e.g. a command triggered a route navigation).
1862
+ */
1863
+ ngOnDestroy() {
1864
+ this.focusTrap?.release();
1865
+ this.focusTrap = undefined;
1866
+ if (this.scrollLocked) {
1867
+ this.document.body.style.removeProperty('overflow');
1868
+ this.scrollLocked = false;
1869
+ }
1870
+ }
1871
+ onGlobalKey(event) {
1872
+ const e = event;
1873
+ if (this.hotkey() && (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
1874
+ e.preventDefault();
1875
+ this.open.update((v) => !v);
1876
+ }
1877
+ }
1878
+ onInput(event) {
1879
+ this.query.set(event.target.value);
1880
+ this.activeIndex.set(this.firstEnabled());
1881
+ }
1882
+ onKeydown(event) {
1883
+ const e = event;
1884
+ switch (e.key) {
1885
+ case 'ArrowDown':
1886
+ e.preventDefault();
1887
+ this.move(1);
1888
+ break;
1889
+ case 'ArrowUp':
1890
+ e.preventDefault();
1891
+ this.move(-1);
1892
+ break;
1893
+ case 'Home':
1894
+ e.preventDefault();
1895
+ this.activeIndex.set(this.firstEnabled());
1896
+ break;
1897
+ case 'End':
1898
+ e.preventDefault();
1899
+ this.activeIndex.set(this.lastEnabled());
1900
+ break;
1901
+ case 'Enter':
1902
+ e.preventDefault();
1903
+ if (this.activeIndex() >= 0)
1904
+ this.select(this.activeIndex());
1905
+ break;
1906
+ case 'Escape':
1907
+ e.preventDefault();
1908
+ this.close();
1909
+ break;
1910
+ }
1911
+ }
1912
+ select(index) {
1913
+ const cmd = this.filtered()[index];
1914
+ if (!cmd || cmd.disabled)
1915
+ return;
1916
+ this.commandSelected.emit(cmd);
1917
+ cmd.run?.();
1918
+ this.close();
1919
+ }
1920
+ close() {
1921
+ this.open.set(false);
1922
+ }
1923
+ move(delta) {
1924
+ const list = this.filtered();
1925
+ if (!list.length)
1926
+ return;
1927
+ let i = this.activeIndex();
1928
+ for (let step = 0; step < list.length; step++) {
1929
+ i = (i + delta + list.length) % list.length;
1930
+ if (!list[i].disabled) {
1931
+ this.activeIndex.set(i);
1932
+ return;
1933
+ }
1934
+ }
1935
+ }
1936
+ firstEnabled() {
1937
+ return this.filtered().findIndex((c) => !c.disabled);
1938
+ }
1939
+ lastEnabled() {
1940
+ const list = this.filtered();
1941
+ for (let i = list.length - 1; i >= 0; i--) {
1942
+ if (!list[i].disabled)
1943
+ return i;
1944
+ }
1945
+ return -1;
1946
+ }
1947
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkCommandPalette, deps: [], target: i0.ɵɵFactoryTarget.Component });
1948
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkCommandPalette, isStandalone: true, selector: "mk-command-palette", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, commands: { classPropertyName: "commands", publicName: "commands", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", commandSelected: "commandSelected" }, host: { listeners: { "document:keydown": "onGlobalKey($event)" }, properties: { "class.mk-command-palette--open": "open()", "attr.inert": "open() ? null : ''" }, classAttribute: "mk-command-palette" }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mk-cmdk__backdrop\" (click)=\"close()\"></div>\n\n<div\n #panel\n class=\"mk-cmdk__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"i18n.commandPaletteLabel\"\n>\n <div class=\"mk-cmdk__search\">\n <mk-icon class=\"mk-cmdk__search-icon\" name=\"search\" [size]=\"18\" />\n <input\n #input\n type=\"text\"\n class=\"mk-cmdk__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listId\"\n aria-autocomplete=\"list\"\n [attr.aria-activedescendant]=\"\n activeIndex() >= 0 ? optionId(activeIndex()) : null\n \"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n </div>\n\n <ul class=\"mk-cmdk__list\" [id]=\"listId\" role=\"listbox\">\n @for (group of grouped(); track group.name; let gi = $index) {\n @if (group.name) {\n <li\n class=\"mk-cmdk__group-section\"\n role=\"group\"\n [attr.aria-labelledby]=\"groupId(gi)\"\n >\n <div class=\"mk-cmdk__group\" [id]=\"groupId(gi)\">{{ group.name }}</div>\n <ul class=\"mk-cmdk__group-items\" role=\"none\">\n @for (entry of group.items; track entry.cmd.id) {\n <ng-container\n [ngTemplateOutlet]=\"optionTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: entry }\"\n />\n }\n </ul>\n </li>\n } @else {\n @for (entry of group.items; track entry.cmd.id) {\n <ng-container\n [ngTemplateOutlet]=\"optionTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: entry }\"\n />\n }\n }\n } @empty {\n <li class=\"mk-cmdk__empty\">{{ emptyMessage() }}</li>\n }\n </ul>\n\n <div class=\"mk-cmdk__sr\" role=\"status\">\n {{ i18n.resultsCount(filtered().length) }}\n </div>\n</div>\n\n<ng-template #optionTpl let-entry>\n <li\n class=\"mk-cmdk__option\"\n [id]=\"optionId(entry.index)\"\n role=\"option\"\n [attr.aria-selected]=\"entry.index === activeIndex()\"\n [attr.aria-disabled]=\"entry.cmd.disabled || null\"\n [class.mk-cmdk__option--active]=\"entry.index === activeIndex()\"\n [class.mk-cmdk__option--disabled]=\"entry.cmd.disabled\"\n (mousemove)=\"!entry.cmd.disabled && activeIndex.set(entry.index)\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(entry.index)\"\n >\n @if (entry.cmd.icon) {\n <mk-icon class=\"mk-cmdk__option-icon\" [name]=\"entry.cmd.icon\" [size]=\"18\" />\n }\n <span class=\"mk-cmdk__option-label\">\n {{ entry.cmd.label }}\n @if (entry.cmd.hint) {\n <span class=\"mk-cmdk__option-hint\">{{ entry.cmd.hint }}</span>\n }\n </span>\n @if (entry.cmd.shortcut) {\n <kbd class=\"mk-cmdk__shortcut\">{{ entry.cmd.shortcut }}</kbd>\n }\n </li>\n</ng-template>\n", styles: [":host{position:fixed;inset:0;z-index:var(--mk-z-dialog);display:flex;align-items:flex-start;justify-content:center;padding:12vh var(--mk-space-4) var(--mk-space-4);pointer-events:none;font-family:var(--mk-font-sans)}:host(.mk-command-palette--open){pointer-events:auto}.mk-cmdk__backdrop{position:absolute;inset:0;background-color:var(--mk-overlay-scrim);opacity:0;transition:opacity var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-command-palette--open) .mk-cmdk__backdrop{opacity:1}.mk-cmdk__panel{position:relative;display:flex;flex-direction:column;width:min(40rem,100%);max-height:70vh;overflow:hidden;background-color:var(--mk-surface);color:var(--mk-text);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-xl);opacity:0;transform:translateY(-8px) scale(.98);transition:opacity var(--mk-duration-fast) var(--mk-ease-out),transform var(--mk-duration-fast) var(--mk-ease-emphasized)}:host(.mk-command-palette--open) .mk-cmdk__panel{opacity:1;transform:none}.mk-cmdk__search{display:flex;align-items:center;gap:var(--mk-space-3);flex-shrink:0;padding:0 var(--mk-space-4);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-cmdk__search-icon{flex:none;color:var(--mk-text-subtle)}.mk-cmdk__input{flex:1 1 auto;min-width:0;height:3.25rem;padding:0;background:transparent;border:0;outline:none;color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-lg)}.mk-cmdk__input::placeholder{color:var(--mk-text-subtle)}.mk-cmdk__list{flex:1 1 auto;min-height:0;overflow-y:auto;margin:0;padding:var(--mk-space-2);list-style:none}.mk-cmdk__group-items{margin:0;padding:0;list-style:none}.mk-cmdk__group{padding:var(--mk-space-2) var(--mk-space-2) var(--mk-space-1);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-cmdk__option{display:flex;align-items:center;gap:var(--mk-space-3);padding:var(--mk-space-2) var(--mk-space-3);border-radius:var(--mk-radius-sm);color:var(--mk-text);cursor:pointer;-webkit-user-select:none;user-select:none}.mk-cmdk__option--active{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-cmdk__option--disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-cmdk__option-icon{flex:none;color:var(--mk-text-muted)}.mk-cmdk__option--active .mk-cmdk__option-icon{color:currentColor}.mk-cmdk__option-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-cmdk__option-hint{margin-inline-start:var(--mk-space-2);color:var(--mk-text-subtle);font-size:var(--mk-font-size-sm)}.mk-cmdk__shortcut{flex:none;padding:2px var(--mk-space-2);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-sm);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-family:var(--mk-font-mono);font-size:var(--mk-font-size-xs);line-height:1.4}.mk-cmdk__empty{padding:var(--mk-space-6) var(--mk-space-3);text-align:center;color:var(--mk-text-subtle);font-size:var(--mk-font-size-md)}.mk-cmdk__sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.mk-cmdk__backdrop,.mk-cmdk__panel{transition:none}}\n"], dependencies: [{ kind: "component", type: MkIcon, selector: "mk-icon", inputs: ["name", "size", "label"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1949
+ }
1950
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkCommandPalette, decorators: [{
1951
+ type: Component,
1952
+ args: [{ selector: 'mk-command-palette', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkIcon, NgTemplateOutlet], host: {
1953
+ class: 'mk-command-palette',
1954
+ '[class.mk-command-palette--open]': 'open()',
1955
+ '[attr.inert]': "open() ? null : ''",
1956
+ '(document:keydown)': 'onGlobalKey($event)',
1957
+ }, template: "<div class=\"mk-cmdk__backdrop\" (click)=\"close()\"></div>\n\n<div\n #panel\n class=\"mk-cmdk__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"i18n.commandPaletteLabel\"\n>\n <div class=\"mk-cmdk__search\">\n <mk-icon class=\"mk-cmdk__search-icon\" name=\"search\" [size]=\"18\" />\n <input\n #input\n type=\"text\"\n class=\"mk-cmdk__input\"\n role=\"combobox\"\n autocomplete=\"off\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listId\"\n aria-autocomplete=\"list\"\n [attr.aria-activedescendant]=\"\n activeIndex() >= 0 ? optionId(activeIndex()) : null\n \"\n [placeholder]=\"placeholder()\"\n [value]=\"query()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n />\n </div>\n\n <ul class=\"mk-cmdk__list\" [id]=\"listId\" role=\"listbox\">\n @for (group of grouped(); track group.name; let gi = $index) {\n @if (group.name) {\n <li\n class=\"mk-cmdk__group-section\"\n role=\"group\"\n [attr.aria-labelledby]=\"groupId(gi)\"\n >\n <div class=\"mk-cmdk__group\" [id]=\"groupId(gi)\">{{ group.name }}</div>\n <ul class=\"mk-cmdk__group-items\" role=\"none\">\n @for (entry of group.items; track entry.cmd.id) {\n <ng-container\n [ngTemplateOutlet]=\"optionTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: entry }\"\n />\n }\n </ul>\n </li>\n } @else {\n @for (entry of group.items; track entry.cmd.id) {\n <ng-container\n [ngTemplateOutlet]=\"optionTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: entry }\"\n />\n }\n }\n } @empty {\n <li class=\"mk-cmdk__empty\">{{ emptyMessage() }}</li>\n }\n </ul>\n\n <div class=\"mk-cmdk__sr\" role=\"status\">\n {{ i18n.resultsCount(filtered().length) }}\n </div>\n</div>\n\n<ng-template #optionTpl let-entry>\n <li\n class=\"mk-cmdk__option\"\n [id]=\"optionId(entry.index)\"\n role=\"option\"\n [attr.aria-selected]=\"entry.index === activeIndex()\"\n [attr.aria-disabled]=\"entry.cmd.disabled || null\"\n [class.mk-cmdk__option--active]=\"entry.index === activeIndex()\"\n [class.mk-cmdk__option--disabled]=\"entry.cmd.disabled\"\n (mousemove)=\"!entry.cmd.disabled && activeIndex.set(entry.index)\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"select(entry.index)\"\n >\n @if (entry.cmd.icon) {\n <mk-icon class=\"mk-cmdk__option-icon\" [name]=\"entry.cmd.icon\" [size]=\"18\" />\n }\n <span class=\"mk-cmdk__option-label\">\n {{ entry.cmd.label }}\n @if (entry.cmd.hint) {\n <span class=\"mk-cmdk__option-hint\">{{ entry.cmd.hint }}</span>\n }\n </span>\n @if (entry.cmd.shortcut) {\n <kbd class=\"mk-cmdk__shortcut\">{{ entry.cmd.shortcut }}</kbd>\n }\n </li>\n</ng-template>\n", styles: [":host{position:fixed;inset:0;z-index:var(--mk-z-dialog);display:flex;align-items:flex-start;justify-content:center;padding:12vh var(--mk-space-4) var(--mk-space-4);pointer-events:none;font-family:var(--mk-font-sans)}:host(.mk-command-palette--open){pointer-events:auto}.mk-cmdk__backdrop{position:absolute;inset:0;background-color:var(--mk-overlay-scrim);opacity:0;transition:opacity var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-command-palette--open) .mk-cmdk__backdrop{opacity:1}.mk-cmdk__panel{position:relative;display:flex;flex-direction:column;width:min(40rem,100%);max-height:70vh;overflow:hidden;background-color:var(--mk-surface);color:var(--mk-text);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-xl);opacity:0;transform:translateY(-8px) scale(.98);transition:opacity var(--mk-duration-fast) var(--mk-ease-out),transform var(--mk-duration-fast) var(--mk-ease-emphasized)}:host(.mk-command-palette--open) .mk-cmdk__panel{opacity:1;transform:none}.mk-cmdk__search{display:flex;align-items:center;gap:var(--mk-space-3);flex-shrink:0;padding:0 var(--mk-space-4);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-cmdk__search-icon{flex:none;color:var(--mk-text-subtle)}.mk-cmdk__input{flex:1 1 auto;min-width:0;height:3.25rem;padding:0;background:transparent;border:0;outline:none;color:var(--mk-text);font-family:inherit;font-size:var(--mk-font-size-lg)}.mk-cmdk__input::placeholder{color:var(--mk-text-subtle)}.mk-cmdk__list{flex:1 1 auto;min-height:0;overflow-y:auto;margin:0;padding:var(--mk-space-2);list-style:none}.mk-cmdk__group-items{margin:0;padding:0;list-style:none}.mk-cmdk__group{padding:var(--mk-space-2) var(--mk-space-2) var(--mk-space-1);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-cmdk__option{display:flex;align-items:center;gap:var(--mk-space-3);padding:var(--mk-space-2) var(--mk-space-3);border-radius:var(--mk-radius-sm);color:var(--mk-text);cursor:pointer;-webkit-user-select:none;user-select:none}.mk-cmdk__option--active{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-cmdk__option--disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-cmdk__option-icon{flex:none;color:var(--mk-text-muted)}.mk-cmdk__option--active .mk-cmdk__option-icon{color:currentColor}.mk-cmdk__option-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-cmdk__option-hint{margin-inline-start:var(--mk-space-2);color:var(--mk-text-subtle);font-size:var(--mk-font-size-sm)}.mk-cmdk__shortcut{flex:none;padding:2px var(--mk-space-2);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-sm);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-family:var(--mk-font-mono);font-size:var(--mk-font-size-xs);line-height:1.4}.mk-cmdk__empty{padding:var(--mk-space-6) var(--mk-space-3);text-align:center;color:var(--mk-text-subtle);font-size:var(--mk-font-size-md)}.mk-cmdk__sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.mk-cmdk__backdrop,.mk-cmdk__panel{transition:none}}\n"] }]
1958
+ }], ctorParameters: () => [], propDecorators: { panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], commands: [{ type: i0.Input, args: [{ isSignal: true, alias: "commands", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], commandSelected: [{ type: i0.Output, args: ["commandSelected"] }] } });
1959
+
1960
+ /**
1961
+ * Splitter — two resizable panes with a draggable, keyboard-operable separator
1962
+ * (ARIA `separator` with `aria-valuenow`). `horizontal` places the panes
1963
+ * side-by-side (a vertical handle); `vertical` stacks them (a horizontal
1964
+ * handle). The start pane's size is a two-way `position` model (percent).
1965
+ *
1966
+ * Project the panes with `mkSplitterStart` / `mkSplitterEnd`:
1967
+ * ```html
1968
+ * <mk-splitter [(position)]="split" [min]="20">
1969
+ * <nav mkSplitterStart>…</nav>
1970
+ * <main mkSplitterEnd>…</main>
1971
+ * </mk-splitter>
1972
+ * ```
1973
+ * Drag the handle, or focus it and use Arrow keys (Home/End jump to min/max).
1974
+ */
1975
+ class MkSplitter {
1976
+ host = inject(ElementRef);
1977
+ i18n = inject(MK_I18N);
1978
+ /** `horizontal` = side-by-side; `vertical` = stacked. */
1979
+ orientation = input('horizontal', /* @ts-ignore */
1980
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
1981
+ /** Two-way position of the separator = start-pane size, in percent (0–100). */
1982
+ position = model(50, /* @ts-ignore */
1983
+ ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
1984
+ /** Minimum start-pane size (percent). */
1985
+ min = input(10, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: numberAttribute });
1986
+ /** Maximum start-pane size (percent). */
1987
+ max = input(90, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: numberAttribute });
1988
+ /** Keyboard resize increment (percent). */
1989
+ step = input(5, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: numberAttribute });
1990
+ /** Disable resizing. */
1991
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1992
+ /** Accessible label for the separator. */
1993
+ ariaLabel = input(this.i18n.resizePanes, /* @ts-ignore */
1994
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1995
+ dragging = false;
1996
+ /** Clamped size actually applied (defends against out-of-range inputs). */
1997
+ clampedSize = computed(() => Math.min(this.max(), Math.max(this.min(), this.position())), /* @ts-ignore */
1998
+ ...(ngDevMode ? [{ debugName: "clampedSize" }] : /* istanbul ignore next */ []));
1999
+ onPointerdown(event) {
2000
+ if (this.disabled() || event.button !== 0)
2001
+ return;
2002
+ event.preventDefault();
2003
+ this.dragging = true;
2004
+ try {
2005
+ event.target.setPointerCapture(event.pointerId);
2006
+ }
2007
+ catch {
2008
+ // Pointer already lifted (fast tap) — nothing left to capture.
2009
+ }
2010
+ }
2011
+ onPointermove(event) {
2012
+ if (!this.dragging)
2013
+ return;
2014
+ const rect = this.host.nativeElement.getBoundingClientRect();
2015
+ const pct = this.orientation() === 'vertical'
2016
+ ? ((event.clientY - rect.top) / rect.height) * 100
2017
+ : ((event.clientX - rect.left) / rect.width) * 100;
2018
+ this.setSize(pct);
2019
+ }
2020
+ onPointerup(event) {
2021
+ if (!this.dragging)
2022
+ return;
2023
+ this.dragging = false;
2024
+ event.target.releasePointerCapture?.(event.pointerId);
2025
+ }
2026
+ onKeydown(event) {
2027
+ if (this.disabled())
2028
+ return;
2029
+ const vertical = this.orientation() === 'vertical';
2030
+ const dec = vertical ? 'ArrowUp' : 'ArrowLeft';
2031
+ const inc = vertical ? 'ArrowDown' : 'ArrowRight';
2032
+ switch (event.key) {
2033
+ case dec:
2034
+ event.preventDefault();
2035
+ this.setSize(this.clampedSize() - this.step());
2036
+ break;
2037
+ case inc:
2038
+ event.preventDefault();
2039
+ this.setSize(this.clampedSize() + this.step());
2040
+ break;
2041
+ case 'Home':
2042
+ event.preventDefault();
2043
+ this.setSize(this.min());
2044
+ break;
2045
+ case 'End':
2046
+ event.preventDefault();
2047
+ this.setSize(this.max());
2048
+ break;
2049
+ }
2050
+ }
2051
+ setSize(pct) {
2052
+ const next = Math.round(Math.min(this.max(), Math.max(this.min(), pct)));
2053
+ if (next !== this.position())
2054
+ this.position.set(next);
2055
+ }
2056
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkSplitter, deps: [], target: i0.ɵɵFactoryTarget.Component });
2057
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkSplitter, isStandalone: true, selector: "mk-splitter", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { position: "positionChange" }, host: { properties: { "class.mk-splitter--vertical": "orientation() === 'vertical'", "class.mk-splitter--disabled": "disabled()" }, classAttribute: "mk-splitter" }, ngImport: i0, template: "<div class=\"mk-splitter__pane mk-splitter__pane--start\" [style.flex-basis.%]=\"clampedSize()\">\n <ng-content select=\"[mkSplitterStart]\" />\n</div>\n\n<div\n class=\"mk-splitter__separator\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-orientation]=\"orientation() === 'vertical' ? 'horizontal' : 'vertical'\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-valuenow]=\"clampedSize()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-disabled]=\"disabled() || null\"\n (pointerdown)=\"onPointerdown($event)\"\n (pointermove)=\"onPointermove($event)\"\n (pointerup)=\"onPointerup($event)\"\n (pointercancel)=\"onPointerup($event)\"\n (keydown)=\"onKeydown($event)\"\n>\n <span class=\"mk-splitter__grip\" aria-hidden=\"true\"></span>\n</div>\n\n<div class=\"mk-splitter__pane mk-splitter__pane--end\">\n <ng-content select=\"[mkSplitterEnd]\" />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:row;width:100%;min-height:0;font-family:var(--mk-font-sans)}:host(.mk-splitter--vertical){flex-direction:column}.mk-splitter__pane{min-width:0;min-height:0;overflow:auto}.mk-splitter__pane--start{flex:0 0 auto}.mk-splitter__pane--end{flex:1 1 0}.mk-splitter__separator{flex:none;position:relative;display:grid;place-items:center;background-color:var(--mk-border-subtle);touch-action:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}@media(pointer:coarse){.mk-splitter__separator:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-splitter__separator:hover,.mk-splitter__separator:focus-visible{background-color:var(--mk-primary)}.mk-splitter__separator:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);outline-offset:-2px}:host:not(.mk-splitter--vertical) .mk-splitter__separator{width:7px;cursor:col-resize}:host(.mk-splitter--vertical) .mk-splitter__separator{height:7px;cursor:row-resize}:host(.mk-splitter--disabled) .mk-splitter__separator{cursor:default;pointer-events:none}.mk-splitter__grip{background-color:var(--mk-surface);border-radius:var(--mk-radius-pill);opacity:.9}:host:not(.mk-splitter--vertical) .mk-splitter__grip{width:3px;height:1.75rem}:host(.mk-splitter--vertical) .mk-splitter__grip{width:1.75rem;height:3px}@media(prefers-reduced-motion:reduce){.mk-splitter__separator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2058
+ }
2059
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkSplitter, decorators: [{
2060
+ type: Component,
2061
+ args: [{ selector: 'mk-splitter', changeDetection: ChangeDetectionStrategy.OnPush, host: {
2062
+ class: 'mk-splitter',
2063
+ '[class.mk-splitter--vertical]': "orientation() === 'vertical'",
2064
+ '[class.mk-splitter--disabled]': 'disabled()',
2065
+ }, template: "<div class=\"mk-splitter__pane mk-splitter__pane--start\" [style.flex-basis.%]=\"clampedSize()\">\n <ng-content select=\"[mkSplitterStart]\" />\n</div>\n\n<div\n class=\"mk-splitter__separator\"\n role=\"separator\"\n tabindex=\"0\"\n [attr.aria-orientation]=\"orientation() === 'vertical' ? 'horizontal' : 'vertical'\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-valuenow]=\"clampedSize()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-disabled]=\"disabled() || null\"\n (pointerdown)=\"onPointerdown($event)\"\n (pointermove)=\"onPointermove($event)\"\n (pointerup)=\"onPointerup($event)\"\n (pointercancel)=\"onPointerup($event)\"\n (keydown)=\"onKeydown($event)\"\n>\n <span class=\"mk-splitter__grip\" aria-hidden=\"true\"></span>\n</div>\n\n<div class=\"mk-splitter__pane mk-splitter__pane--end\">\n <ng-content select=\"[mkSplitterEnd]\" />\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:row;width:100%;min-height:0;font-family:var(--mk-font-sans)}:host(.mk-splitter--vertical){flex-direction:column}.mk-splitter__pane{min-width:0;min-height:0;overflow:auto}.mk-splitter__pane--start{flex:0 0 auto}.mk-splitter__pane--end{flex:1 1 0}.mk-splitter__separator{flex:none;position:relative;display:grid;place-items:center;background-color:var(--mk-border-subtle);touch-action:none;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}@media(pointer:coarse){.mk-splitter__separator:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-splitter__separator:hover,.mk-splitter__separator:focus-visible{background-color:var(--mk-primary)}.mk-splitter__separator:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);outline-offset:-2px}:host:not(.mk-splitter--vertical) .mk-splitter__separator{width:7px;cursor:col-resize}:host(.mk-splitter--vertical) .mk-splitter__separator{height:7px;cursor:row-resize}:host(.mk-splitter--disabled) .mk-splitter__separator{cursor:default;pointer-events:none}.mk-splitter__grip{background-color:var(--mk-surface);border-radius:var(--mk-radius-pill);opacity:.9}:host:not(.mk-splitter--vertical) .mk-splitter__grip{width:3px;height:1.75rem}:host(.mk-splitter--vertical) .mk-splitter__grip{width:1.75rem;height:3px}@media(prefers-reduced-motion:reduce){.mk-splitter__separator{transition:none}}\n"] }]
2066
+ }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }, { type: i0.Output, args: ["positionChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
2067
+
2068
+ /** A speed-dial action button projected into {@link MkFab}. */
2069
+ class MkFabAction {
2070
+ el = inject(ElementRef);
2071
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkFabAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2072
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MkFabAction, isStandalone: true, selector: "[mkFabAction]", host: { attributes: { "type": "button" }, classAttribute: "mk-fab__action" }, ngImport: i0 });
2073
+ }
2074
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkFabAction, decorators: [{
2075
+ type: Directive,
2076
+ args: [{
2077
+ selector: '[mkFabAction]',
2078
+ host: { class: 'mk-fab__action', type: 'button' },
2079
+ }]
2080
+ }] });
2081
+ /**
2082
+ * FAB — a floating action button, fixed to a corner (or `static` for inline
2083
+ * use). Project the icon as content and set `label` for the accessible name
2084
+ * (shown too when `extended`). Add `mkFabAction` buttons to turn it into a
2085
+ * speed-dial that reveals them on toggle.
2086
+ *
2087
+ * ```html
2088
+ * <mk-fab label="Create" position="bottom-end">
2089
+ * <svg>…</svg>
2090
+ * <button mkFabAction (click)="newDoc()">Doc</button>
2091
+ * <button mkFabAction (click)="newFolder()">Folder</button>
2092
+ * </mk-fab>
2093
+ * ```
2094
+ */
2095
+ class MkFab {
2096
+ actions = contentChildren(MkFabAction, /* @ts-ignore */
2097
+ ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
2098
+ trigger = viewChild('trigger', /* @ts-ignore */
2099
+ ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
2100
+ injector = inject(Injector);
2101
+ i18n = inject(MK_I18N);
2102
+ /** Id of the actions container, referenced by the trigger's aria-controls. */
2103
+ actionsId = mkUniqueId('mk-fab-actions');
2104
+ /** Corner placement (or `static` to render inline). */
2105
+ position = input('bottom-end', /* @ts-ignore */
2106
+ ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
2107
+ /** Semantic tone. */
2108
+ tone = input('primary', /* @ts-ignore */
2109
+ ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
2110
+ /** Size scale. */
2111
+ size = input('md', /* @ts-ignore */
2112
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
2113
+ /** Accessible label (and visible text when `extended`). */
2114
+ label = input(this.i18n.fabLabel, /* @ts-ignore */
2115
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
2116
+ /** Render as a pill with the label beside the icon. */
2117
+ extended = input(false, { ...(ngDevMode ? { debugName: "extended" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2118
+ /** Disable the button. */
2119
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2120
+ /** Two-way open state of the speed-dial. */
2121
+ open = model(false, /* @ts-ignore */
2122
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
2123
+ /** Emitted when the FAB is activated (only when it has no actions). */
2124
+ action = output();
2125
+ hasActions = computed(() => this.actions().length > 0, /* @ts-ignore */
2126
+ ...(ngDevMode ? [{ debugName: "hasActions" }] : /* istanbul ignore next */ []));
2127
+ onClick() {
2128
+ if (this.disabled())
2129
+ return;
2130
+ if (this.hasActions()) {
2131
+ const opening = !this.open();
2132
+ this.open.set(opening);
2133
+ if (opening) {
2134
+ // The actions container un-hides on the next render (zoneless);
2135
+ // only then can the first action receive focus.
2136
+ afterNextRender(() => this.actions()[0]?.el.nativeElement.focus(), { injector: this.injector });
2137
+ }
2138
+ }
2139
+ else {
2140
+ this.action.emit();
2141
+ }
2142
+ }
2143
+ onKeydown(event) {
2144
+ if (event.key === 'Escape' && this.open()) {
2145
+ event.preventDefault();
2146
+ this.open.set(false);
2147
+ // Focus may sit on an action inside the now-hidden container —
2148
+ // return it to the trigger instead of letting it drop to <body>.
2149
+ this.trigger()?.nativeElement.focus();
2150
+ }
2151
+ }
2152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkFab, deps: [], target: i0.ɵɵFactoryTarget.Component });
2153
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkFab, isStandalone: true, selector: "mk-fab", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, extended: { classPropertyName: "extended", publicName: "extended", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", action: "action" }, host: { properties: { "attr.data-position": "position()", "class.mk-fab--extended": "extended()", "class.mk-fab--open": "open()" }, classAttribute: "mk-fab" }, queries: [{ propertyName: "actions", predicate: MkFabAction, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #trigger\n type=\"button\"\n class=\"mk-fab__button\"\n [attr.data-tone]=\"tone()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"hasActions() ? open() : null\"\n [attr.aria-controls]=\"hasActions() ? actionsId : null\"\n [disabled]=\"disabled()\"\n (click)=\"onClick()\"\n (keydown)=\"onKeydown($event)\"\n>\n <span class=\"mk-fab__icon\"><ng-content /></span>\n @if (extended()) {\n <span class=\"mk-fab__label\">{{ label() }}</span>\n }\n</button>\n\n<!-- After the trigger in DOM so Tab reaches the revealed actions next;\n the visual stacking is restored with flex column-reverse in fab.scss. -->\n<div\n class=\"mk-fab__actions\"\n [id]=\"actionsId\"\n [attr.hidden]=\"hasActions() && open() ? null : ''\"\n (keydown)=\"onKeydown($event)\"\n>\n <ng-content select=\"[mkFabAction]\" />\n</div>\n", styles: ["@charset \"UTF-8\";:host{position:fixed;z-index:var(--mk-z-sticky, 100);display:flex;flex-direction:column-reverse;align-items:flex-end;gap:var(--mk-space-2)}:host([data-position=bottom-end]){bottom:calc(var(--mk-space-5) + env(safe-area-inset-bottom,0px));inset-inline-end:calc(var(--mk-space-5) + env(safe-area-inset-right,0px))}:host([data-position=bottom-start]){bottom:calc(var(--mk-space-5) + env(safe-area-inset-bottom,0px));inset-inline-start:calc(var(--mk-space-5) + env(safe-area-inset-left,0px));align-items:flex-start}:host([data-position=top-end]){top:calc(var(--mk-space-5) + env(safe-area-inset-top,0px));inset-inline-end:calc(var(--mk-space-5) + env(safe-area-inset-right,0px));flex-direction:column}:host([data-position=top-start]){top:calc(var(--mk-space-5) + env(safe-area-inset-top,0px));inset-inline-start:calc(var(--mk-space-5) + env(safe-area-inset-left,0px));align-items:flex-start;flex-direction:column}:host([data-position=static]){position:static;display:inline-flex}.mk-fab__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);height:3.5rem;min-width:3.5rem;padding:0;color:var(--mk-primary-contrast);background-color:var(--mk-primary);border:none;border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-lg);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);cursor:pointer;transition:filter var(--mk-duration-fast) var(--mk-ease-standard),transform var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-fab--extended) .mk-fab__button{padding:0 var(--mk-space-5);border-radius:var(--mk-radius-pill)}.mk-fab__button:hover:not(:disabled){filter:brightness(1.06)}.mk-fab__button:active:not(:disabled){transform:scale(.96)}.mk-fab__button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-fab__button:disabled{opacity:.6;cursor:not-allowed}.mk-fab__button[data-tone=success]{background-color:var(--mk-success);color:var(--mk-success-contrast)}.mk-fab__button[data-tone=warning]{background-color:var(--mk-warning);color:var(--mk-warning-contrast)}.mk-fab__button[data-tone=danger]{background-color:var(--mk-danger);color:var(--mk-danger-contrast)}.mk-fab__button[data-tone=info]{background-color:var(--mk-info);color:var(--mk-info-contrast)}.mk-fab__button[data-tone=neutral]{background-color:var(--mk-surface);color:var(--mk-text);box-shadow:var(--mk-shadow-md);border:var(--mk-border-width) solid var(--mk-border)}.mk-fab__icon{display:grid;place-items:center;line-height:0;font-size:var(--mk-font-size-xl, 1.5rem)}.mk-fab__label{white-space:nowrap}.mk-fab__actions{display:flex;flex-direction:column;align-items:inherit;gap:var(--mk-space-2)}.mk-fab__actions[hidden]{display:none}:host ::ng-deep .mk-fab__action{display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);height:2.75rem;min-width:2.75rem;padding:0 var(--mk-space-3);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-pill);box-shadow:var(--mk-shadow-md);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-sm);cursor:pointer;animation:mk-fab-in var(--mk-duration-fast) var(--mk-ease-out)}::ng-deep .mk-fab__action:hover{background-color:var(--mk-surface-2)}@keyframes mk-fab-in{0%{opacity:0;transform:translateY(var(--mk-space-2)) scale(.9)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.mk-fab__button,:host ::ng-deep .mk-fab__action{transition:none;animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2154
+ }
2155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkFab, decorators: [{
2156
+ type: Component,
2157
+ args: [{ selector: 'mk-fab', changeDetection: ChangeDetectionStrategy.OnPush, host: {
2158
+ class: 'mk-fab',
2159
+ '[attr.data-position]': 'position()',
2160
+ '[class.mk-fab--extended]': 'extended()',
2161
+ '[class.mk-fab--open]': 'open()',
2162
+ }, template: "<button\n #trigger\n type=\"button\"\n class=\"mk-fab__button\"\n [attr.data-tone]=\"tone()\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"hasActions() ? open() : null\"\n [attr.aria-controls]=\"hasActions() ? actionsId : null\"\n [disabled]=\"disabled()\"\n (click)=\"onClick()\"\n (keydown)=\"onKeydown($event)\"\n>\n <span class=\"mk-fab__icon\"><ng-content /></span>\n @if (extended()) {\n <span class=\"mk-fab__label\">{{ label() }}</span>\n }\n</button>\n\n<!-- After the trigger in DOM so Tab reaches the revealed actions next;\n the visual stacking is restored with flex column-reverse in fab.scss. -->\n<div\n class=\"mk-fab__actions\"\n [id]=\"actionsId\"\n [attr.hidden]=\"hasActions() && open() ? null : ''\"\n (keydown)=\"onKeydown($event)\"\n>\n <ng-content select=\"[mkFabAction]\" />\n</div>\n", styles: ["@charset \"UTF-8\";:host{position:fixed;z-index:var(--mk-z-sticky, 100);display:flex;flex-direction:column-reverse;align-items:flex-end;gap:var(--mk-space-2)}:host([data-position=bottom-end]){bottom:calc(var(--mk-space-5) + env(safe-area-inset-bottom,0px));inset-inline-end:calc(var(--mk-space-5) + env(safe-area-inset-right,0px))}:host([data-position=bottom-start]){bottom:calc(var(--mk-space-5) + env(safe-area-inset-bottom,0px));inset-inline-start:calc(var(--mk-space-5) + env(safe-area-inset-left,0px));align-items:flex-start}:host([data-position=top-end]){top:calc(var(--mk-space-5) + env(safe-area-inset-top,0px));inset-inline-end:calc(var(--mk-space-5) + env(safe-area-inset-right,0px));flex-direction:column}:host([data-position=top-start]){top:calc(var(--mk-space-5) + env(safe-area-inset-top,0px));inset-inline-start:calc(var(--mk-space-5) + env(safe-area-inset-left,0px));align-items:flex-start;flex-direction:column}:host([data-position=static]){position:static;display:inline-flex}.mk-fab__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);height:3.5rem;min-width:3.5rem;padding:0;color:var(--mk-primary-contrast);background-color:var(--mk-primary);border:none;border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-lg);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-md);font-weight:var(--mk-font-weight-medium);cursor:pointer;transition:filter var(--mk-duration-fast) var(--mk-ease-standard),transform var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-fab--extended) .mk-fab__button{padding:0 var(--mk-space-5);border-radius:var(--mk-radius-pill)}.mk-fab__button:hover:not(:disabled){filter:brightness(1.06)}.mk-fab__button:active:not(:disabled){transform:scale(.96)}.mk-fab__button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-fab__button:disabled{opacity:.6;cursor:not-allowed}.mk-fab__button[data-tone=success]{background-color:var(--mk-success);color:var(--mk-success-contrast)}.mk-fab__button[data-tone=warning]{background-color:var(--mk-warning);color:var(--mk-warning-contrast)}.mk-fab__button[data-tone=danger]{background-color:var(--mk-danger);color:var(--mk-danger-contrast)}.mk-fab__button[data-tone=info]{background-color:var(--mk-info);color:var(--mk-info-contrast)}.mk-fab__button[data-tone=neutral]{background-color:var(--mk-surface);color:var(--mk-text);box-shadow:var(--mk-shadow-md);border:var(--mk-border-width) solid var(--mk-border)}.mk-fab__icon{display:grid;place-items:center;line-height:0;font-size:var(--mk-font-size-xl, 1.5rem)}.mk-fab__label{white-space:nowrap}.mk-fab__actions{display:flex;flex-direction:column;align-items:inherit;gap:var(--mk-space-2)}.mk-fab__actions[hidden]{display:none}:host ::ng-deep .mk-fab__action{display:inline-flex;align-items:center;justify-content:center;gap:var(--mk-space-2);height:2.75rem;min-width:2.75rem;padding:0 var(--mk-space-3);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-pill);box-shadow:var(--mk-shadow-md);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-sm);cursor:pointer;animation:mk-fab-in var(--mk-duration-fast) var(--mk-ease-out)}::ng-deep .mk-fab__action:hover{background-color:var(--mk-surface-2)}@keyframes mk-fab-in{0%{opacity:0;transform:translateY(var(--mk-space-2)) scale(.9)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.mk-fab__button,:host ::ng-deep .mk-fab__action{transition:none;animation:none}}\n"] }]
2163
+ }], propDecorators: { actions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkFabAction), { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], extended: [{ type: i0.Input, args: [{ isSignal: true, alias: "extended", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], action: [{ type: i0.Output, args: ["action"] }] } });
2164
+
2165
+ /**
2166
+ * BackToTop — a floating button that appears once the page (or a scroll target)
2167
+ * has scrolled past `threshold`, and smooth-scrolls back to the top on click.
2168
+ *
2169
+ * ```html
2170
+ * <mk-back-to-top [threshold]="600" />
2171
+ * ```
2172
+ * Pass `[target]` to watch/scroll a specific scroll container instead of the
2173
+ * window.
2174
+ */
2175
+ class MkBackToTop {
2176
+ document = inject(DOCUMENT);
2177
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
2178
+ i18n = inject(MK_I18N);
2179
+ /** Scroll distance (px) before the button appears. */
2180
+ threshold = input(400, /* @ts-ignore */
2181
+ ...(ngDevMode ? [{ debugName: "threshold" }] : /* istanbul ignore next */ []));
2182
+ /** Optional scroll container; defaults to the window. */
2183
+ target = input(null, /* @ts-ignore */
2184
+ ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
2185
+ /** Accessible label. */
2186
+ ariaLabel = input(this.i18n.backToTop, /* @ts-ignore */
2187
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2188
+ /**
2189
+ * Smooth-scroll on click. Ignored under `prefers-reduced-motion: reduce`
2190
+ * (programmatic `scrollTo` is not toned down by browsers, so the component
2191
+ * falls back to an instant jump itself).
2192
+ */
2193
+ smooth = input(true, { ...(ngDevMode ? { debugName: "smooth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2194
+ scrollY = signal(0, /* @ts-ignore */
2195
+ ...(ngDevMode ? [{ debugName: "scrollY" }] : /* istanbul ignore next */ []));
2196
+ visible = computed(() => this.scrollY() > this.threshold(), /* @ts-ignore */
2197
+ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
2198
+ scrollSource;
2199
+ onScroll = () => {
2200
+ const el = this.target();
2201
+ this.scrollY.set(el
2202
+ ? el.scrollTop
2203
+ : (this.document.defaultView?.scrollY ??
2204
+ this.document.documentElement.scrollTop));
2205
+ };
2206
+ constructor() {
2207
+ // afterNextRender runs after inputs are bound, so `target()` is resolved.
2208
+ afterNextRender(() => {
2209
+ if (!this.isBrowser)
2210
+ return;
2211
+ this.scrollSource = this.target() ?? this.document.defaultView ?? undefined;
2212
+ this.scrollSource?.addEventListener('scroll', this.onScroll, {
2213
+ passive: true,
2214
+ });
2215
+ this.onScroll();
2216
+ });
2217
+ }
2218
+ scrollToTop() {
2219
+ const el = this.target();
2220
+ const behavior = this.smooth() && !this.prefersReducedMotion() ? 'smooth' : 'auto';
2221
+ if (el) {
2222
+ el.scrollTo({ top: 0, behavior });
2223
+ this.focusScrollTarget(el);
2224
+ }
2225
+ else {
2226
+ this.document.defaultView?.scrollTo({ top: 0, behavior });
2227
+ const doc = (this.document.scrollingElement ??
2228
+ this.document.documentElement);
2229
+ if (doc)
2230
+ this.focusScrollTarget(doc);
2231
+ }
2232
+ }
2233
+ /**
2234
+ * Once the page is back at the top the button hides itself, which would
2235
+ * silently drop keyboard focus on `<body>` (WCAG 2.4.3). Move focus to the
2236
+ * scroll container instead, so the tab sequence resumes from the top.
2237
+ */
2238
+ focusScrollTarget(el) {
2239
+ if (!el.hasAttribute('tabindex'))
2240
+ el.setAttribute('tabindex', '-1');
2241
+ el.focus({ preventScroll: true });
2242
+ }
2243
+ /** `prefers-reduced-motion: reduce`, SSR-safe. */
2244
+ prefersReducedMotion() {
2245
+ const win = this.document.defaultView;
2246
+ return (!!win &&
2247
+ typeof win.matchMedia === 'function' &&
2248
+ win.matchMedia('(prefers-reduced-motion: reduce)').matches);
2249
+ }
2250
+ ngOnDestroy() {
2251
+ this.scrollSource?.removeEventListener('scroll', this.onScroll);
2252
+ }
2253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBackToTop, deps: [], target: i0.ɵɵFactoryTarget.Component });
2254
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkBackToTop, isStandalone: true, selector: "mk-back-to-top", inputs: { threshold: { classPropertyName: "threshold", publicName: "threshold", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, smooth: { classPropertyName: "smooth", publicName: "smooth", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.mk-back-to-top--visible": "visible()", "attr.hidden": "visible() ? null : \"\"" }, classAttribute: "mk-back-to-top" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"mk-back-to-top__button\"\n [attr.aria-label]=\"ariaLabel()\"\n (click)=\"scrollToTop()\"\n>\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n</button>\n", styles: [":host{position:fixed;bottom:var(--mk-space-5);inset-inline-end:var(--mk-space-5);z-index:var(--mk-z-sticky, 100)}.mk-back-to-top__button{display:grid;place-items:center;width:2.75rem;height:2.75rem;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-lg);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),transform var(--mk-duration-fast) var(--mk-ease-standard)}.mk-back-to-top__button:hover{background-color:var(--mk-surface-2);transform:translateY(-2px)}.mk-back-to-top__button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}@media(prefers-reduced-motion:reduce){.mk-back-to-top__button{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2255
+ }
2256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkBackToTop, decorators: [{
2257
+ type: Component,
2258
+ args: [{ selector: 'mk-back-to-top', changeDetection: ChangeDetectionStrategy.OnPush, host: {
2259
+ class: 'mk-back-to-top',
2260
+ '[class.mk-back-to-top--visible]': 'visible()',
2261
+ '[attr.hidden]': 'visible() ? null : ""',
2262
+ }, template: "<button\n type=\"button\"\n class=\"mk-back-to-top__button\"\n [attr.aria-label]=\"ariaLabel()\"\n (click)=\"scrollToTop()\"\n>\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M12 19V5M5 12l7-7 7 7\" />\n </svg>\n</button>\n", styles: [":host{position:fixed;bottom:var(--mk-space-5);inset-inline-end:var(--mk-space-5);z-index:var(--mk-z-sticky, 100)}.mk-back-to-top__button{display:grid;place-items:center;width:2.75rem;height:2.75rem;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-lg);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),transform var(--mk-duration-fast) var(--mk-ease-standard)}.mk-back-to-top__button:hover{background-color:var(--mk-surface-2);transform:translateY(-2px)}.mk-back-to-top__button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}@media(prefers-reduced-motion:reduce){.mk-back-to-top__button{transition:none}}\n"] }]
2263
+ }], ctorParameters: () => [], propDecorators: { threshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "threshold", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], smooth: [{ type: i0.Input, args: [{ isSignal: true, alias: "smooth", required: false }] }] } });
2264
+
2265
+ /**
2266
+ * ScrollArea — a scroll container that hides the native scrollbar and renders
2267
+ * its own themed overlay bars (à la Radix ScrollArea). The projected content
2268
+ * scrolls inside a real, keyboard-focusable viewport, so native wheel / arrow
2269
+ * scrolling keeps working; the custom bars are decorative (`aria-hidden`) and
2270
+ * draggable.
2271
+ *
2272
+ * ```html
2273
+ * <mk-scroll-area maxHeight="20rem">
2274
+ * <p>…lots of content…</p>
2275
+ * </mk-scroll-area>
2276
+ * ```
2277
+ *
2278
+ * Set `orientation` to `horizontal` / `both` for a horizontal bar, and
2279
+ * `hideDelay` to auto-hide the bars after inactivity (`0` = always visible).
2280
+ */
2281
+ class MkScrollArea {
2282
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
2283
+ viewportRef = viewChild.required('viewport', /* @ts-ignore */
2284
+ ...(ngDevMode ? [{ debugName: "viewportRef" }] : /* istanbul ignore next */ []));
2285
+ contentRef = viewChild.required('content', /* @ts-ignore */
2286
+ ...(ngDevMode ? [{ debugName: "contentRef" }] : /* istanbul ignore next */ []));
2287
+ /** Optional CSS max-height for the viewport, e.g. `'20rem'`. */
2288
+ maxHeight = input('', /* @ts-ignore */
2289
+ ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
2290
+ /**
2291
+ * Accessible name for the scrollable viewport. When set, the viewport is
2292
+ * exposed as a labelled `region` landmark — recommended, since the viewport
2293
+ * is keyboard-focusable (`tabindex="0"`) and a focusable scroll container
2294
+ * should have an accessible name. When empty, no role / label is added.
2295
+ */
2296
+ ariaLabel = input('', /* @ts-ignore */
2297
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2298
+ /** Which axes render a custom bar. */
2299
+ orientation = input('vertical', /* @ts-ignore */
2300
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
2301
+ /** Auto-hide the bars after this many ms of inactivity (`0` = always on). */
2302
+ hideDelay = input(1000, { ...(ngDevMode ? { debugName: "hideDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2303
+ viewportId = mkUniqueId('mk-scroll-area');
2304
+ // Live viewport metrics, mirrored into signals so the thumbs recompute.
2305
+ scrollTop = signal(0, /* @ts-ignore */
2306
+ ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
2307
+ scrollLeft = signal(0, /* @ts-ignore */
2308
+ ...(ngDevMode ? [{ debugName: "scrollLeft" }] : /* istanbul ignore next */ []));
2309
+ scrollHeight = signal(0, /* @ts-ignore */
2310
+ ...(ngDevMode ? [{ debugName: "scrollHeight" }] : /* istanbul ignore next */ []));
2311
+ scrollWidth = signal(0, /* @ts-ignore */
2312
+ ...(ngDevMode ? [{ debugName: "scrollWidth" }] : /* istanbul ignore next */ []));
2313
+ clientHeight = signal(0, /* @ts-ignore */
2314
+ ...(ngDevMode ? [{ debugName: "clientHeight" }] : /* istanbul ignore next */ []));
2315
+ clientWidth = signal(0, /* @ts-ignore */
2316
+ ...(ngDevMode ? [{ debugName: "clientWidth" }] : /* istanbul ignore next */ []));
2317
+ showVertical = computed(() => this.orientation() !== 'horizontal', /* @ts-ignore */
2318
+ ...(ngDevMode ? [{ debugName: "showVertical" }] : /* istanbul ignore next */ []));
2319
+ showHorizontal = computed(() => this.orientation() !== 'vertical', /* @ts-ignore */
2320
+ ...(ngDevMode ? [{ debugName: "showHorizontal" }] : /* istanbul ignore next */ []));
2321
+ /** The viewport actually overflows on the given axis. */
2322
+ vScrollable = computed(() => this.scrollHeight() - this.clientHeight() > 1, /* @ts-ignore */
2323
+ ...(ngDevMode ? [{ debugName: "vScrollable" }] : /* istanbul ignore next */ []));
2324
+ hScrollable = computed(() => this.scrollWidth() - this.clientWidth() > 1, /* @ts-ignore */
2325
+ ...(ngDevMode ? [{ debugName: "hScrollable" }] : /* istanbul ignore next */ []));
2326
+ /** Thumb length as a fraction of the track (`clientHeight / scrollHeight`). */
2327
+ vThumbSize = computed(() => {
2328
+ const sh = this.scrollHeight();
2329
+ return sh > 0 ? Math.min(1, this.clientHeight() / sh) : 0;
2330
+ }, /* @ts-ignore */
2331
+ ...(ngDevMode ? [{ debugName: "vThumbSize" }] : /* istanbul ignore next */ []));
2332
+ hThumbSize = computed(() => {
2333
+ const sw = this.scrollWidth();
2334
+ return sw > 0 ? Math.min(1, this.clientWidth() / sw) : 0;
2335
+ }, /* @ts-ignore */
2336
+ ...(ngDevMode ? [{ debugName: "hThumbSize" }] : /* istanbul ignore next */ []));
2337
+ /** Thumb position as a fraction of the scroll range (0 = start, 1 = end). */
2338
+ vThumbOffset = computed(() => {
2339
+ const range = this.scrollHeight() - this.clientHeight();
2340
+ return range > 0 ? Math.min(1, Math.max(0, this.scrollTop() / range)) : 0;
2341
+ }, /* @ts-ignore */
2342
+ ...(ngDevMode ? [{ debugName: "vThumbOffset" }] : /* istanbul ignore next */ []));
2343
+ hThumbOffset = computed(() => {
2344
+ const range = this.scrollWidth() - this.clientWidth();
2345
+ return range > 0 ? Math.min(1, Math.max(0, this.scrollLeft() / range)) : 0;
2346
+ }, /* @ts-ignore */
2347
+ ...(ngDevMode ? [{ debugName: "hThumbOffset" }] : /* istanbul ignore next */ []));
2348
+ /** CSS `top` / `left` for the thumb, accounting for its own length. */
2349
+ vThumbTop = computed(() => `calc((100% - ${this.vThumbSize() * 100}%) * ${this.vThumbOffset()})`, /* @ts-ignore */
2350
+ ...(ngDevMode ? [{ debugName: "vThumbTop" }] : /* istanbul ignore next */ []));
2351
+ hThumbLeft = computed(() => `calc((100% - ${this.hThumbSize() * 100}%) * ${this.hThumbOffset()})`, /* @ts-ignore */
2352
+ ...(ngDevMode ? [{ debugName: "hThumbLeft" }] : /* istanbul ignore next */ []));
2353
+ _visible = signal(false, /* @ts-ignore */
2354
+ ...(ngDevMode ? [{ debugName: "_visible" }] : /* istanbul ignore next */ []));
2355
+ /** Whether the bars are currently shown (always true when `hideDelay` is 0). */
2356
+ barsVisible = computed(() => this.hideDelay() === 0 || this._visible(), /* @ts-ignore */
2357
+ ...(ngDevMode ? [{ debugName: "barsVisible" }] : /* istanbul ignore next */ []));
2358
+ dragAxis = null;
2359
+ dragStartPointer = 0;
2360
+ dragStartScroll = 0;
2361
+ hideTimer;
2362
+ resizeObserver;
2363
+ scrollRaf = null;
2364
+ constructor() {
2365
+ // Runs after the first render (inputs bound, view children resolved).
2366
+ afterNextRender(() => {
2367
+ if (!this.isBrowser)
2368
+ return;
2369
+ this.measure();
2370
+ const view = this.viewportRef().nativeElement;
2371
+ const content = this.contentRef().nativeElement;
2372
+ if ('ResizeObserver' in window) {
2373
+ this.resizeObserver = new ResizeObserver(() => this.measure());
2374
+ this.resizeObserver.observe(view);
2375
+ this.resizeObserver.observe(content);
2376
+ }
2377
+ });
2378
+ }
2379
+ /** Snapshot the viewport's scroll metrics into signals. */
2380
+ measure() {
2381
+ const view = this.viewportRef().nativeElement;
2382
+ this.scrollTop.set(view.scrollTop);
2383
+ this.scrollLeft.set(view.scrollLeft);
2384
+ this.scrollHeight.set(view.scrollHeight);
2385
+ this.scrollWidth.set(view.scrollWidth);
2386
+ this.clientHeight.set(view.clientHeight);
2387
+ this.clientWidth.set(view.clientWidth);
2388
+ }
2389
+ /**
2390
+ * rAF-coalesced (same pattern as `MkScrollspy`): a burst of scroll events
2391
+ * per frame triggers a single batch of layout reads + signal writes, and the
2392
+ * auto-hide idle timer restarts once per flushed frame rather than per raw
2393
+ * event.
2394
+ */
2395
+ onScroll() {
2396
+ if (!this.isBrowser || this.scrollRaf != null)
2397
+ return;
2398
+ this.scrollRaf = requestAnimationFrame(() => {
2399
+ this.scrollRaf = null;
2400
+ this.measure();
2401
+ this.show();
2402
+ this.scheduleHide();
2403
+ });
2404
+ }
2405
+ onEnter() {
2406
+ this.show();
2407
+ }
2408
+ onLeave() {
2409
+ this.scheduleHide();
2410
+ }
2411
+ // --- Thumb dragging -------------------------------------------------------
2412
+ onThumbPointerdown(event, axis) {
2413
+ if (event.button !== 0)
2414
+ return;
2415
+ event.preventDefault();
2416
+ event.stopPropagation(); // don't let the track handler page as well
2417
+ const view = this.viewportRef().nativeElement;
2418
+ this.dragAxis = axis;
2419
+ if (axis === 'vertical') {
2420
+ this.dragStartPointer = event.clientY;
2421
+ this.dragStartScroll = view.scrollTop;
2422
+ }
2423
+ else {
2424
+ this.dragStartPointer = event.clientX;
2425
+ this.dragStartScroll = view.scrollLeft;
2426
+ }
2427
+ try {
2428
+ event.target.setPointerCapture(event.pointerId);
2429
+ }
2430
+ catch {
2431
+ // Pointer already lifted (fast tap) — nothing left to capture.
2432
+ }
2433
+ this.show();
2434
+ }
2435
+ onThumbPointermove(event) {
2436
+ if (!this.dragAxis)
2437
+ return;
2438
+ const view = this.viewportRef().nativeElement;
2439
+ if (this.dragAxis === 'vertical') {
2440
+ const delta = event.clientY - this.dragStartPointer;
2441
+ // A 1px thumb move maps to scrollHeight/clientHeight px of scroll.
2442
+ const scale = this.clientHeight()
2443
+ ? this.scrollHeight() / this.clientHeight()
2444
+ : 0;
2445
+ view.scrollTop = this.dragStartScroll + delta * scale;
2446
+ }
2447
+ else {
2448
+ const delta = event.clientX - this.dragStartPointer;
2449
+ const scale = this.clientWidth()
2450
+ ? this.scrollWidth() / this.clientWidth()
2451
+ : 0;
2452
+ view.scrollLeft = this.dragStartScroll + delta * scale;
2453
+ }
2454
+ }
2455
+ onThumbPointerup(event) {
2456
+ if (!this.dragAxis)
2457
+ return;
2458
+ this.dragAxis = null;
2459
+ event.target.releasePointerCapture?.(event.pointerId);
2460
+ this.scheduleHide();
2461
+ }
2462
+ // --- Track paging ---------------------------------------------------------
2463
+ onTrackPointerdown(event, axis) {
2464
+ if (event.button !== 0)
2465
+ return;
2466
+ const view = this.viewportRef().nativeElement;
2467
+ const rect = event.currentTarget.getBoundingClientRect();
2468
+ if (axis === 'vertical') {
2469
+ const clickPos = event.clientY - rect.top;
2470
+ const thumbLen = rect.height * this.vThumbSize();
2471
+ const thumbStart = this.vThumbOffset() * (rect.height - thumbLen);
2472
+ const page = view.clientHeight * 0.9;
2473
+ if (clickPos < thumbStart)
2474
+ view.scrollTop -= page;
2475
+ else if (clickPos > thumbStart + thumbLen)
2476
+ view.scrollTop += page;
2477
+ }
2478
+ else {
2479
+ const clickPos = event.clientX - rect.left;
2480
+ const thumbLen = rect.width * this.hThumbSize();
2481
+ const thumbStart = this.hThumbOffset() * (rect.width - thumbLen);
2482
+ const page = view.clientWidth * 0.9;
2483
+ if (clickPos < thumbStart)
2484
+ view.scrollLeft -= page;
2485
+ else if (clickPos > thumbStart + thumbLen)
2486
+ view.scrollLeft += page;
2487
+ }
2488
+ }
2489
+ // --- Auto-hide ------------------------------------------------------------
2490
+ show() {
2491
+ if (!this.isBrowser)
2492
+ return;
2493
+ this._visible.set(true);
2494
+ this.clearHideTimer();
2495
+ }
2496
+ scheduleHide() {
2497
+ const delay = this.hideDelay();
2498
+ if (delay === 0 || this.dragAxis)
2499
+ return;
2500
+ this.clearHideTimer();
2501
+ this.hideTimer = setTimeout(() => this._visible.set(false), delay);
2502
+ }
2503
+ clearHideTimer() {
2504
+ if (this.hideTimer !== undefined) {
2505
+ clearTimeout(this.hideTimer);
2506
+ this.hideTimer = undefined;
2507
+ }
2508
+ }
2509
+ ngOnDestroy() {
2510
+ if (this.scrollRaf != null) {
2511
+ cancelAnimationFrame(this.scrollRaf);
2512
+ this.scrollRaf = null;
2513
+ }
2514
+ this.resizeObserver?.disconnect();
2515
+ this.clearHideTimer();
2516
+ }
2517
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkScrollArea, deps: [], target: i0.ɵɵFactoryTarget.Component });
2518
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkScrollArea, isStandalone: true, selector: "mk-scroll-area", inputs: { maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, hideDelay: { classPropertyName: "hideDelay", publicName: "hideDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onEnter()", "mouseleave": "onLeave()" }, classAttribute: "mk-scroll-area" }, viewQueries: [{ propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }, { propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #viewport\n [id]=\"viewportId\"\n class=\"mk-scroll-area__viewport\"\n tabindex=\"0\"\n [attr.role]=\"ariaLabel() ? 'region' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [style.max-height]=\"maxHeight() || null\"\n (scroll)=\"onScroll()\"\n>\n <div #content class=\"mk-scroll-area__content\">\n <ng-content />\n </div>\n</div>\n\n@if (showVertical() && vScrollable()) {\n <div\n class=\"mk-scroll-area__scrollbar mk-scroll-area__scrollbar--vertical\"\n [class.mk-scroll-area__scrollbar--visible]=\"barsVisible()\"\n aria-hidden=\"true\"\n (pointerdown)=\"onTrackPointerdown($event, 'vertical')\"\n >\n <div\n class=\"mk-scroll-area__thumb\"\n [style.height.%]=\"vThumbSize() * 100\"\n [style.top]=\"vThumbTop()\"\n (pointerdown)=\"onThumbPointerdown($event, 'vertical')\"\n (pointermove)=\"onThumbPointermove($event)\"\n (pointerup)=\"onThumbPointerup($event)\"\n (pointercancel)=\"onThumbPointerup($event)\"\n ></div>\n </div>\n}\n\n@if (showHorizontal() && hScrollable()) {\n <div\n class=\"mk-scroll-area__scrollbar mk-scroll-area__scrollbar--horizontal\"\n [class.mk-scroll-area__scrollbar--visible]=\"barsVisible()\"\n aria-hidden=\"true\"\n (pointerdown)=\"onTrackPointerdown($event, 'horizontal')\"\n >\n <div\n class=\"mk-scroll-area__thumb\"\n [style.width.%]=\"hThumbSize() * 100\"\n [style.left]=\"hThumbLeft()\"\n (pointerdown)=\"onThumbPointerdown($event, 'horizontal')\"\n (pointermove)=\"onThumbPointermove($event)\"\n (pointerup)=\"onThumbPointerup($event)\"\n (pointercancel)=\"onThumbPointerup($event)\"\n ></div>\n </div>\n}\n", styles: [":host{--_bar-size: .75rem;position:relative;display:block;min-height:0;font-family:var(--mk-font-sans)}.mk-scroll-area__viewport{width:100%;height:100%;overflow:auto;overscroll-behavior:contain;scrollbar-width:none;-ms-overflow-style:none}.mk-scroll-area__viewport::-webkit-scrollbar{display:none}.mk-scroll-area__viewport:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);outline-offset:-2px}.mk-scroll-area__content{min-width:0;min-height:0}.mk-scroll-area__scrollbar{position:absolute;z-index:1;padding:var(--mk-space-1);background-color:transparent;touch-action:none;opacity:0;transition:opacity var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-scroll-area__scrollbar--visible{opacity:1}.mk-scroll-area__scrollbar:hover{background-color:var(--mk-hover-overlay)}.mk-scroll-area__scrollbar--vertical{top:0;inset-inline-end:0;bottom:0;width:var(--_bar-size)}.mk-scroll-area__scrollbar--horizontal{left:0;right:0;bottom:0;height:var(--_bar-size)}.mk-scroll-area__scrollbar--vertical{bottom:var(--_bar-size)}.mk-scroll-area__scrollbar--horizontal{inset-inline-end:var(--_bar-size)}.mk-scroll-area__thumb{position:absolute;border-radius:var(--mk-radius-pill);background-color:var(--mk-scrollbar-thumb, var(--mk-text-muted));transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-scroll-area__thumb:hover,.mk-scroll-area__thumb:active{background-color:var(--mk-scrollbar-thumb-hover, var(--mk-border))}.mk-scroll-area__scrollbar--vertical .mk-scroll-area__thumb{left:var(--mk-space-1);right:var(--mk-space-1);min-height:1.5rem}.mk-scroll-area__scrollbar--horizontal .mk-scroll-area__thumb{top:var(--mk-space-1);bottom:var(--mk-space-1);min-width:1.5rem}@media(prefers-reduced-motion:reduce){.mk-scroll-area__scrollbar,.mk-scroll-area__thumb{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2519
+ }
2520
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkScrollArea, decorators: [{
2521
+ type: Component,
2522
+ args: [{ selector: 'mk-scroll-area', changeDetection: ChangeDetectionStrategy.OnPush, host: {
2523
+ class: 'mk-scroll-area',
2524
+ '(mouseenter)': 'onEnter()',
2525
+ '(mouseleave)': 'onLeave()',
2526
+ }, template: "<div\n #viewport\n [id]=\"viewportId\"\n class=\"mk-scroll-area__viewport\"\n tabindex=\"0\"\n [attr.role]=\"ariaLabel() ? 'region' : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [style.max-height]=\"maxHeight() || null\"\n (scroll)=\"onScroll()\"\n>\n <div #content class=\"mk-scroll-area__content\">\n <ng-content />\n </div>\n</div>\n\n@if (showVertical() && vScrollable()) {\n <div\n class=\"mk-scroll-area__scrollbar mk-scroll-area__scrollbar--vertical\"\n [class.mk-scroll-area__scrollbar--visible]=\"barsVisible()\"\n aria-hidden=\"true\"\n (pointerdown)=\"onTrackPointerdown($event, 'vertical')\"\n >\n <div\n class=\"mk-scroll-area__thumb\"\n [style.height.%]=\"vThumbSize() * 100\"\n [style.top]=\"vThumbTop()\"\n (pointerdown)=\"onThumbPointerdown($event, 'vertical')\"\n (pointermove)=\"onThumbPointermove($event)\"\n (pointerup)=\"onThumbPointerup($event)\"\n (pointercancel)=\"onThumbPointerup($event)\"\n ></div>\n </div>\n}\n\n@if (showHorizontal() && hScrollable()) {\n <div\n class=\"mk-scroll-area__scrollbar mk-scroll-area__scrollbar--horizontal\"\n [class.mk-scroll-area__scrollbar--visible]=\"barsVisible()\"\n aria-hidden=\"true\"\n (pointerdown)=\"onTrackPointerdown($event, 'horizontal')\"\n >\n <div\n class=\"mk-scroll-area__thumb\"\n [style.width.%]=\"hThumbSize() * 100\"\n [style.left]=\"hThumbLeft()\"\n (pointerdown)=\"onThumbPointerdown($event, 'horizontal')\"\n (pointermove)=\"onThumbPointermove($event)\"\n (pointerup)=\"onThumbPointerup($event)\"\n (pointercancel)=\"onThumbPointerup($event)\"\n ></div>\n </div>\n}\n", styles: [":host{--_bar-size: .75rem;position:relative;display:block;min-height:0;font-family:var(--mk-font-sans)}.mk-scroll-area__viewport{width:100%;height:100%;overflow:auto;overscroll-behavior:contain;scrollbar-width:none;-ms-overflow-style:none}.mk-scroll-area__viewport::-webkit-scrollbar{display:none}.mk-scroll-area__viewport:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);outline-offset:-2px}.mk-scroll-area__content{min-width:0;min-height:0}.mk-scroll-area__scrollbar{position:absolute;z-index:1;padding:var(--mk-space-1);background-color:transparent;touch-action:none;opacity:0;transition:opacity var(--mk-duration-fast) var(--mk-ease-standard),background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-scroll-area__scrollbar--visible{opacity:1}.mk-scroll-area__scrollbar:hover{background-color:var(--mk-hover-overlay)}.mk-scroll-area__scrollbar--vertical{top:0;inset-inline-end:0;bottom:0;width:var(--_bar-size)}.mk-scroll-area__scrollbar--horizontal{left:0;right:0;bottom:0;height:var(--_bar-size)}.mk-scroll-area__scrollbar--vertical{bottom:var(--_bar-size)}.mk-scroll-area__scrollbar--horizontal{inset-inline-end:var(--_bar-size)}.mk-scroll-area__thumb{position:absolute;border-radius:var(--mk-radius-pill);background-color:var(--mk-scrollbar-thumb, var(--mk-text-muted));transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-scroll-area__thumb:hover,.mk-scroll-area__thumb:active{background-color:var(--mk-scrollbar-thumb-hover, var(--mk-border))}.mk-scroll-area__scrollbar--vertical .mk-scroll-area__thumb{left:var(--mk-space-1);right:var(--mk-space-1);min-height:1.5rem}.mk-scroll-area__scrollbar--horizontal .mk-scroll-area__thumb{top:var(--mk-space-1);bottom:var(--mk-space-1);min-width:1.5rem}@media(prefers-reduced-motion:reduce){.mk-scroll-area__scrollbar,.mk-scroll-area__thumb{transition:none}}\n"] }]
2527
+ }], ctorParameters: () => [], propDecorators: { viewportRef: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], contentRef: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], hideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideDelay", required: false }] }] } });
2528
+
2529
+ /**
2530
+ * Navigation & Layout component group barrel.
2531
+ *
2532
+ * Re-exports every public symbol of the group via each component folder's
2533
+ * `index.ts`. Not part of the library `public-api` by itself — consumed by the
2534
+ * top-level surface when the group is wired in.
2535
+ */
2536
+
2537
+ /**
2538
+ * Generated bundle index. Do not edit.
2539
+ */
2540
+
2541
+ export { MkAccordion, MkAccordionHeader, MkAccordionItem, MkAppShell, MkBackToTop, MkBreadcrumb, MkBreadcrumbItem, MkCommandPalette, MkDrawer, MkFab, MkFabAction, MkMenu, MkMenuItem, MkMenuTrigger, MkNavGroup, MkNavItem, MkNavList, MkPageHeader, MkPagination, MkScrollArea, MkSplitter, MkStep, MkStepper, MkTab, MkTabs, MkToolbar, MkTree };
2542
+ //# sourceMappingURL=mk-kit-ui-navigation.mjs.map