@mk-kit/ui 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mk-kit/ui",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -15,7 +15,7 @@ type MkThemePreference = 'light' | 'dark' | 'system';
15
15
  /** Concrete resolved theme (never `system`). */
16
16
  type MkResolvedTheme = 'light' | 'dark';
17
17
  /** Common placement values for overlays (menus, tooltips, popovers). */
18
- type MkPlacement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'right';
18
+ type MkPlacement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end';
19
19
 
20
20
  /**
21
21
  * Global control-density mode.
@@ -339,6 +339,12 @@ declare class MkAnchoredPanel implements AfterViewInit, OnDestroy {
339
339
  readonly flip: _angular_core.InputSignal<boolean>;
340
340
  /** Clamp the panel inside the viewport. */
341
341
  readonly clamp: _angular_core.InputSignal<boolean>;
342
+ /**
343
+ * Predicate consulted before an outside pointerdown dismisses the panel.
344
+ * Return `true` for targets that must keep it open — e.g. a nested panel
345
+ * (a submenu) that lives in the top layer outside this element.
346
+ */
347
+ readonly keepOpenWhen: _angular_core.InputSignal<((target: Node) => boolean) | null>;
342
348
  /** Emitted on an outside pointerdown or when the window loses focus. */
343
349
  readonly dismiss: _angular_core.OutputEmitterRef<void>;
344
350
  private popover;
