@mk-kit/ui 0.40.0 → 0.42.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.
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { Signal, TemplateRef } from '@angular/core';
2
+ import { Signal, TemplateRef, PipeTransform } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * Emits when a pointer press lands outside the host element — the building block
@@ -803,5 +803,236 @@ declare class MkCanDisable {
803
803
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MkCanDisable, "[mkCanDisable]", never, { "permission": { "alias": "mkCanDisable"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
804
804
  }
805
805
 
806
- export { MK_FIELD_PRESETS, MkAutofocus, MkAutosize, MkCan, MkCanDisable, MkCannot, MkClickOutside, MkCopyToClipboard, MkField, MkHistoryService, MkHistoryStack, MkHotkey, MkHotkeysService, MkInfiniteScroll, MkIntersect, MkMask, MkPermissionPolicy, MkRipple, MkScrollspy, mkApplyMask, mkMaskCaret, mkMatchesHotkey, mkParseHotkey, mkPermissionGranted, registerHistoryHotkeys };
807
- export type { MkFieldKind, MkFieldPreset, MkHistoryEntry, MkHotkeyOptions, MkMaskResult, MkParsedHotkey };
806
+ /**
807
+ * Options accepted by {@link MkCurrencyPipe}. They map onto
808
+ * `Intl.NumberFormat` currency options; `locale` overrides the i18n /
809
+ * runtime locale for this call only.
810
+ */
811
+ interface MkCurrencyOptions {
812
+ /** BCP 47 locale for this call; defaults to `provideMkI18n({ locale })`, then the runtime locale. */
813
+ locale?: string;
814
+ /** How the currency is shown. Default `'symbol'` (`€`, `$`, `zł`). */
815
+ display?: 'symbol' | 'narrowSymbol' | 'code' | 'name';
816
+ /** Fraction digits; both default to the currency's own (2 for EUR, 0 for JPY). */
817
+ minimumFractionDigits?: number;
818
+ maximumFractionDigits?: number;
819
+ /** Sign display, e.g. `'always'` for `+€10.00` or `'exceptZero'`. */
820
+ signDisplay?: 'auto' | 'never' | 'always' | 'exceptZero';
821
+ /** Compact notation for dashboards (`$1.2M`). Default `'standard'`. */
822
+ notation?: 'standard' | 'compact';
823
+ /** Thousands grouping; default `true`. */
824
+ useGrouping?: boolean;
825
+ }
826
+ /**
827
+ * `mkCurrency` — format a number as money with `Intl.NumberFormat`, honouring
828
+ * the locale and default currency from `provideMkI18n({ locale, currency })`.
829
+ * No Angular locale data is needed. Pure: `null`, `undefined`, `''` and
830
+ * non-numeric input render as `''`.
831
+ *
832
+ * ```html
833
+ * {{ 1234.5 | mkCurrency }} <!-- $1,234.50 (i18n currency, default USD) -->
834
+ * {{ 1234.5 | mkCurrency:'EUR' }} <!-- €1,234.50 -->
835
+ * {{ 1234.5 | mkCurrency:'PLN':{ locale: 'pl-PL' } }} <!-- 1234,50 zł -->
836
+ * {{ 1234567 | mkCurrency:'USD':{ notation: 'compact' } }} <!-- $1.2M -->
837
+ * {{ total() | mkCurrency:'GBP':{ signDisplay: 'always' } }} <!-- signals work as-is -->
838
+ * ```
839
+ */
840
+ declare class MkCurrencyPipe implements PipeTransform {
841
+ private readonly i18n;
842
+ /**
843
+ * @param value Amount in major units (`12.5` → `$12.50`).
844
+ * @param currency ISO 4217 code; defaults to `provideMkI18n({ currency })`, then `'USD'`.
845
+ * @param options Locale and `Intl.NumberFormat` currency options.
846
+ */
847
+ transform(value: number | string | null | undefined, currency?: string | null, options?: MkCurrencyOptions): string;
848
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkCurrencyPipe, never>;
849
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkCurrencyPipe, "mkCurrency", true>;
850
+ }
851
+
852
+ /** Options accepted by {@link MkRelativeTimePipe}. */
853
+ interface MkRelativeTimeOptions {
854
+ /** BCP 47 locale for this call; defaults to `provideMkI18n({ locale })`, then the runtime locale. */
855
+ locale?: string;
856
+ /**
857
+ * `'auto'` (default) uses phrases like "yesterday" / "now" where the locale
858
+ * has them; `'always'` sticks to "1 day ago" / "in 0 seconds".
859
+ */
860
+ numeric?: 'always' | 'auto';
861
+ /** Length of the unit word: `'long'` (default, "minutes"), `'short'` ("min."), `'narrow'` ("m"). */
862
+ style?: 'long' | 'short' | 'narrow';
863
+ /**
864
+ * Largest unit to use. Default `'year'`; e.g. `'day'` keeps "45 days ago"
865
+ * instead of "1 month ago".
866
+ */
867
+ maxUnit?: Intl.RelativeTimeFormatUnit;
868
+ }
869
+ /**
870
+ * `mkRelativeTime` — "3 minutes ago" / "in 2 days" / "yesterday" from a
871
+ * `Date`, timestamp or ISO string, via `Intl.RelativeTimeFormat` in the
872
+ * `provideMkI18n` locale (no Angular locale data). Pure: `null`, `undefined`
873
+ * and unparsable input render as `''`.
874
+ *
875
+ * The pipe picks the largest unit whose magnitude is at least 1 (seconds →
876
+ * minutes → hours → days → weeks → months → years) and rounds. Pass `now`
877
+ * for deterministic output in tests, or bind a ticking signal to keep a
878
+ * list live — a pure pipe only re-runs when an argument changes:
879
+ *
880
+ * ```html
881
+ * {{ comment.createdAt | mkRelativeTime }} <!-- 3 minutes ago -->
882
+ * {{ due | mkRelativeTime:now() }} <!-- in 2 days (now() ticks) -->
883
+ * {{ due | mkRelativeTime:null:{ style: 'short' } }} <!-- in 2 days -->
884
+ * {{ ts | mkRelativeTime:null:{ locale: 'pl', numeric: 'always' } }} <!-- 3 minuty temu -->
885
+ * ```
886
+ */
887
+ declare class MkRelativeTimePipe implements PipeTransform {
888
+ private readonly i18n;
889
+ /**
890
+ * @param value The instant to describe.
891
+ * @param now Reference instant; defaults to `Date.now()` at call time.
892
+ * @param options Locale, numeric mode, style and unit cap.
893
+ */
894
+ transform(value: Date | number | string | null | undefined, now?: Date | number | string | null, options?: MkRelativeTimeOptions): string;
895
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkRelativeTimePipe, never>;
896
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkRelativeTimePipe, "mkRelativeTime", true>;
897
+ }
898
+
899
+ /** Options accepted by {@link MkFileSizePipe}. */
900
+ interface MkFileSizeOptions {
901
+ /** BCP 47 locale for the number part (decimal separator); defaults to the i18n / runtime locale. */
902
+ locale?: string;
903
+ /**
904
+ * `'decimal'` (default) divides by 1000 and labels `kB MB GB …` (what
905
+ * operating systems and file dialogs show); `'binary'` divides by 1024
906
+ * and labels `KiB MiB GiB …`.
907
+ */
908
+ base?: 'decimal' | 'binary';
909
+ /** Maximum fraction digits. Default `1`; bytes never get fractions. */
910
+ digits?: number;
911
+ }
912
+ /**
913
+ * `mkFileSize` — bytes → a human-readable size such as `1.2 MB`, with the
914
+ * number formatted by `Intl.NumberFormat` in the `provideMkI18n` locale.
915
+ * Pure: `null`, `undefined`, `''` and non-numeric input render as `''`.
916
+ *
917
+ * ```html
918
+ * {{ 1_234_567 | mkFileSize }} <!-- 1.2 MB -->
919
+ * {{ 1_234_567 | mkFileSize:{ base: 'binary' } }} <!-- 1.2 MiB -->
920
+ * {{ 1_234_567 | mkFileSize:{ digits: 2, locale: 'de' } }} <!-- 1,23 MB -->
921
+ * {{ 512 | mkFileSize }} <!-- 512 B -->
922
+ * ```
923
+ */
924
+ declare class MkFileSizePipe implements PipeTransform {
925
+ private readonly i18n;
926
+ transform(value: number | string | null | undefined, options?: MkFileSizeOptions): string;
927
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkFileSizePipe, never>;
928
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkFileSizePipe, "mkFileSize", true>;
929
+ }
930
+
931
+ /**
932
+ * `mkInitials` — "Ada Lovelace" → "AL", the same rule `mk-avatar` uses for
933
+ * its fallback: first letter of the first and last word, upper-cased. A
934
+ * single word yields its first `max` letters ("Ada" → "AD"); more than two
935
+ * words with `max` > 2 take one letter per word from the start ("Jean
936
+ * Luc Picard" with `max: 3` → "JLP"). Leading/trailing/duplicate whitespace
937
+ * is ignored, and grapheme-aware slicing keeps emoji and accents intact.
938
+ * Pure: `null`, `undefined` and blank input render as `''`.
939
+ *
940
+ * ```html
941
+ * {{ user.name | mkInitials }} <!-- Grace Hopper → GH -->
942
+ * {{ user.name | mkInitials:1 }} <!-- Grace Hopper → G -->
943
+ * {{ 'Jean Luc Picard' | mkInitials:3 }} <!-- JLP -->
944
+ * ```
945
+ */
946
+ declare class MkInitialsPipe implements PipeTransform {
947
+ /**
948
+ * @param value Full name.
949
+ * @param max Maximum number of letters; default `2`, minimum `1`.
950
+ */
951
+ transform(value: string | null | undefined, max?: number): string;
952
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkInitialsPipe, never>;
953
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkInitialsPipe, "mkInitials", true>;
954
+ }
955
+
956
+ /** Options accepted by {@link MkTruncatePipe}. */
957
+ interface MkTruncateOptions {
958
+ /** Suffix appended when text is cut. Default `'…'` (a single ellipsis character). */
959
+ ellipsis?: string;
960
+ /**
961
+ * Cut at the last whitespace before the limit instead of mid-word
962
+ * (falls back to a hard cut when the first word alone is longer).
963
+ * Default `false`.
964
+ */
965
+ wordBoundary?: boolean;
966
+ }
967
+ /**
968
+ * `mkTruncate` — shorten text to `length` characters and append an ellipsis.
969
+ * The ellipsis counts toward the limit, so the output never exceeds
970
+ * `length` characters; counting is grapheme-aware (emoji and accented
971
+ * letters are one character). Text that already fits is returned as-is.
972
+ * Pure: `null` and `undefined` render as `''`.
973
+ *
974
+ * ```html
975
+ * {{ post.body | mkTruncate:80 }} <!-- hard cut at 80 -->
976
+ * {{ post.body | mkTruncate:80:{ wordBoundary: true } }} <!-- cut at the last space -->
977
+ * {{ hash | mkTruncate:12:{ ellipsis: '...' } }}
978
+ * ```
979
+ */
980
+ declare class MkTruncatePipe implements PipeTransform {
981
+ /**
982
+ * @param value Text to shorten.
983
+ * @param length Maximum characters in the output, ellipsis included. Default `50`.
984
+ * @param options Ellipsis string and word-boundary mode.
985
+ */
986
+ transform(value: string | null | undefined, length?: number, options?: MkTruncateOptions): string;
987
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkTruncatePipe, never>;
988
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkTruncatePipe, "mkTruncate", true>;
989
+ }
990
+
991
+ /**
992
+ * Per-category word forms for {@link MkPluralizePipe}, keyed by the CLDR
993
+ * plural categories `Intl.PluralRules` returns. `other` is required; the
994
+ * pipe falls back to it for any category you leave out.
995
+ *
996
+ * ```ts
997
+ * const files: MkPluralForms = { one: 'plik', few: 'pliki', many: 'plików', other: 'pliku' };
998
+ * ```
999
+ */
1000
+ type MkPluralForms = {
1001
+ other: string;
1002
+ } & Partial<Record<Intl.LDMLPluralRule, string>>;
1003
+ /** Options accepted by {@link MkPluralizePipe}. */
1004
+ interface MkPluralizeOptions {
1005
+ /** BCP 47 locale for the plural rules and number; defaults to the i18n / runtime locale. */
1006
+ locale?: string;
1007
+ /** Prefix the formatted count (`"3 items"`); `false` gives just the word. Default `true`. */
1008
+ withCount?: boolean;
1009
+ }
1010
+ /**
1011
+ * `mkPluralize` — pick the right word for a count using the locale's plural
1012
+ * rules (`Intl.PluralRules`), optionally prefixed with the formatted count.
1013
+ * The English shorthand takes a singular and an optional plural (default
1014
+ * `singular + 's'`); other languages pass a {@link MkPluralForms} map.
1015
+ * Pure: `null`, `undefined`, `''` and non-numeric counts render as `''`.
1016
+ *
1017
+ * ```html
1018
+ * {{ count | mkPluralize:'item' }} <!-- 1 item / 3 items -->
1019
+ * {{ count | mkPluralize:'entry':'entries' }} <!-- 1 entry / 2 entries -->
1020
+ * {{ count | mkPluralize:'file':null:{ withCount: false } }} <!-- files -->
1021
+ * {{ n | mkPluralize:{ one: 'plik', few: 'pliki', many: 'plików', other: 'pliku' }:null:{ locale: 'pl' } }}
1022
+ * ```
1023
+ */
1024
+ declare class MkPluralizePipe implements PipeTransform {
1025
+ private readonly i18n;
1026
+ /**
1027
+ * @param value The count.
1028
+ * @param singular Singular word, or a full {@link MkPluralForms} map.
1029
+ * @param plural Plural word for the shorthand form; default `singular + 's'`. Ignored with a map.
1030
+ * @param options Locale and whether to prefix the count.
1031
+ */
1032
+ transform(value: number | string | null | undefined, singular: string | MkPluralForms, plural?: string | null, options?: MkPluralizeOptions): string;
1033
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkPluralizePipe, never>;
1034
+ static ɵpipe: _angular_core.ɵɵPipeDeclaration<MkPluralizePipe, "mkPluralize", true>;
1035
+ }
1036
+
1037
+ export { MK_FIELD_PRESETS, MkAutofocus, MkAutosize, MkCan, MkCanDisable, MkCannot, MkClickOutside, MkCopyToClipboard, MkCurrencyPipe, MkField, MkFileSizePipe, MkHistoryService, MkHistoryStack, MkHotkey, MkHotkeysService, MkInfiniteScroll, MkInitialsPipe, MkIntersect, MkMask, MkPermissionPolicy, MkPluralizePipe, MkRelativeTimePipe, MkRipple, MkScrollspy, MkTruncatePipe, mkApplyMask, mkMaskCaret, mkMatchesHotkey, mkParseHotkey, mkPermissionGranted, registerHistoryHotkeys };
1038
+ export type { MkCurrencyOptions, MkFieldKind, MkFieldPreset, MkFileSizeOptions, MkHistoryEntry, MkHotkeyOptions, MkMaskResult, MkParsedHotkey, MkPluralForms, MkPluralizeOptions, MkRelativeTimeOptions, MkTruncateOptions };
@@ -11,29 +11,64 @@ import { TemplateRef } from '@angular/core';
11
11
  * Its look (grab cursor, muted colour, `touch-action: none`) ships as the
12
12
  * global `.mk-drag-handle` class in the theme stylesheet.
13
13
  *
14
+ * **Decorative grip** — a non-focusable element (`<span>`, `<mk-icon>`): the
15
+ * item itself stays the keyboard target (`role="button"`, focusable), so the
16
+ * grip should be `aria-hidden`:
17
+ *
14
18
  * ```html
15
19
  * <div mkDrag [mkDragData]="row">
16
20
  * <span mkDragHandle aria-hidden="true">⠿</span>
17
21
  * {{ row.name }}
18
22
  * </div>
19
23
  * ```
24
+ *
25
+ * **Focusable grip** — a `<button>` (or any element with `tabindex`): the
26
+ * handle becomes the keyboard target instead. The item is then a plain
27
+ * container (no role, not focusable), so rows may hold inputs, links and
28
+ * other buttons without nesting interactive controls, and `<li>` items keep
29
+ * valid list semantics. Give it an accessible name:
30
+ *
31
+ * ```html
32
+ * <li mkDrag [mkDragData]="row">
33
+ * <button type="button" mkDragHandle [attr.aria-label]="'Reorder ' + row.name">⠿</button>
34
+ * <input mkInput [(ngModel)]="row.name" />
35
+ * </li>
36
+ * ```
20
37
  */
21
38
  declare class MkDragHandle {
22
39
  /** The handle's host element. */
23
40
  readonly element: HTMLElement;
41
+ /**
42
+ * Whether the handle can take keyboard focus itself — a native control
43
+ * (`<button>`, …), a link with `href`, or any element with a `tabindex`.
44
+ * A focusable handle carries the keyboard drag for its `[mkDrag]`.
45
+ */
46
+ isFocusable(): boolean;
24
47
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDragHandle, never>;
25
48
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MkDragHandle, "[mkDragHandle]", ["mkDragHandle"], {}, {}, never, never, true, never>;
26
49
  }
27
50
 
28
51
  /**
29
52
  * Makes an item inside a `[mkDropList]` draggable — by pointer (mouse / touch /
30
- * pen) **and** by keyboard (WCAG 2.1.1). The item is focusable, exposes
31
- * `role="button"` + `aria-roledescription="Draggable item"`, and every move is
32
- * announced via {@link MkLiveAnnouncer}.
53
+ * pen) **and** by keyboard (WCAG 2.1.1). Every move is announced via
54
+ * {@link MkLiveAnnouncer}. Which element carries the keyboard interaction
55
+ * depends on the handle:
56
+ *
57
+ * - **No handle, or a decorative one** (`<span mkDragHandle aria-hidden>`):
58
+ * the item itself is focusable and exposes `aria-roledescription="Draggable
59
+ * item"` with `role="button"` — or `role="option"` when it is an `<li>` of a
60
+ * `<ul mkDropList>`, which then becomes a labelled `listbox` (an `<li>` may
61
+ * not take the `button` role).
62
+ * - **A focusable handle** (`<button mkDragHandle aria-label="…">`, or any
63
+ * handle with `tabindex`): the handle is the keyboard target and receives
64
+ * the `aria-roledescription` / `aria-pressed` / `aria-grabbed` state; the
65
+ * item stays a plain container with no role and no `tabindex`, so it can hold
66
+ * inputs, links and buttons of its own (no nested interactive controls) and
67
+ * `<li>` items keep their list semantics.
33
68
  *
34
- * Keyboard: focus an item and press **Space/Enter** to pick it up, **Arrow**
35
- * keys to move it (crossing into connected lists at the ends / across the
36
- * perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
69
+ * Keyboard: focus the item (or its handle) and press **Space/Enter** to pick
70
+ * it up, **Arrow** keys to move it (crossing into connected lists at the ends /
71
+ * across the perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
37
72
  *
38
73
  * Touch: a swipe scrolls the page as usual — the drag only arms after a
39
74
  * long-press ({@link mkDragTouchDelay}, default 300 ms). While armed the item
@@ -46,9 +81,9 @@ declare class MkDragHandle {
46
81
  * synchronously on release so drops land exactly where the pointer ended.
47
82
  *
48
83
  * ```html
49
- * <li mkDrag [mkDragData]="row" [mkDragDisabled]="row.locked">
84
+ * <div mkDrag [mkDragData]="row" [mkDragDisabled]="row.locked">
50
85
  * <span mkDragHandle aria-hidden="true">⠿</span> {{ row.title }}
51
- * </li>
86
+ * </div>
52
87
  * ```
53
88
  *
54
89
  * @typeParam T item data type.
@@ -82,6 +117,20 @@ declare class MkDrag<T = unknown> {
82
117
  * inner handle would start the outer drag and inner dnd would never work.
83
118
  */
84
119
  protected readonly ownHandles: _angular_core.Signal<MkDragHandle[]>;
120
+ /**
121
+ * The handle that carries the keyboard drag — the first of this item's
122
+ * handles that is focusable on its own (a `<button mkDragHandle>`, say).
123
+ * `null` when the item itself is the keyboard target.
124
+ */
125
+ readonly keyboardHandle: _angular_core.Signal<MkDragHandle | null>;
126
+ /**
127
+ * The role the item itself exposes: `null` when a focusable handle carries
128
+ * the interaction; `option` inside a list that resolved to a `listbox`
129
+ * (`<ul mkDropList>` / `<li mkDrag>`); `button` otherwise.
130
+ */
131
+ protected readonly itemRole: _angular_core.Signal<"button" | "option" | null>;
132
+ /** The element keyboard events act on: the focusable handle, else the item. */
133
+ private keyboardTarget;
85
134
  /** True while a pointer drag is in progress. */
86
135
  protected readonly dragging: _angular_core.WritableSignal<boolean>;
87
136
  /** True while the item is "picked up" for keyboard movement. */
@@ -105,6 +154,8 @@ declare class MkDrag<T = unknown> {
105
154
  private originLeft;
106
155
  private originTop;
107
156
  private preview;
157
+ constructor();
158
+ private toggleAttr;
108
159
  private readonly moveHandler;
109
160
  private readonly upHandler;
110
161
  private readonly cancelHandler;
@@ -168,7 +219,7 @@ declare class MkDrag<T = unknown> {
168
219
  private finishPointer;
169
220
  private commitPointer;
170
221
  protected onKeyDown(event: Event): void;
171
- protected onBlur(): void;
222
+ protected onFocusOut(event: Event): void;
172
223
  private pickUp;
173
224
  private stepPrimary;
174
225
  private stepList;
@@ -228,14 +279,29 @@ declare class MkDrag<T = unknown> {
228
279
  * The array bound to `mkDropListData` is **not** mutated for you — handle
229
280
  * `mkDropListDropped` and call {@link mkMoveItemInArray} / {@link mkTransferArrayItem}.
230
281
  *
282
+ * Semantics follow the host element and its items, so the tree is always
283
+ * valid ARIA:
284
+ *
285
+ * - any host other than `<ul>`/`<ol>` is a `role="group"` (named by
286
+ * `mkDropListLabel`) of `role="button"` items;
287
+ * - a `<ul>`/`<ol>` whose `<li mkDrag>` items all carry a *focusable*
288
+ * `[mkDragHandle]` stays a plain list — the handles are the controls;
289
+ * - a `<ul>`/`<ol>` whose items are themselves the keyboard targets becomes a
290
+ * `listbox` of `option`s (an `<li>` may not be a `button`); give it a
291
+ * `mkDropListLabel`, listboxes need a name.
292
+ *
293
+ * A `role` you set in the template is kept, and `aria-orientation` is only
294
+ * exposed on roles that allow it (`listbox`, `toolbar`, `tree`, …) — the
295
+ * keyboard model handles both axes regardless.
296
+ *
231
297
  * ```html
232
- * <ul mkDropList [mkDropListData]="todo()"
233
- * mkDropListId="todo" [mkDropListConnectedTo]="['done']"
234
- * (mkDropListDropped)="drop($event)">
298
+ * <div mkDropList [mkDropListData]="todo()" mkDropListLabel="To do"
299
+ * mkDropListId="todo" [mkDropListConnectedTo]="['done']"
300
+ * (mkDropListDropped)="drop($event)">
235
301
  * @for (t of todo(); track t.id) {
236
- * <li mkDrag [mkDragData]="t">{{ t.title }}</li>
302
+ * <div mkDrag [mkDragData]="t">{{ t.title }}</div>
237
303
  * }
238
- * </ul>
304
+ * </div>
239
305
  * ```
240
306
  *
241
307
  * @typeParam T item data type.
@@ -268,6 +334,22 @@ declare class MkDropList<T = unknown> {
268
334
  private readonly autoId;
269
335
  /** Announceable name: the label when set, otherwise the resolved id. */
270
336
  readonly label: _angular_core.Signal<string>;
337
+ /** A `role` written in the template — always kept. */
338
+ private readonly explicitRole;
339
+ private readonly isNativeList;
340
+ /**
341
+ * The role the host exposes. One set in the template wins. A `<ul>`/`<ol>`
342
+ * keeps its implicit `list` role (`null` — nothing is written) while every
343
+ * item hands the keyboard drag to a focusable handle, and becomes a
344
+ * `listbox` (its items `option`s) otherwise. Any other element is a `group`.
345
+ */
346
+ readonly role: _angular_core.Signal<string | null>;
347
+ /** Whether `aria-orientation` is valid on the effective role. */
348
+ protected readonly orientationAllowed: _angular_core.Signal<boolean>;
349
+ /** A static `aria-label` written in the template, kept when no label input is set. */
350
+ private readonly staticAriaLabel;
351
+ /** Accessible name of the list: `mkDropListLabel`, else the template's own. */
352
+ protected readonly ariaLabel: _angular_core.Signal<string | null>;
271
353
  /** Connected-list ids, normalised to a plain array. */
272
354
  readonly connectedTo: _angular_core.Signal<readonly string[]>;
273
355
  /** The `mkDrag` items projected into this list, in DOM order. */
@@ -569,6 +569,8 @@ declare class MkSelect implements ControlValueAccessor, OnDestroy {
569
569
  protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
570
570
  private readonly triggerRef;
571
571
  /** The list of options to choose from. */
572
+ /** Accessible name when the control is used without an `mk-form-field` label. */
573
+ readonly ariaLabel: _angular_core.InputSignal<string>;
572
574
  readonly options: _angular_core.InputSignal<readonly MkSelectOption[]>;
573
575
  /** Placeholder shown when nothing is selected. */
574
576
  readonly placeholder: _angular_core.InputSignal<string>;
@@ -615,7 +617,7 @@ declare class MkSelect implements ControlValueAccessor, OnDestroy {
615
617
  registerOnTouched(fn: () => void): void;
616
618
  setDisabledState(isDisabled: boolean): void;
617
619
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkSelect, never>;
618
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkSelect, "mk-select", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
620
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkSelect, "mk-select", never, { "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
619
621
  }
620
622
 
621
623
  /** One row of an `mk-listbox`. */
@@ -912,6 +914,8 @@ declare class MkAutocomplete implements ControlValueAccessor {
912
914
  private lastAnnouncedCount;
913
915
  constructor();
914
916
  /** The list of suggestions. Update it from `search` for async sources. */
917
+ /** Accessible name when the control is used without an `mk-form-field` label. */
918
+ readonly ariaLabel: _angular_core.InputSignal<string>;
915
919
  readonly options: _angular_core.InputSignal<readonly MkAutocompleteOption[]>;
916
920
  /** Placeholder shown when the input is empty. */
917
921
  readonly placeholder: _angular_core.InputSignal<string>;
@@ -980,7 +984,7 @@ declare class MkAutocomplete implements ControlValueAccessor {
980
984
  registerOnTouched(fn: () => void): void;
981
985
  setDisabledState(isDisabled: boolean): void;
982
986
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkAutocomplete, never>;
983
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkAutocomplete, "mk-autocomplete", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "filterMode": { "alias": "filterMode"; "required": false; "isSignal": true; }; "minChars": { "alias": "minChars"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "requireSelection": { "alias": "requireSelection"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; "optionSelected": "optionSelected"; }, never, never, true, never>;
987
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkAutocomplete, "mk-autocomplete", never, { "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "filterMode": { "alias": "filterMode"; "required": false; "isSignal": true; }; "minChars": { "alias": "minChars"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "requireSelection": { "alias": "requireSelection"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; "optionSelected": "optionSelected"; }, never, never, true, never>;
984
988
  }
985
989
 
986
990
  /** How mention suggestions are narrowed against the typed query. */
@@ -1201,6 +1205,8 @@ declare class MkMultiSelect implements ControlValueAccessor, Validator {
1201
1205
  private readonly announcer;
1202
1206
  private readonly inputRef;
1203
1207
  /** The list of options to choose from. Update from `search` for async. */
1208
+ /** Accessible name when the control is used without an `mk-form-field` label. */
1209
+ readonly ariaLabel: _angular_core.InputSignal<string>;
1204
1210
  readonly options: _angular_core.InputSignal<readonly MkMultiSelectOption[]>;
1205
1211
  /** Placeholder shown when no value is selected. */
1206
1212
  readonly placeholder: _angular_core.InputSignal<string>;
@@ -1299,7 +1305,7 @@ declare class MkMultiSelect implements ControlValueAccessor, Validator {
1299
1305
  validate(control: AbstractControl): ValidationErrors | null;
1300
1306
  registerOnValidatorChange(fn: () => void): void;
1301
1307
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkMultiSelect, never>;
1302
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMultiSelect, "mk-multi-select", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "filterMode": { "alias": "filterMode"; "required": false; "isSignal": true; }; "minChars": { "alias": "minChars"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; "optionAdded": "optionAdded"; "optionRemoved": "optionRemoved"; }, never, never, true, never>;
1308
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkMultiSelect, "mk-multi-select", never, { "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "filterMode": { "alias": "filterMode"; "required": false; "isSignal": true; }; "minChars": { "alias": "minChars"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; "optionAdded": "optionAdded"; "optionRemoved": "optionRemoved"; }, never, never, true, never>;
1303
1309
  }
1304
1310
 
1305
1311
  /**
@@ -1675,10 +1681,12 @@ declare class MkRepeaterEmpty {
1675
1681
  *
1676
1682
  * With `reorderable`, each row gets a drag handle wired through the dnd
1677
1683
  * module's touch-safe handle-based configuration (a swipe on the row body
1678
- * still scrolls the page). Reordering also works by keyboard: focus the
1679
- * handle (or the row) and press Space/Enter to pick up, arrows to move,
1680
- * Space/Enter to drop, Escape to cancel. Each completed reorder is announced
1681
- * via {@link MkLiveAnnouncer}.
1684
+ * still scrolls the page). The handle button is the only control the dnd
1685
+ * layer adds rows stay plain list items, so the inputs and buttons inside
1686
+ * them are never nested in an interactive role. Reordering also works by
1687
+ * keyboard: focus the handle and press Space/Enter to pick up, arrows to
1688
+ * move, Space/Enter to drop, Escape to cancel. Each completed reorder is
1689
+ * announced via {@link MkLiveAnnouncer}.
1682
1690
  *
1683
1691
  * ```html
1684
1692
  * <mk-repeater [(items)]="rows" [min]="1" [max]="10" reorderable
@@ -2217,6 +2225,8 @@ declare class MkNumberInput implements ControlValueAccessor, Validator {
2217
2225
  protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
2218
2226
  private readonly inputRef;
2219
2227
  /** Minimum value. */
2228
+ /** Accessible name when the control is used without an `mk-form-field` label. */
2229
+ readonly ariaLabel: _angular_core.InputSignal<string>;
2220
2230
  readonly min: _angular_core.InputSignal<number | null>;
2221
2231
  /** Maximum value. */
2222
2232
  readonly max: _angular_core.InputSignal<number | null>;
@@ -2264,7 +2274,7 @@ declare class MkNumberInput implements ControlValueAccessor, Validator {
2264
2274
  validate(control: AbstractControl): ValidationErrors | null;
2265
2275
  registerOnValidatorChange(fn: () => void): void;
2266
2276
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkNumberInput, never>;
2267
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkNumberInput, "mk-number-input", never, { "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "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; }; }, { "value": "valueChange"; }, never, never, true, never>;
2277
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkNumberInput, "mk-number-input", never, { "ariaLabel": { "alias": "aria-label"; "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; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "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; }; }, { "value": "valueChange"; }, never, never, true, never>;
2268
2278
  }
2269
2279
 
2270
2280
  type MkNumericKeypadMode = 'pin' | 'quantity' | 'amount';
@@ -2860,6 +2870,8 @@ declare class MkCurrencyInput implements ControlValueAccessor, Validator {
2860
2870
  /** Localised strings (override globally via `provideMkI18n`). */
2861
2871
  protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
2862
2872
  /** ISO 4217 currency code (`PLN`, `USD`, …). Empty = plain decimal. */
2873
+ /** Accessible name when the control is used without an `mk-form-field` label. */
2874
+ readonly ariaLabel: _angular_core.InputSignal<string>;
2863
2875
  readonly currency: _angular_core.InputSignal<string>;
2864
2876
  /** BCP 47 locale for separators/symbol; defaults to the runtime locale. */
2865
2877
  readonly locale: _angular_core.InputSignal<string | undefined>;
@@ -2926,7 +2938,7 @@ declare class MkCurrencyInput implements ControlValueAccessor, Validator {
2926
2938
  validate(control: AbstractControl): ValidationErrors | null;
2927
2939
  registerOnValidatorChange(fn: () => void): void;
2928
2940
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkCurrencyInput, never>;
2929
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkCurrencyInput, "mk-currency-input", ["mkCurrencyInput"], { "currency": { "alias": "currency"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "decimals": { "alias": "decimals"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "allowNegative": { "alias": "allowNegative"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
2941
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkCurrencyInput, "mk-currency-input", ["mkCurrencyInput"], { "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "currency": { "alias": "currency"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "decimals": { "alias": "decimals"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "allowNegative": { "alias": "allowNegative"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
2930
2942
  }
2931
2943
 
2932
2944
  /** Card networks {@link MkCardNumberInput} recognises. */
@@ -178,12 +178,29 @@ interface MkGroupToggle {
178
178
  /** Whether the group is now collapsed. */
179
179
  collapsed: boolean;
180
180
  }
181
- /** Options for {@link MkTable.exportCsv}. */
182
- interface MkTableExportOptions extends MkCsvExportOptions {
181
+ /** Options for {@link MkTable.getExportRows} — which rows and columns to export. */
182
+ interface MkTableExportRowsOptions {
183
183
  /** Export only the selected rows (default: every row). */
184
184
  selectedOnly?: boolean;
185
185
  /** Restrict to these column keys, in table order (default: every column). */
186
186
  columns?: readonly string[];
187
+ }
188
+ /** What {@link MkTable.getExportRows} returns: the rows and the columns to write them with. */
189
+ interface MkTableExportRows<T> {
190
+ /**
191
+ * The rows in display order — sorted the way they are shown, tree children
192
+ * flattened under their parent (expanded or not), selection applied when
193
+ * `selectedOnly` was set.
194
+ */
195
+ rows: T[];
196
+ /**
197
+ * The columns in the table's current (user-reordered) order, restricted to
198
+ * the requested keys, each with its header and formatter.
199
+ */
200
+ columns: MkCsvColumn<T>[];
201
+ }
202
+ /** Options for {@link MkTable.exportCsv}. */
203
+ interface MkTableExportOptions extends MkCsvExportOptions, MkTableExportRowsOptions {
187
204
  /** Start the browser download (default `true`); `false` just returns the text. */
188
205
  download?: boolean;
189
206
  }
@@ -498,11 +515,28 @@ declare class MkTable<T = Record<string, unknown>> {
498
515
  expandAllRows(): void;
499
516
  /** Collapse every parent row (tree mode). */
500
517
  collapseAllRows(): void;
518
+ /**
519
+ * The rows and columns an export writes — exactly what {@link exportCsv}
520
+ * serialises, for other formats (XLSX, PDF, the clipboard, …): rows in
521
+ * display order with the current sort applied and tree children
522
+ * (`childrenKey`) flattened under their parent whether or not they are
523
+ * expanded, optionally only the selected ones; columns in the table's
524
+ * current order, restricted to `options.columns` when given, each carrying
525
+ * its header and `format` so what the user saw is what gets written.
526
+ *
527
+ * ```ts
528
+ * const { rows, columns } = table.getExportRows({ selectedOnly: true });
529
+ * const sheet = rows.map((row) =>
530
+ * Object.fromEntries(columns.map((c) => [c.header ?? c.key, c.format ? c.format(row[c.key], row) : row[c.key]])),
531
+ * );
532
+ * ```
533
+ */
534
+ getExportRows(options?: MkTableExportRowsOptions): MkTableExportRows<T>;
501
535
  /**
502
536
  * The table's rows as CSV: current column order, column formatters applied,
503
537
  * sorted the way they are shown, tree children flattened under their parent
504
538
  * whether or not they are expanded. Downloads the file (default name
505
- * `table.csv`) and returns the text.
539
+ * `table.csv`) and returns the text. Built on {@link getExportRows}.
506
540
  */
507
541
  exportCsv(options?: MkTableExportOptions): string;
508
542
  private setTreeExpanded;
@@ -876,4 +910,4 @@ declare class MkTableDataSource<T> {
876
910
  }
877
911
 
878
912
  export { MkSort, MkSortHeader, MkTable, MkTableCell, MkTableDataSource, MkTableRowDetail, mkDownloadText, mkExportCsv, mkToCsv };
879
- export type { MkCellEdit, MkColumnResize, MkCsvColumn, MkCsvExportOptions, MkCsvOptions, MkDataFetcher, MkDataPage, MkDataRequest, MkGroupToggle, MkSortChange, MkSortDirection, MkSortState, MkSortable, MkTableAlign, MkTableCellContext, MkTableColumn, MkTableDataSourceOptions, MkTableDensity, MkTableExportOptions, MkTableGroup, MkTreeToggle };
913
+ export type { MkCellEdit, MkColumnResize, MkCsvColumn, MkCsvExportOptions, MkCsvOptions, MkDataFetcher, MkDataPage, MkDataRequest, MkGroupToggle, MkSortChange, MkSortDirection, MkSortState, MkSortable, MkTableAlign, MkTableCellContext, MkTableColumn, MkTableDataSourceOptions, MkTableDensity, MkTableExportOptions, MkTableExportRows, MkTableExportRowsOptions, MkTableGroup, MkTreeToggle };