@mk-kit/ui 0.41.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.
@@ -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. */
@@ -1681,10 +1681,12 @@ declare class MkRepeaterEmpty {
1681
1681
  *
1682
1682
  * With `reorderable`, each row gets a drag handle wired through the dnd
1683
1683
  * module's touch-safe handle-based configuration (a swipe on the row body
1684
- * still scrolls the page). Reordering also works by keyboard: focus the
1685
- * handle (or the row) and press Space/Enter to pick up, arrows to move,
1686
- * Space/Enter to drop, Escape to cancel. Each completed reorder is announced
1687
- * 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}.
1688
1690
  *
1689
1691
  * ```html
1690
1692
  * <mk-repeater [(items)]="rows" [min]="1" [max]="10" reorderable
@@ -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 };