@@ -371,7 +377,7 @@ declare class MkAnchoredPanel implements AfterViewInit, OnDestroy {
371
377
  private resolveAnchorRect;
372
378
  ngOnDestroy(): void;
373
379
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkAnchoredPanel, never>;
374
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MkAnchoredPanel, "[mkAnchoredPanel]", ["mkAnchoredPanel"], { "anchor": { "alias": "mkAnchoredPanelFor"; "required": false; "isSignal": true; }; "anchorRect": { "alias": "anchorRect"; "required": false; "isSignal": true; }; "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; "matchWidth": { "alias": "matchWidth"; "required": false; "isSignal": true; }; "flip": { "alias": "flip"; "required": false; "isSignal": true; }; "clamp": { "alias": "clamp"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, never, never, true, never>;
380
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MkAnchoredPanel, "[mkAnchoredPanel]", ["mkAnchoredPanel"], { "anchor": { "alias": "mkAnchoredPanelFor"; "required": false; "isSignal": true; }; "anchorRect": { "alias": "anchorRect"; "required": false; "isSignal": true; }; "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; "matchWidth": { "alias": "matchWidth"; "required": false; "isSignal": true; }; "flip": { "alias": "flip"; "required": false; "isSignal": true; }; "clamp": { "alias": "clamp"; "required": false; "isSignal": true; }; "keepOpenWhen": { "alias": "keepOpenWhen"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, never, never, true, never>;
375
381
  }
376
382
 
377
383
  /** Direction passed to the sort announcer. */
@@ -740,6 +746,16 @@ interface MkI18nStrings {
740
746
  chooseWeek: string;
741
747
  openCalendar: string;
742
748
  openTimeList: string;
749
+ /** Accessible name of the chevron segment of `mk-split-button`. */
750
+ moreActions: string;
751
+ /** Placeholder of `mk-datetime-picker`. */
752
+ selectDateTime: string;
753
+ /** Dialog label of the `mk-datetime-picker` panel. */
754
+ chooseDateTime: string;
755
+ /** Listbox label of the time list inside `mk-datetime-picker`. */
756
+ chooseTime: string;
757
+ /** Toggle-button label of `mk-datetime-picker`. */
758
+ openDateTimePicker: string;
743
759
  /** Mini-date segment names. */
744
760
  daySegment: string;
745
761
  monthSegment: string;
@@ -53,9 +53,11 @@ declare function parseISODate(value: string | null | undefined): Date | null;
53
53
  /**
54
54
  * Format `date` using a small pattern set. Supported tokens:
55
55
  * `yyyy` (4-digit year), `MMMM` (full month), `MMM` (short month),
56
- * `MM` (2-digit month), `dd` (2-digit day), `d` (day), `ddd` (short weekday).
57
- * Longer tokens are matched first so `MMMM` wins over `MM`.
58
- * Month/weekday names come from `names` when given, else English.
56
+ * `MM` (2-digit month), `dd` (2-digit day), `d` (day), `ddd` (short weekday),
57
+ * `HH` / `H` (24-hour, padded / bare), `hh` / `h` (12-hour, padded / bare),
58
+ * `mm` (2-digit minutes) and `a` (`AM` / `PM`). Longer tokens are matched
59
+ * first so `MMMM` wins over `MM`. Month/weekday names come from `names` when
60
+ * given, else English.
59
61
  */
60
62
  declare function formatDate(date: Date, pattern: string, names?: MkDateNames): string;
61
63
  /**
@@ -313,7 +315,7 @@ declare class MkDatePicker implements ControlValueAccessor, Validator {
313
315
  }
314
316
 
315
317
  /** A selectable time option. */
316
- interface MkTimeOption {
318
+ interface MkTimeOption$1 {
317
319
  /** Canonical `HH:mm` (24h) value. */
318
320
  value: string;
319
321
  /** Display label (respects `hour12`). */
@@ -401,7 +403,7 @@ declare class MkTimePicker implements ControlValueAccessor, Validator {
401
403
  protected readonly describedBy: _angular_core.Signal<string | null>;
402
404
  protected readonly showClear: _angular_core.Signal<boolean>;
403
405
  /** Generated, min/max-filtered list of selectable times. */
404
- protected readonly options: _angular_core.Signal<MkTimeOption[]>;
406
+ protected readonly options: _angular_core.Signal<MkTimeOption$1[]>;
405
407
  protected readonly selectedIndex: _angular_core.Signal<number>;
406
408
  constructor();
407
409
  protected optionId(index: number): string;
@@ -454,6 +456,161 @@ declare class MkTimePicker implements ControlValueAccessor, Validator {
454
456
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkTimePicker, "mk-time-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "valueFormat": { "alias": "valueFormat"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "hour12": { "alias": "hour12"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
455
457
  }
456
458
 
459
+ /** A selectable time-of-day option in the panel's list. */
460
+ interface MkTimeOption {
461
+ /** Minutes since midnight. */
462
+ minutes: number;
463
+ /** Display label (respects `hour12`). */
464
+ label: string;
465
+ /** Stable id for `aria-activedescendant`. */
466
+ id: string;
467
+ }
468
+ /**
469
+ * DateTimePicker — one field for a date **and** a time of day. The panel pairs
470
+ * an `mk-calendar` with a time list generated from `step`; the value is a
471
+ * single `Date` carrying both parts in local time (seconds and milliseconds
472
+ * zeroed). Implements `ControlValueAccessor` and a two-way `value` model, so it
473
+ * works with `[(ngModel)]`, reactive forms and `[(value)]`.
474
+ *
475
+ * Interaction: picking a day keeps the panel open and — when a value already
476
+ * exists — moves that value to the new day, preserving its time; picking a
477
+ * time commits and closes. With no value yet, the day is held until a time is
478
+ * chosen (so a half-picked datetime is never emitted). Typing also works:
479
+ * `2026-08-26 14:30`, `2026-08-26T09:05`, `Aug 26, 2026 2:30 pm` and a bare
480
+ * ISO date (→ midnight) are parsed on Enter/blur.
481
+ *
482
+ * `min` / `max` bound the whole instant at minute precision: the calendar
483
+ * greys out days outside the range and the time list hides times outside it
484
+ * on the boundary days. Validation reports `mkMinDate` / `mkMaxDate`.
485
+ *
486
+ * When nested in an `mk-form-field` it adopts the field's id and aria wiring.
487
+ *
488
+ * ```html
489
+ * <mk-datetime-picker [(value)]="startsAt" [step]="15" [min]="now" clearable />
490
+ * <mk-datetime-picker [(value)]="startsAt" hour12 />
491
+ * ```
492
+ */
493
+ declare class MkDateTimePicker implements ControlValueAccessor, Validator {
494
+ protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
495
+ private readonly field;
496
+ private readonly host;
497
+ private readonly injector;
498
+ private readonly inputRef;
499
+ /** The panel — lives in the top layer once opened. */
500
+ private readonly panelRef;
501
+ private readonly timesRef;
502
+ /** Two-way selected date-time (local; seconds and ms are zero). */
503
+ readonly value: _angular_core.ModelSignal<Date | null>;
504
+ /** Earliest selectable instant (inclusive, minute precision). */
505
+ readonly min: _angular_core.InputSignal<Date | null>;
506
+ /** Latest selectable instant (inclusive, minute precision). */
507
+ readonly max: _angular_core.InputSignal<Date | null>;
508
+ /** Predicate marking individual days as disabled (threaded to the calendar). */
509
+ readonly disabledDate: _angular_core.InputSignal<((d: Date) => boolean) | null>;
510
+ /** Placeholder shown when empty. */
511
+ readonly placeholder: _angular_core.InputSignal<string>;
512
+ /**
513
+ * Pattern used to render the value in the field. Defaults to
514
+ * `'MMM d, yyyy HH:mm'`, or `'MMM d, yyyy h:mm a'` when `hour12` is set.
515
+ */
516
+ readonly displayFormat: _angular_core.InputSignal<string | null>;
517
+ /** Interval between generated time options, in minutes. */
518
+ readonly step: _angular_core.InputSignalWithTransform<number, unknown>;
519
+ /** Display 12-hour times with AM/PM (the model is a `Date` either way). */
520
+ readonly hour12: _angular_core.InputSignalWithTransform<boolean, unknown>;
521
+ /** Disable the control. */
522
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
523
+ /** Show a clear button when a value is selected. */
524
+ readonly clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
525
+ /** Force invalid styling + `aria-invalid`. */
526
+ readonly invalid: _angular_core.InputSignalWithTransform<boolean, unknown>;
527
+ /** First column of the calendar week (0 = Sunday). */
528
+ readonly firstDayOfWeek: _angular_core.InputSignalWithTransform<number, unknown>;
529
+ /** Control size. Ignored when nested in an `mk-form-field`. */
530
+ readonly size: _angular_core.InputSignal<MkSize>;
531
+ protected readonly open: _angular_core.WritableSignal<boolean>;
532
+ /** Editable text mirror of the field. */
533
+ protected readonly inputText: _angular_core.WritableSignal<string>;
534
+ /** Keyboard-active option in the time list (index into `options`). */
535
+ protected readonly activeIndex: _angular_core.WritableSignal<number>;
536
+ /** Day picked in the calendar while there is no value yet. */
537
+ private readonly pendingDay;
538
+ private readonly cvaDisabled;
539
+ private onChange;
540
+ private onTouched;
541
+ readonly inputId: string;
542
+ readonly panelId: string;
543
+ readonly listId: string;
544
+ protected readonly effectiveSize: _angular_core.Signal<MkSize>;
545
+ protected readonly isDisabled: _angular_core.Signal<boolean>;
546
+ protected readonly isInvalid: _angular_core.Signal<boolean>;
547
+ protected readonly isRequired: _angular_core.Signal<boolean>;
548
+ protected readonly describedBy: _angular_core.Signal<string | null>;
549
+ protected readonly showClear: _angular_core.Signal<boolean>;
550
+ protected readonly effectiveFormat: _angular_core.Signal<string>;
551
+ /** The day the calendar shows as selected: the value's day, else the pending one. */
552
+ protected readonly calendarValue: _angular_core.Signal<Date | null>;
553
+ /** Day-granular bounds for the calendar (inclusive). */
554
+ protected readonly calendarMin: _angular_core.Signal<Date | null>;
555
+ protected readonly calendarMax: _angular_core.Signal<Date | null>;
556
+ /**
557
+ * Generated time options. On the `min` / `max` boundary day the list only
558
+ * offers times inside the bound; on other days every step is listed.
559
+ */
560
+ protected readonly options: _angular_core.Signal<MkTimeOption[]>;
561
+ protected readonly selectedIndex: _angular_core.Signal<number>;
562
+ constructor();
563
+ private displayTime;
564
+ /**
565
+ * Accepts `YYYY-MM-DD[ T]HH:mm[ am|pm]`, a bare ISO date (→ midnight), any
566
+ * `Date.parse`-able date followed by a time, or a full `Date.parse`-able
567
+ * string. Seconds are dropped.
568
+ */
569
+ private parse;
570
+ private clamp;
571
+ protected toggle(): void;
572
+ protected openPanel(): void;
573
+ protected close(): void;
574
+ /** Index of the first option at or after `d`'s time of day (for the initial highlight). */
575
+ private nearestIndex;
576
+ protected onCalendarPick(day: Date | null): void;
577
+ protected selectTime(index: number): void;
578
+ protected onListKeydown(event: Event): void;
579
+ private moveActive;
580
+ private scrollActiveIntoView;
581
+ protected onInput(event: Event): void;
582
+ protected onInputKeydown(event: Event): void;
583
+ protected clear(): void;
584
+ private commitInput;
585
+ private setValue;
586
+ protected onFocusOut(event: Event): void;
587
+ /**
588
+ * Escape pressed inside the (teleported) panel — close and return focus to
589
+ * the input. `preventDefault` + `stopPropagation` keep any outer Escape
590
+ * handling (e.g. a containing dialog) from also firing.
591
+ */
592
+ protected onPanelEscape(event: Event): void;
593
+ /**
594
+ * Focus left the teleported panel. The host `(focusout)` never sees this —
595
+ * the panel lives under `document.body` — so Tab-out is handled here: close
596
+ * unless focus moved back into the field or stayed inside the panel.
597
+ */
598
+ protected onPanelFocusout(event: FocusEvent): void;
599
+ writeValue(value: Date | null): void;
600
+ registerOnChange(fn: (value: Date | null) => void): void;
601
+ registerOnTouched(fn: () => void): void;
602
+ setDisabledState(isDisabled: boolean): void;
603
+ private readonly validatorChange;
604
+ /**
605
+ * Reports `mkMinDate` / `mkMaxDate` against the `[min]` and `[max]` inputs,
606
+ * comparing whole instants at minute precision.
607
+ */
608
+ validate(control: AbstractControl): ValidationErrors | null;
609
+ registerOnValidatorChange(fn: () => void): void;
610
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDateTimePicker, never>;
611
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDateTimePicker, "mk-datetime-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "disabledDate": { "alias": "disabledDate"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "displayFormat": { "alias": "displayFormat"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "hour12": { "alias": "hour12"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "firstDayOfWeek": { "alias": "firstDayOfWeek"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
612
+ }
613
+
457
614
  /** A selected date range. Either endpoint may be `null` while incomplete. */
458
615
  interface MkDateRange {
459
616
  start: Date | null;
@@ -1167,5 +1324,5 @@ declare class MkMiniDate implements ControlValueAccessor, Validator {
1167
1324
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMiniDate, "mk-mini-date", ["mkMiniDate"], { "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "order": { "alias": "order"; "required": false; "isSignal": true; }; "yearMin": { "alias": "yearMin"; "required": false; "isSignal": true; }; "yearMax": { "alias": "yearMax"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
1168
1325
  }
1169
1326
 
1170
- export { MkCalendar, MkDatePicker, MkDateRangePicker, MkEventCalendar, MkMiniDate, MkMonthPicker, MkTimePicker, MkWeekPicker, addDays, addMonths, buildMonthMatrix, clampDate, endOfMonth, endOfWeek, formatDate, formatISODate, getISOWeek, getMonthNames, getWeekdayFullName, getWeekdayNames, isAfter, isBefore, isSameDay, isSameMonth, parseISODate, startOfDay, startOfMonth, startOfWeek };
1327
+ export { MkCalendar, MkDatePicker, MkDateRangePicker, MkDateTimePicker, MkEventCalendar, MkMiniDate, MkMonthPicker, MkTimePicker, MkWeekPicker, addDays, addMonths, buildMonthMatrix, clampDate, endOfMonth, endOfWeek, formatDate, formatISODate, getISOWeek, getMonthNames, getWeekdayFullName, getWeekdayNames, isAfter, isBefore, isSameDay, isSameMonth, parseISODate, startOfDay, startOfMonth, startOfWeek };
1171
1328
  export type { MkCalendarEvent, MkCalendarEventEdit, MkCalendarView, MkDateRange, MkMiniDateOrder, MkMonthPickerMode, MkWeek, MkWeekday };
@@ -1,7 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { ElementRef, OnDestroy, TemplateRef } from '@angular/core';
3
3
  import * as _mk_kit_ui_core from '@mk-kit/ui/core';
4
- import { MkTone, MkSize } from '@mk-kit/ui/core';
4
+ import { MkPlacement, MkVariant, MkTone, MkSize } from '@mk-kit/ui/core';
5
5
 
6
6
  /**
7
7
  * A single tab + its panel. Declared inside `<mk-tabs>`; the projected content
@@ -231,6 +231,57 @@ declare class MkPagination {
231
231
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkPagination, "mk-pagination", never, { "total": { "alias": "total"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageCount": { "alias": "pageCount"; "required": false; "isSignal": true; }; "page": { "alias": "page"; "required": false; "isSignal": true; }; "siblingCount": { "alias": "siblingCount"; "required": false; "isSignal": true; }; "boundaryCount": { "alias": "boundaryCount"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, { "page": "pageChange"; }, never, never, true, never>;
232
232
  }
233
233
 
234
+ /**
235
+ * An item within an `<mk-menu>`. Renders as an ARIA `menuitem` with an optional
236
+ * icon slot (`[mkMenuItemIcon]`), disabled and danger states, and either emits
237
+ * `action` or navigates when `href` is set. Activating closes the menu.
238
+ *
239
+ * With `[mkSubmenuFor]` pointing at a nested `<mk-menu>` the item becomes a
240
+ * submenu trigger instead: it shows a chevron, exposes `aria-haspopup` /
241
+ * `aria-expanded`, opens the submenu beside itself on hover, ArrowRight,
242
+ * Enter, Space or click, and never emits `action`.
243
+ *
244
+ * ```html
245
+ * <mk-menu-item (action)="rename()">
246
+ * <svg mkMenuItemIcon>…</svg> Rename
247
+ * </mk-menu-item>
248
+ * <mk-menu-item danger (action)="remove()">Delete</mk-menu-item>
249
+ * <mk-menu-item href="/help">Help</mk-menu-item>
250
+ * <mk-menu-item [mkSubmenuFor]="more">More</mk-menu-item>
251
+ * ```
252
+ */
253
+ declare class MkMenuItem implements OnDestroy {
254
+ private readonly el;
255
+ private readonly document;
256
+ private readonly menu;
257
+ /** Prevent selection and focus. */
258
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
259
+ /** Destructive styling (e.g. Delete). */
260
+ readonly danger: _angular_core.InputSignalWithTransform<boolean, unknown>;
261
+ /** When set, activating the item navigates here. */
262
+ readonly href: _angular_core.InputSignal<string | undefined>;
263
+ /** A nested `mk-menu` this item opens as a submenu. */
264
+ readonly submenu: _angular_core.InputSignal<MkMenu | undefined>;
265
+ /** Emitted when the item is activated (not for disabled or submenu items). */
266
+ readonly action: _angular_core.OutputEmitterRef<void>;
267
+ private hoverTimer?;
268
+ activate(event?: Event): void;
269
+ /** Open the submenu beside this item; `focus` moves focus to its first item. */
270
+ openSubmenu(focus: boolean): void;
271
+ protected onMouseEnter(): void;
272
+ protected onMouseLeave(): void;
273
+ private cancelHover;
274
+ /** Move DOM focus to this item (roving focus). */
275
+ focusEl(): void;
276
+ /** Whether `node` lives inside this item. */
277
+ contains(node: Node | null): boolean;
278
+ /** Lowercased text content, used for typeahead matching. */
279
+ text(): string;
280
+ ngOnDestroy(): void;
281
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkMenuItem, never>;
282
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMenuItem, "mk-menu-item", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "danger": { "alias": "danger"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "submenu": { "alias": "mkSubmenuFor"; "required": false; "isSignal": true; }; }, { "action": "action"; }, never, ["[mkMenuItemIcon]", "*"], true, never>;
283
+ }
284
+
234
285
  /**
235
286
  * Dropdown menu implementing the ARIA menu pattern (roving focus, Arrow / Home
236
287
  * / End / typeahead, Enter/Space to activate). Attach it to a trigger with the
@@ -247,12 +298,33 @@ declare class MkPagination {
247
298
  * <mk-menu-item danger (action)="del()">Delete</mk-menu-item>
248
299
  * </mk-menu>
249
300
  * ```
301
+ *
302
+ * **Submenus.** Point an item at a nested menu with `[mkSubmenuFor]`; declare
303
+ * the nested `<mk-menu>` anywhere inside the parent menu. The submenu opens
304
+ * beside its item on hover (after a short delay), on ArrowRight / Enter /
305
+ * Space / click, and closes with ArrowLeft or Escape (returning focus to the
306
+ * item) — only that level, per the APG menu pattern. Activating any leaf item
307
+ * closes the whole chain. In RTL the submenu opens on the left and the arrow
308
+ * keys swap.
309
+ *
310
+ * ```html
311
+ * <mk-menu #menu>
312
+ * <mk-menu-item [mkSubmenuFor]="exportMenu">Export</mk-menu-item>
313
+ * <mk-menu #exportMenu>
314
+ * <mk-menu-item (action)="csv()">CSV</mk-menu-item>
315
+ * <mk-menu-item (action)="pdf()">PDF</mk-menu-item>
316
+ * </mk-menu>
317
+ * </mk-menu>
318
+ * ```
250
319
  */
251
320
  declare class MkMenu implements OnDestroy {
252
321
  private readonly document;
253
322
  private readonly injector;
254
323
  private readonly isBrowser;
324
+ /** The enclosing menu when this one is a submenu. */
325
+ private readonly parent;
255
326
  private readonly items;
327
+ private readonly panelRef;
256
328
  /** Stable id for `aria-controls` on the trigger. */
257
329
  readonly panelId: string;
258
330
  private readonly _open;
@@ -267,16 +339,27 @@ declare class MkMenu implements OnDestroy {
267
339
  x: number;
268
340
  y: number;
269
341
  } | undefined>;
342
+ /** Where the panel sits relative to its anchor (submenus open sideways). */
343
+ protected readonly placement: _angular_core.WritableSignal<MkPlacement>;
270
344
  private triggerEl;
271
345
  private typeahead;
272
346
  private typeaheadTimer?;
347
+ /** Submenus that are currently open under this menu. */
348
+ private readonly openChildren;
349
+ /**
350
+ * Whether this menu is a submenu of another `mk-menu`. Submenus open
351
+ * sideways, close a single level on Escape, and swap ArrowLeft/ArrowRight.
352
+ */
353
+ get isSubmenu(): boolean;
273
354
  /**
274
355
  * Open the menu anchored to `trigger`. `focus` picks the item that receives
275
356
  * focus once the panel is painted: `true`/`'first'` for the first enabled
276
357
  * item, `'last'` for the last (ArrowUp on a menu button, per the APG
277
358
  * menu-button pattern), `false` to leave focus where it is (mouse open).
359
+ * `placement` overrides the default `bottom-start` (submenus pass
360
+ * `right-start` / `left-start`).
278
361
  */
279
- open(trigger: HTMLElement, focus?: boolean | 'first' | 'last'): void;
362
+ open(trigger: HTMLElement, focus?: boolean | 'first' | 'last', placement?: MkPlacement): void;
280
363
  /**
281
364
  * Open the menu at viewport coordinates — e.g. a right-click point from a
282
365
  * `contextmenu` event. Positioning (and flip/shift back on-screen) is handled
@@ -284,8 +367,19 @@ declare class MkMenu implements OnDestroy {
284
367
  * item; `restoreFocusEl` (when provided) regains focus on close.
285
368
  */
286
369
  openAt(x: number, y: number, restoreFocusEl?: HTMLElement): void;
287
- /** Close the menu; optionally restore focus to the trigger. */
370
+ /**
371
+ * Close this menu (and any submenu open under it); optionally restore focus
372
+ * to the trigger — for a submenu that is the item it hangs off.
373
+ */
288
374
  close(restoreFocus?: boolean): void;
375
+ /**
376
+ * Close the whole menu chain from the root down — what activating a leaf
377
+ * item does, wherever in the tree it sits. Focus returns to the root
378
+ * trigger when `restoreFocus` is set.
379
+ */
380
+ closeAll(restoreFocus?: boolean): void;
381
+ /** Close every submenu open under this menu, except `keep`. */
382
+ closeChildren(keep?: MkMenu): void;
289
383
  /** Toggle open/closed from a trigger. */
290
384
  toggle(trigger: HTMLElement, focusFirst?: boolean): void;
291
385
  /**
@@ -296,9 +390,35 @@ declare class MkMenu implements OnDestroy {
296
390
  focusFirstItem(): void;
297
391
  /** Move focus to the last enabled item (ArrowUp from the trigger). */
298
392
  focusLastItem(): void;
393
+ /**
394
+ * Whether `node` lives inside this menu's panel or any submenu open under
395
+ * it. Submenu panels are separate top-layer elements, so a plain
396
+ * `contains` on the parent panel would treat clicks in them as outside.
397
+ */
398
+ containsTarget(node: Node | null): boolean;
399
+ /** Bound for the anchored panel's `keepOpenWhen` (stable identity). */
400
+ protected readonly keepOpenWhen: (target: Node) => boolean;
401
+ /**
402
+ * An item was hovered: close sibling submenus so only the hovered branch
403
+ * stays open (the item opens its own submenu after a delay).
404
+ */
405
+ itemHovered(item: MkMenuItem): void;
406
+ /** @internal */
407
+ childOpened(child: MkMenu): void;
408
+ /** @internal */
409
+ childClosed(child: MkMenu): void;
410
+ private root;
299
411
  /** Focus an item once the panel is in the top layer and painted. */
300
412
  private focusAfterOpen;
413
+ /**
414
+ * Whether the menu is laid out right-to-left (submenu side + arrow keys).
415
+ * Read from the trigger's nearest `dir` attribute — the panel itself lives
416
+ * in the top layer, outside any `dir` container — then from computed style.
417
+ */
418
+ private isRtl;
301
419
  protected onPanelKeydown(event: KeyboardEvent): void;
420
+ /** Placement for a submenu hanging off `item`, honouring text direction. */
421
+ submenuPlacement(): MkPlacement;
302
422
  private enabled;
303
423
  private activeItem;
304
424
  private currentIndex;
@@ -311,42 +431,6 @@ declare class MkMenu implements OnDestroy {
311
431
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMenu, "mk-menu", ["mkMenu"], {}, {}, ["items"], ["*"], true, never>;
312
432
  }
313
433
 
314
- /**
315
- * An item within an `<mk-menu>`. Renders as an ARIA `menuitem` with an optional
316
- * icon slot (`[mkMenuItemIcon]`), disabled and danger states, and either emits
317
- * `action` or navigates when `href` is set. Activating closes the menu.
318
- *
319
- * ```html
320
- * <mk-menu-item (action)="rename()">
321
- * <svg mkMenuItemIcon>…</svg> Rename
322
- * </mk-menu-item>
323
- * <mk-menu-item danger (action)="remove()">Delete</mk-menu-item>
324
- * <mk-menu-item href="/help">Help</mk-menu-item>
325
- * ```
326
- */
327
- declare class MkMenuItem {
328
- private readonly el;
329
- private readonly document;
330
- private readonly menu;
331
- /** Prevent selection and focus. */
332
- readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
333
- /** Destructive styling (e.g. Delete). */
334
- readonly danger: _angular_core.InputSignalWithTransform<boolean, unknown>;
335
- /** When set, activating the item navigates here. */
336
- readonly href: _angular_core.InputSignal<string | undefined>;
337
- /** Emitted when the item is activated (not for disabled items). */
338
- readonly action: _angular_core.OutputEmitterRef<void>;
339
- activate(event?: Event): void;
340
- /** Move DOM focus to this item (roving focus). */
341
- focusEl(): void;
342
- /** Whether `node` lives inside this item. */
343
- contains(node: Node | null): boolean;
344
- /** Lowercased text content, used for typeahead matching. */
345
- text(): string;
346
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkMenuItem, never>;
347
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMenuItem, "mk-menu-item", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "danger": { "alias": "danger"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; }, { "action": "action"; }, never, ["[mkMenuItemIcon]", "*"], true, never>;
348
- }
349
-
350
434
  /**
351
435
  * Turns its host button into a trigger for an `<mk-menu>`. Wires
352
436
  * `aria-haspopup="menu"` / `aria-expanded` / `aria-controls`, toggles on click,
@@ -371,6 +455,54 @@ declare class MkMenuTrigger {
371
455
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MkMenuTrigger, "[mkMenuTriggerFor]", ["mkMenuTrigger"], { "menu": { "alias": "mkMenuTriggerFor"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
372
456
  }
373
457
 
458
+ /**
459
+ * SplitButton — a primary action with an attached menu of alternatives.
460
+ * The main segment emits `action`; the chevron segment is a menu button
461
+ * (`mkMenuTriggerFor`) for the `mk-menu` passed in `[menu]`, with the full
462
+ * keyboard model of the menu trigger (ArrowDown / Enter / Space open and focus
463
+ * the first item, ArrowUp the last, Escape closes).
464
+ *
465
+ * Both segments share `variant`, `tone` and `size`; `disabled` disables both,
466
+ * `loading` shows the main segment's spinner and disables the chevron.
467
+ *
468
+ * ```html
469
+ * <mk-split-button [menu]="saveMenu" tone="primary" (action)="save()">
470
+ * Save
471
+ * </mk-split-button>
472
+ * <mk-menu #saveMenu>
473
+ * <mk-menu-item (action)="saveAs()">Save as…</mk-menu-item>
474
+ * <mk-menu-item (action)="saveTemplate()">Save as template</mk-menu-item>
475
+ * </mk-menu>
476
+ * ```
477
+ */
478
+ declare class MkSplitButton {
479
+ protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
480
+ /** The menu the chevron segment opens. */
481
+ readonly menu: _angular_core.InputSignal<MkMenu>;
482
+ /** Visual treatment shared by both segments. */
483
+ readonly variant: _angular_core.InputSignal<MkVariant>;
484
+ /** Semantic color tone shared by both segments. */
485
+ readonly tone: _angular_core.InputSignal<MkTone>;
486
+ /** Control size shared by both segments. */
487
+ readonly size: _angular_core.InputSignal<MkSize>;
488
+ /** Disable both segments. */
489
+ readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
490
+ /** Spinner on the main segment; the menu segment is disabled meanwhile. */
491
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
492
+ /** Stretch to the container width (the main segment grows). */
493
+ readonly fullWidth: _angular_core.InputSignalWithTransform<boolean, unknown>;
494
+ /** `type` of the main segment — `submit` to submit the enclosing form. */
495
+ readonly type: _angular_core.InputSignal<"button" | "submit">;
496
+ /** Accessible name of the chevron segment. */
497
+ readonly menuLabel: _angular_core.InputSignal<string>;
498
+ /** Emitted when the main segment is activated (not while disabled/loading). */
499
+ readonly action: _angular_core.OutputEmitterRef<void>;
500
+ protected readonly menuDisabled: _angular_core.Signal<boolean>;
501
+ protected onMain(): void;
502
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkSplitButton, never>;
503
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkSplitButton, "mk-split-button", never, { "menu": { "alias": "menu"; "required": true; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "fullWidth": { "alias": "fullWidth"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "menuLabel": { "alias": "menuLabel"; "required": false; "isSignal": true; }; }, { "action": "action"; }, never, ["*"], true, never>;
504
+ }
505
+
374
506
  /**
375
507
  * Admin dashboard layout with a fixed header, a collapsible/responsive sidebar
376
508
  * and a main content area. On small screens the sidebar becomes a focus-trapped
@@ -1165,5 +1297,5 @@ declare class MkScrollArea {
1165
1297
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkScrollArea, "mk-scroll-area", never, { "maxHeight": { "alias": "maxHeight"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "hideDelay": { "alias": "hideDelay"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1166
1298
  }
1167
1299
 
1168
- 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 };
1300
+ export { MkAccordion, MkAccordionHeader, MkAccordionItem, MkAppShell, MkBackToTop, MkBreadcrumb, MkBreadcrumbItem, MkCommandPalette, MkDrawer, MkFab, MkFabAction, MkMenu, MkMenuItem, MkMenuTrigger, MkNavGroup, MkNavItem, MkNavList, MkPageHeader, MkPagination, MkScrollArea, MkSplitButton, MkSplitter, MkStep, MkStepper, MkTab, MkTabs, MkToolbar, MkTree };
1169
1301
  export type { MkCommand, MkDrawerSide, MkFabPosition, MkPageItem, MkScrollAreaOrientation, MkSplitterOrientation, MkStepperOrientation, MkTabsVariant, MkTreeNode };