@mk-kit/ui 0.54.0 → 0.55.1

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.
@@ -0,0 +1,272 @@
1
+ /* =========================================================================
2
+ * Momentum — a warm, high-energy preset
3
+ * -------------------------------------------------------------------------
4
+ * The look of the Momentum task app: Manrope set heavy and tight, soft
5
+ * violet-grey surfaces, generous radii, feather-light card shadows and an
6
+ * indigo accent that is meant to be swapped at runtime (see below).
7
+ *
8
+ * import '@mk-kit/ui/styles.css';
9
+ * import '@mk-kit/ui/presets/momentum.css';
10
+ * <html data-mk-preset="momentum">
11
+ *
12
+ * Works on <html> for the whole app or on any subtree — these are plain
13
+ * custom properties, they inherit. Light and dark resolve with the same
14
+ * precedence as the base theme (`data-mk-theme`, else the OS). The preset
15
+ * steps aside for `data-mk-contrast="high"`: the high-contrast values are
16
+ * tuned for legibility and the preset never overrides them.
17
+ *
18
+ * The preset sets the font family but does not load it. Load Manrope
19
+ * yourself (Google Fonts, weights 500–800) or the stack falls back to the
20
+ * system sans without complaint.
21
+ *
22
+ * Accent swapping — the app changes its accent at runtime by writing
23
+ * `--mk-primary`, `--mk-primary-hover`, `--mk-primary-active`,
24
+ * `--mk-primary-subtle`, `--mk-primary-subtle-hover`,
25
+ * `--mk-primary-subtle-text`, `--mk-focus-ring`, `--mk-selected-bg` and
26
+ * `--mk-selected-text` on the same element. Everything else stays.
27
+ *
28
+ * Every text/surface pair the base theme holds to WCAG AA is held to AA here
29
+ * too (`contrast-smoke.spec.ts` reads this file as well).
30
+ * ========================================================================= */
31
+ /* Theme-independent: type, geometry, elevation shape, motion. */
32
+ /* Light (default) + the theme-independent tokens. */
33
+ [data-mk-preset=momentum]:not([data-mk-contrast=high]) {
34
+ color-scheme: light;
35
+ --mk-font-sans: 'Manrope', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
36
+ --mk-font-weight-normal: 500;
37
+ --mk-font-weight-medium: 600;
38
+ --mk-font-weight-semibold: 700;
39
+ --mk-font-weight-bold: 800;
40
+ --mk-line-height-tight: 1.18;
41
+ --mk-line-height-snug: 1.28;
42
+ --mk-line-height-normal: 1.5;
43
+ --mk-letter-spacing-tight: -0.02em;
44
+ --mk-letter-spacing-wide: 0.12em;
45
+ --mk-radius-xs: 5px;
46
+ --mk-radius-sm: 8px;
47
+ --mk-radius-md: 11px;
48
+ --mk-radius-lg: 13px;
49
+ --mk-radius-xl: 16px;
50
+ --mk-radius-2xl: 20px;
51
+ --mk-duration-fast: 150ms;
52
+ --mk-duration-normal: 220ms;
53
+ --mk-duration-slow: 360ms;
54
+ --mk-ease-emphasized: cubic-bezier(0.2, 0.8, 0.2, 1);
55
+ --mk-bg: #f4f4f8;
56
+ --mk-surface: #ffffff;
57
+ --mk-surface-2: #fbfbfe;
58
+ --mk-surface-3: #f4f4f9;
59
+ --mk-surface-inverse: #16161f;
60
+ --mk-overlay-scrim: rgba(22, 22, 31, 0.45);
61
+ --mk-text: #16161f;
62
+ --mk-text-muted: #42424f;
63
+ --mk-text-subtle: #6e6e82;
64
+ --mk-text-inverse: #ffffff;
65
+ --mk-text-disabled: #a9a9ba;
66
+ --mk-border: #ececf2;
67
+ --mk-border-strong: #d6d6e0;
68
+ --mk-border-subtle: #f1f1f6;
69
+ --mk-hover-overlay: rgba(22, 22, 31, 0.04);
70
+ --mk-active-overlay: rgba(22, 22, 31, 0.08);
71
+ --mk-selected-bg: #ebeafb;
72
+ --mk-selected-text: #4a3fc9;
73
+ --mk-primary: #5b4fe0;
74
+ --mk-primary-hover: #4f43d1;
75
+ --mk-primary-active: #4338be;
76
+ --mk-primary-contrast: #ffffff;
77
+ --mk-primary-subtle: #ebeafb;
78
+ --mk-primary-subtle-hover: #dedcf8;
79
+ --mk-primary-subtle-text: #4a3fc9;
80
+ --mk-success: #168553;
81
+ --mk-success-hover: #137548;
82
+ --mk-success-contrast: #ffffff;
83
+ --mk-success-subtle: #e4f6ee;
84
+ --mk-success-subtle-text: #136e47;
85
+ --mk-warning: #a8660e;
86
+ --mk-warning-hover: #93590c;
87
+ --mk-warning-contrast: #ffffff;
88
+ --mk-warning-subtle: #fbf1e1;
89
+ --mk-warning-subtle-text: #7f4e0b;
90
+ --mk-danger: #d4433a;
91
+ --mk-danger-hover: #bf3a32;
92
+ --mk-danger-contrast: #ffffff;
93
+ --mk-danger-text: #c43d33;
94
+ --mk-danger-subtle: #fce9e7;
95
+ --mk-danger-subtle-text: #a8322a;
96
+ --mk-info: #2568d6;
97
+ --mk-info-hover: #1f58b8;
98
+ --mk-info-contrast: #ffffff;
99
+ --mk-info-subtle: #e6effd;
100
+ --mk-info-subtle-text: #1b4e9e;
101
+ --mk-neutral-subtle: #f4f4f9;
102
+ --mk-neutral-subtle-hover: #eaeaf1;
103
+ --mk-neutral-subtle-text: #42424f;
104
+ --mk-focus-ring: #5b4fe0;
105
+ --mk-shadow-xs: 0 1px 2px rgba(22, 22, 31, 0.05);
106
+ --mk-shadow-sm: 0 2px 6px rgba(22, 22, 31, 0.05);
107
+ --mk-shadow-md: 0 8px 18px rgba(22, 22, 31, 0.12);
108
+ --mk-shadow-lg: 0 22px 50px rgba(22, 22, 31, 0.22);
109
+ --mk-shadow-xl: 0 32px 70px rgba(22, 22, 31, 0.4);
110
+ --mk-skeleton-base: #ececf2;
111
+ --mk-skeleton-shine: #f7f7fb;
112
+ --mk-scrollbar-thumb: #ececf2;
113
+ --mk-scrollbar-thumb-hover: #d6d6e0;
114
+ --mk-code-bg: #f4f4f9;
115
+ --mk-chart-1: #5b4fe0;
116
+ --mk-chart-2: #1faa6e;
117
+ --mk-chart-3: #ff6b5c;
118
+ --mk-chart-4: #e0982e;
119
+ --mk-chart-5: #a85cd6;
120
+ --mk-chart-6: #3aafa9;
121
+ --mk-chart-7: #d85c8a;
122
+ --mk-chart-8: #6e8bff;
123
+ }
124
+
125
+ /* Auto (OS) dark, unless explicitly overridden to light. */
126
+ @media (prefers-color-scheme: dark) {
127
+ :root:not([data-mk-theme=light]) [data-mk-preset=momentum]:not([data-mk-contrast=high]),
128
+ :root:not([data-mk-theme=light])[data-mk-preset=momentum]:not([data-mk-contrast=high]) {
129
+ color-scheme: dark;
130
+ --mk-bg: #0d0d13;
131
+ --mk-surface: #1b1b25;
132
+ --mk-surface-2: #14141c;
133
+ --mk-surface-3: #23232f;
134
+ --mk-surface-inverse: #f3f3f8;
135
+ --mk-overlay-scrim: rgba(0, 0, 0, 0.6);
136
+ --mk-text: #f3f3f8;
137
+ --mk-text-muted: #c3c3cf;
138
+ --mk-text-subtle: #8e8ea2;
139
+ --mk-text-inverse: #14141c;
140
+ --mk-text-disabled: #5c5c70;
141
+ --mk-border: #2a2a38;
142
+ --mk-border-strong: #3a3a4c;
143
+ --mk-border-subtle: #22222e;
144
+ --mk-hover-overlay: rgba(255, 255, 255, 0.05);
145
+ --mk-active-overlay: rgba(255, 255, 255, 0.09);
146
+ --mk-selected-bg: #26244a;
147
+ --mk-selected-text: #c9c3fa;
148
+ --mk-primary: #6d62ec;
149
+ --mk-primary-hover: #7f75f0;
150
+ --mk-primary-active: #918af4;
151
+ --mk-primary-contrast: #ffffff;
152
+ --mk-primary-subtle: #26244a;
153
+ --mk-primary-subtle-hover: #2f2c5c;
154
+ --mk-primary-subtle-text: #c9c3fa;
155
+ --mk-success: #2bc282;
156
+ --mk-success-hover: #4ed39a;
157
+ --mk-success-contrast: #06281a;
158
+ --mk-success-subtle: #123326;
159
+ --mk-success-subtle-text: #7ee2b3;
160
+ --mk-warning: #f0a93a;
161
+ --mk-warning-hover: #f5bb5c;
162
+ --mk-warning-contrast: #2a1a02;
163
+ --mk-warning-subtle: #33240f;
164
+ --mk-warning-subtle-text: #fbcf7a;
165
+ --mk-danger: #f06c62;
166
+ --mk-danger-hover: #f58a82;
167
+ --mk-danger-contrast: #2a0b09;
168
+ --mk-danger-text: #f58a82;
169
+ --mk-danger-subtle: #3a1a1a;
170
+ --mk-danger-subtle-text: #fca9a2;
171
+ --mk-info: #5b9cf9;
172
+ --mk-info-hover: #7cb1fb;
173
+ --mk-info-contrast: #04182e;
174
+ --mk-info-subtle: #12243c;
175
+ --mk-info-subtle-text: #a8ccfd;
176
+ --mk-neutral-subtle: #23232f;
177
+ --mk-neutral-subtle-hover: #2c2c3a;
178
+ --mk-neutral-subtle-text: #c3c3cf;
179
+ --mk-focus-ring: #8478f2;
180
+ --mk-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
181
+ --mk-shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.4);
182
+ --mk-shadow-md: 0 8px 18px rgba(0, 0, 0, 0.5);
183
+ --mk-shadow-lg: 0 22px 50px rgba(0, 0, 0, 0.55);
184
+ --mk-shadow-xl: 0 32px 70px rgba(0, 0, 0, 0.65);
185
+ --mk-skeleton-base: #23232f;
186
+ --mk-skeleton-shine: #2e2e3c;
187
+ --mk-scrollbar-thumb: #2a2a38;
188
+ --mk-scrollbar-thumb-hover: #3a3a4c;
189
+ --mk-code-bg: #23232f;
190
+ --mk-chart-1: #8478f2;
191
+ --mk-chart-2: #2fc07f;
192
+ --mk-chart-3: #ff7d70;
193
+ --mk-chart-4: #f0a93a;
194
+ --mk-chart-5: #bb7de3;
195
+ --mk-chart-6: #4dc2bc;
196
+ --mk-chart-7: #e5709c;
197
+ --mk-chart-8: #86a0ff;
198
+ }
199
+ }
200
+ /* Explicit dark. */
201
+ [data-mk-theme=dark] [data-mk-preset=momentum]:not([data-mk-contrast=high]),
202
+ [data-mk-preset=momentum]:not([data-mk-contrast=high])[data-mk-theme=dark] {
203
+ color-scheme: dark;
204
+ --mk-bg: #0d0d13;
205
+ --mk-surface: #1b1b25;
206
+ --mk-surface-2: #14141c;
207
+ --mk-surface-3: #23232f;
208
+ --mk-surface-inverse: #f3f3f8;
209
+ --mk-overlay-scrim: rgba(0, 0, 0, 0.6);
210
+ --mk-text: #f3f3f8;
211
+ --mk-text-muted: #c3c3cf;
212
+ --mk-text-subtle: #8e8ea2;
213
+ --mk-text-inverse: #14141c;
214
+ --mk-text-disabled: #5c5c70;
215
+ --mk-border: #2a2a38;
216
+ --mk-border-strong: #3a3a4c;
217
+ --mk-border-subtle: #22222e;
218
+ --mk-hover-overlay: rgba(255, 255, 255, 0.05);
219
+ --mk-active-overlay: rgba(255, 255, 255, 0.09);
220
+ --mk-selected-bg: #26244a;
221
+ --mk-selected-text: #c9c3fa;
222
+ --mk-primary: #6d62ec;
223
+ --mk-primary-hover: #7f75f0;
224
+ --mk-primary-active: #918af4;
225
+ --mk-primary-contrast: #ffffff;
226
+ --mk-primary-subtle: #26244a;
227
+ --mk-primary-subtle-hover: #2f2c5c;
228
+ --mk-primary-subtle-text: #c9c3fa;
229
+ --mk-success: #2bc282;
230
+ --mk-success-hover: #4ed39a;
231
+ --mk-success-contrast: #06281a;
232
+ --mk-success-subtle: #123326;
233
+ --mk-success-subtle-text: #7ee2b3;
234
+ --mk-warning: #f0a93a;
235
+ --mk-warning-hover: #f5bb5c;
236
+ --mk-warning-contrast: #2a1a02;
237
+ --mk-warning-subtle: #33240f;
238
+ --mk-warning-subtle-text: #fbcf7a;
239
+ --mk-danger: #f06c62;
240
+ --mk-danger-hover: #f58a82;
241
+ --mk-danger-contrast: #2a0b09;
242
+ --mk-danger-text: #f58a82;
243
+ --mk-danger-subtle: #3a1a1a;
244
+ --mk-danger-subtle-text: #fca9a2;
245
+ --mk-info: #5b9cf9;
246
+ --mk-info-hover: #7cb1fb;
247
+ --mk-info-contrast: #04182e;
248
+ --mk-info-subtle: #12243c;
249
+ --mk-info-subtle-text: #a8ccfd;
250
+ --mk-neutral-subtle: #23232f;
251
+ --mk-neutral-subtle-hover: #2c2c3a;
252
+ --mk-neutral-subtle-text: #c3c3cf;
253
+ --mk-focus-ring: #8478f2;
254
+ --mk-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
255
+ --mk-shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.4);
256
+ --mk-shadow-md: 0 8px 18px rgba(0, 0, 0, 0.5);
257
+ --mk-shadow-lg: 0 22px 50px rgba(0, 0, 0, 0.55);
258
+ --mk-shadow-xl: 0 32px 70px rgba(0, 0, 0, 0.65);
259
+ --mk-skeleton-base: #23232f;
260
+ --mk-skeleton-shine: #2e2e3c;
261
+ --mk-scrollbar-thumb: #2a2a38;
262
+ --mk-scrollbar-thumb-hover: #3a3a4c;
263
+ --mk-code-bg: #23232f;
264
+ --mk-chart-1: #8478f2;
265
+ --mk-chart-2: #2fc07f;
266
+ --mk-chart-3: #ff7d70;
267
+ --mk-chart-4: #f0a93a;
268
+ --mk-chart-5: #bb7de3;
269
+ --mk-chart-6: #4dc2bc;
270
+ --mk-chart-7: #e5709c;
271
+ --mk-chart-8: #86a0ff;
272
+ }
@@ -1130,6 +1130,10 @@ interface MkI18nStrings {
1130
1130
  dndMovedToList: (list: string, position: number, total: number) => string;
1131
1131
  /** Announced when the item is dropped. */
1132
1132
  dndDropped: (position: number) => string;
1133
+ /** Announced as a lifted item reaches a drop zone (a target that is not a list). */
1134
+ dndMovedToZone: (zone: string) => string;
1135
+ /** Announced when the item is dropped on a zone. */
1136
+ dndDroppedInZone: (zone: string) => string;
1133
1137
  /** Announced when the drag is cancelled. */
1134
1138
  dndCancelled: string;
1135
1139
  /** Repeater add-row button caption (default when no `addLabel` is given). */
@@ -70,6 +70,12 @@ declare class MkDragHandle {
70
70
  * it up, **Arrow** keys to move it (crossing into connected lists at the ends /
71
71
  * across the perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
72
72
  *
73
+ * Besides lists, an item can be released on a connected `[mkDropZone]` — a
74
+ * target that reports *where* it was dropped instead of an index (a timeline,
75
+ * a priority band, a "focus on this" pane). Zones sit in the same keyboard
76
+ * travel group as lists, in document order; while an item hovers a zone no
77
+ * placeholder is shown and the zone streams `mkDropZoneMoved` events.
78
+ *
73
79
  * Touch: a swipe scrolls the page as usual — the drag only arms after a
74
80
  * long-press ({@link mkDragTouchDelay}, default 300 ms). While armed the item
75
81
  * gets the `mk-drag--armed` class so consumers can style the lift moment.
@@ -145,6 +151,8 @@ declare class MkDrag<T = unknown> {
145
151
  private targetIndex;
146
152
  private homeIndex;
147
153
  private placeholder;
154
+ /** The zone the item hovers (pointer) or sits on (keyboard); `null` while a list is the target. */
155
+ private targetZone;
148
156
  private pointerId;
149
157
  private started;
150
158
  private startX;
@@ -182,6 +190,12 @@ declare class MkDrag<T = unknown> {
182
190
  private cachedGroup;
183
191
  /** List bounds snapshotted at lift / after invalidation. */
184
192
  private readonly listRects;
193
+ /** Connected zones resolved once at lift, and their bounds. */
194
+ private cachedZones;
195
+ private readonly zoneRects;
196
+ /** Last pointer position a frame applied — the drop position on a zone. */
197
+ private lastX;
198
+ private lastY;
185
199
  /** Item bounds per list, aligned with `itemElementsExcept(this)`. */
186
200
  private readonly itemRects;
187
201
  /** Lists whose snapshots a placeholder move invalidated (re-measured next frame). */
@@ -216,13 +230,31 @@ declare class MkDrag<T = unknown> {
216
230
  * all style/DOM writes — no read ever follows a write within the frame.
217
231
  */
218
232
  private applyPendingMove;
233
+ /** Pointer entered / moved over `zone`: no placeholder, the zone gets the position. */
234
+ private hoverZone;
235
+ /** Leave the current zone, if any (the zone is told, so it can clear its preview). */
236
+ private leaveZone;
237
+ /**
238
+ * Take the placeholder out of the lists while the item is over a zone. The
239
+ * next `syncPlaceholder` re-inserts it (the idempotence guard is reset), and
240
+ * the list it left is re-measured because its layout just changed.
241
+ */
242
+ private detachPlaceholder;
243
+ /** Position of the item over `zone` — cached bounds on the pointer path, live otherwise. */
244
+ private zoneHover;
245
+ private zoneEvent;
219
246
  private finishPointer;
220
247
  private commitPointer;
221
248
  protected onKeyDown(event: Event): void;
222
249
  protected onFocusOut(event: Event): void;
223
250
  private pickUp;
224
251
  private stepPrimary;
225
- private stepList;
252
+ /**
253
+ * Cross to the previous / next target on the perpendicular axis: connected
254
+ * lists **and** zones, in document order (see `MkDragDropRegistry.travelGroup`).
255
+ */
256
+ private stepTarget;
257
+ private moveToZone;
226
258
  private moveToList;
227
259
  private dropKeyboard;
228
260
  private cancelKeyboard;
@@ -232,6 +264,8 @@ declare class MkDrag<T = unknown> {
232
264
  private announceMove;
233
265
  /** Confirmation after a successful drop (pointer: polite; keyboard: assertive). */
234
266
  private announceDropped;
267
+ /** Confirmation after a drop on a zone. */
268
+ private announceDroppedInZone;
235
269
  /** The drag was cancelled and the item snapped back. */
236
270
  private announceCancelled;
237
271
  /** Highest valid target index for `list` given the item is being removed. */
@@ -242,10 +276,10 @@ declare class MkDrag<T = unknown> {
242
276
  /** (Re)measure one list's bounds and item bounds into the cache. */
243
277
  private measureList;
244
278
  /**
245
- * Which connected list (if any) the pointer is currently over. Pointer path
246
- * only — reads the rects snapshotted at lift, not live layout.
279
+ * Which connected list or zone (if any) the pointer is currently over.
280
+ * Pointer path only — reads the rects snapshotted at lift, not live layout.
247
281
  */
248
- private listUnderPoint;
282
+ private targetUnderPoint;
249
283
  /**
250
284
  * Insertion index for the pointer position within `list`. Pointer path only
251
285
  * — reads the cached item rects (live measurement is the fallback for a
@@ -381,6 +415,90 @@ declare class MkDropList<T = unknown> {
381
415
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDropList<any>, "[mkDropList]", ["mkDropList"], { "mkDropListData": { "alias": "mkDropListData"; "required": false; "isSignal": true; }; "mkDropListId": { "alias": "mkDropListId"; "required": false; "isSignal": true; }; "mkDropListConnectedTo": { "alias": "mkDropListConnectedTo"; "required": false; "isSignal": true; }; "mkDropListLabel": { "alias": "mkDropListLabel"; "required": false; "isSignal": true; }; "mkDropListLabelledBy": { "alias": "mkDropListLabelledBy"; "required": false; "isSignal": true; }; "mkDropListOrientation": { "alias": "mkDropListOrientation"; "required": false; "isSignal": true; }; "mkDropListDisabled": { "alias": "mkDropListDisabled"; "required": false; "isSignal": true; }; }, { "mkDropListDropped": "mkDropListDropped"; }, ["drags"], ["*"], true, never>;
382
416
  }
383
417
 
418
+ /**
419
+ * A drop **target** that is not a list: an item dragged out of a connected
420
+ * `[mkDropList]` can be released anywhere on it, and the zone reports *where*
421
+ * — client coordinates, the offset inside the zone and the 0–1 fraction along
422
+ * each axis — so the consumer can turn a position into meaning: a time on a
423
+ * timeline, a priority band, a "focus on this" pane, a trash can.
424
+ *
425
+ * Nothing reorders and no placeholder is shown while an item hovers a zone;
426
+ * the zone gets the `mk-drop-zone--receiving` class and a stream of
427
+ * {@link mkDropZoneMoved} events instead. Zones and lists can overlap — the
428
+ * innermost target under the pointer wins, so a column can hold three
429
+ * priority bands and still accept plain drops between the bands.
430
+ *
431
+ * Wire a zone exactly like another list: give it an id and name that id in
432
+ * the source list's `mkDropListConnectedTo`.
433
+ *
434
+ * Keyboard: a lifted item reaches zones with the arrow keys that cross lists
435
+ * (Left/Right in a vertical list, Up/Down in a horizontal one) — zones sit in
436
+ * the same DOM-ordered travel group as connected lists; Space/Enter drops at
437
+ * the zone's centre. Every step is announced.
438
+ *
439
+ * ```html
440
+ * <ul mkDropList mkDropListId="backlog" [mkDropListConnectedTo]="['now', 'rail']" …>
441
+ * <section mkDropZone mkDropZoneId="now" mkDropZoneLabel="Focus now"
442
+ * (mkDropZoneDropped)="focus($event.item.mkDragData())">
443
+ * <div mkDropZone mkDropZoneId="rail" mkDropZoneLabel="Today"
444
+ * (mkDropZoneMoved)="preview($event.fractionY)"
445
+ * (mkDropZoneDropped)="schedule($event.item.mkDragData(), $event.fractionY)">
446
+ * ```
447
+ *
448
+ * @typeParam Z the zone's own payload type (`mkDropZoneData`).
449
+ * @typeParam T the dragged item's data type. A zone cannot infer it from a
450
+ * binding the way a list does from `mkDropListData`, so it defaults to
451
+ * `any` — a handler typed `(e: MkDropZoneEvent<Task>) => …` binds directly.
452
+ */
453
+ declare class MkDropZone<Z = unknown, T = any> {
454
+ private readonly registry;
455
+ /** The zone's host element (drop target bounds). */
456
+ readonly element: HTMLElement;
457
+ /** Stable id source lists name in `mkDropListConnectedTo`. Auto-generated when omitted. */
458
+ readonly mkDropZoneId: _angular_core.InputSignal<string | undefined>;
459
+ /**
460
+ * Human-readable name, used in screen-reader announcements when a lifted
461
+ * item reaches the zone ("Moved to Focus now") and as the zone's accessible
462
+ * name. Set it: the fallback is the id, which may be generated gibberish.
463
+ */
464
+ readonly mkDropZoneLabel: _angular_core.InputSignal<string>;
465
+ /** Id of a visible element that names the zone (`aria-labelledby`); wins over the label as the accessible name. */
466
+ readonly mkDropZoneLabelledBy: _angular_core.InputSignal<string>;
467
+ /** Arbitrary payload handed back on every hover and drop event. */
468
+ readonly mkDropZoneData: _angular_core.InputSignal<Z | undefined>;
469
+ /** Disable dropping onto this zone (it leaves the travel group too). */
470
+ readonly mkDropZoneDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
471
+ /** An item entered the zone (pointer or keyboard). */
472
+ readonly mkDropZoneEntered: _angular_core.OutputEmitterRef<MkDropZoneHover<T, Z>>;
473
+ /** The pointer moved while over the zone (one per frame, pointer only). */
474
+ readonly mkDropZoneMoved: _angular_core.OutputEmitterRef<MkDropZoneHover<T, Z>>;
475
+ /** The item left the zone without dropping (moved on, or the drag was cancelled). */
476
+ readonly mkDropZoneLeft: _angular_core.OutputEmitterRef<MkDrag<T>>;
477
+ /** The item was released on the zone. */
478
+ readonly mkDropZoneDropped: _angular_core.OutputEmitterRef<MkDropZoneEvent<T, Z>>;
479
+ /** Resolved id (input or generated). */
480
+ readonly id: _angular_core.Signal<string>;
481
+ private readonly autoId;
482
+ /** Announceable name: the label when set, otherwise the resolved id. */
483
+ readonly label: _angular_core.Signal<string>;
484
+ /** A `role` written in the template is kept; a zone is otherwise a named `group`. */
485
+ protected readonly role: string;
486
+ private readonly staticAriaLabel;
487
+ protected readonly ariaLabel: _angular_core.Signal<string | null>;
488
+ /** Highlight while a drag is hovering (or a keyboard-lifted item sits on) the zone. */
489
+ protected readonly _receiving: _angular_core.WritableSignal<boolean>;
490
+ constructor();
491
+ /** Toggle the "receiving" highlight (called by the active drag). */
492
+ setReceiving(value: boolean): void;
493
+ /** Called by the active `MkDrag` — not part of the consumer API. */
494
+ emitEntered(event: MkDropZoneHover<T, Z>): void;
495
+ emitMoved(event: MkDropZoneHover<T, Z>): void;
496
+ emitLeft(item: MkDrag<T>): void;
497
+ emitDrop(event: MkDropZoneEvent<T, Z>): void;
498
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDropZone<any, any>, never>;
499
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDropZone<any, any>, "[mkDropZone]", ["mkDropZone"], { "mkDropZoneId": { "alias": "mkDropZoneId"; "required": false; "isSignal": true; }; "mkDropZoneLabel": { "alias": "mkDropZoneLabel"; "required": false; "isSignal": true; }; "mkDropZoneLabelledBy": { "alias": "mkDropZoneLabelledBy"; "required": false; "isSignal": true; }; "mkDropZoneData": { "alias": "mkDropZoneData"; "required": false; "isSignal": true; }; "mkDropZoneDisabled": { "alias": "mkDropZoneDisabled"; "required": false; "isSignal": true; }; }, { "mkDropZoneEntered": "mkDropZoneEntered"; "mkDropZoneMoved": "mkDropZoneMoved"; "mkDropZoneLeft": "mkDropZoneLeft"; "mkDropZoneDropped": "mkDropZoneDropped"; }, never, ["*"], true, never>;
500
+ }
501
+
384
502
  /** Layout axis a drop list lays its items along. */
385
503
  type MkDropListOrientation = 'vertical' | 'horizontal';
386
504
  /**
@@ -420,6 +538,43 @@ interface MkDropEvent<T = unknown> {
420
538
  /** `true` for pointer (mouse/touch/pen) drops, `false` for keyboard drops. */
421
539
  isPointerEvent: boolean;
422
540
  }
541
+ /**
542
+ * Where an item is over a `[mkDropZone]`, emitted on enter and on every
543
+ * pointer frame (`mkDropZoneEntered` / `mkDropZoneMoved`). Keyboard entries
544
+ * report the zone's centre.
545
+ *
546
+ * @typeParam T item data type.
547
+ * @typeParam Z zone data type (`mkDropZoneData`).
548
+ */
549
+ interface MkDropZoneHover<T = unknown, Z = unknown> {
550
+ /** The `MkDrag` directive instance being dragged. */
551
+ item: MkDrag<T>;
552
+ /** The zone under the pointer. */
553
+ zone: MkDropZone<Z>;
554
+ /** Pointer position in client (viewport) coordinates. */
555
+ x: number;
556
+ y: number;
557
+ /** Pointer position relative to the zone's top-left corner, in CSS pixels. */
558
+ offsetX: number;
559
+ offsetY: number;
560
+ /** Pointer position as a 0–1 fraction of the zone's width / height (clamped). */
561
+ fractionX: number;
562
+ fractionY: number;
563
+ /** `true` for pointer (mouse/touch/pen) hovers, `false` for keyboard steps. */
564
+ isPointerEvent: boolean;
565
+ }
566
+ /**
567
+ * Emitted by `[mkDropZone]` (`mkDropZoneDropped`) when an item is released on
568
+ * it. Carries the same position fields as {@link MkDropZoneHover} plus where
569
+ * the item came from — the consumer removes it from that list (or not: a
570
+ * "focus on this" zone may leave the card where it is).
571
+ */
572
+ interface MkDropZoneEvent<T = unknown, Z = unknown> extends MkDropZoneHover<T, Z> {
573
+ /** The list the item came from. */
574
+ previousContainer: MkDropList<T>;
575
+ /** Index of the item within `previousContainer`'s data. */
576
+ previousIndex: number;
577
+ }
423
578
 
424
579
  /**
425
580
  * Pure array helpers for applying an {@link MkDropEvent}. They mutate the passed
@@ -446,6 +601,8 @@ declare function mkMoveItemInArray<T>(array: T[], fromIndex: number, toIndex: nu
446
601
  */
447
602
  declare function mkTransferArrayItem<T>(from: T[], to: T[], fromIndex: number, toIndex: number): T[];
448
603
 
604
+ /** A keyboard-reachable drop target: a connected list or a connected zone. */
605
+ type MkDropTarget = MkDropList<any> | MkDropZone<any>;
449
606
  /**
450
607
  * Central registry of every live `[mkDropList]` on the page, keyed by id.
451
608
  *
@@ -458,6 +615,7 @@ declare function mkTransferArrayItem<T>(from: T[], to: T[], fromIndex: number, t
458
615
  */
459
616
  declare class MkDragDropRegistry {
460
617
  private readonly lists;
618
+ private readonly zones;
461
619
  /** Register (or replace) the list published under `id`. */
462
620
  register(id: string, list: MkDropList<any>): void;
463
621
  /** Remove `list` from the registry if it is still the holder of `id`. */
@@ -473,6 +631,20 @@ declare class MkDragDropRegistry {
473
631
  * hit-test the pointer against candidate targets.
474
632
  */
475
633
  connectedGroup(list: MkDropList<any>): MkDropList<any>[];
634
+ /** Register (or replace) the zone published under `id`. */
635
+ registerZone(id: string, zone: MkDropZone<any>): void;
636
+ /** Remove `zone` from the registry if it is still the holder of `id`. */
637
+ unregisterZone(id: string, zone: MkDropZone<any>): void;
638
+ /** Look up a zone by its `mkDropZoneId`. */
639
+ getZone(id: string): MkDropZone<any> | undefined;
640
+ /** Every enabled zone named in `list`'s `mkDropListConnectedTo`, in registration order. */
641
+ connectedZones(list: MkDropList<any>): MkDropZone<any>[];
642
+ /**
643
+ * The keyboard travel group for `list`: its connected lists **and** zones,
644
+ * in document order, so arrow keys walk targets the way they appear on
645
+ * screen regardless of when each registered.
646
+ */
647
+ travelGroup(list: MkDropList<any>): MkDropTarget[];
476
648
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDragDropRegistry, never>;
477
649
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<MkDragDropRegistry>;
478
650
  }
@@ -531,5 +703,5 @@ declare class MkSortableList<T = unknown> {
531
703
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkSortableList<any>, "mk-sortable-list", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelledBy": { "alias": "labelledBy"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; }, { "items": "itemsChange"; "sorted": "sorted"; }, ["itemTemplate"], never, true, never>;
532
704
  }
533
705
 
534
- export { MkDrag, MkDragDropRegistry, MkDragHandle, MkDropList, MkSortableList, mkMoveItemInArray, mkTransferArrayItem };
535
- export type { MkDropEvent, MkDropListOrientation };
706
+ export { MkDrag, MkDragDropRegistry, MkDragHandle, MkDropList, MkDropZone, MkSortableList, mkMoveItemInArray, mkTransferArrayItem };
707
+ export type { MkDropEvent, MkDropListOrientation, MkDropTarget, MkDropZoneEvent, MkDropZoneHover };
@@ -0,0 +1,39 @@
1
+ import { MkTranslateLoaderFactory } from '@mk-kit/ui/translate';
2
+
3
+ /** Options for {@link mkFsTranslateLoader}. */
4
+ interface MkFsTranslateLoaderOptions {
5
+ /**
6
+ * Directories searched in order for `<lang><suffix>`; the first readable
7
+ * file wins. Typical: the built browser assets next to the server bundle,
8
+ * then the source tree for `ng serve`.
9
+ */
10
+ dirs: string[];
11
+ /** File suffix after the language code. Default `.json`. */
12
+ suffix?: string;
13
+ /**
14
+ * When no directory has the file: return `{}` (default) so the app boots
15
+ * with keys as text, or throw so the failure surfaces at once.
16
+ */
17
+ onNotFound?: 'empty' | 'throw';
18
+ }
19
+ /**
20
+ * Server-side loader that reads the translation JSON from disk instead of
21
+ * fetching the app's own HTTP endpoint during SSR — no self-request, no
22
+ * proxy hop, no interceptor ordering to get right. Register it in the
23
+ * server config only (`app.config.server.ts`); the browser keeps
24
+ * `mkHttpTranslateLoader`. Files are parsed once per process.
25
+ *
26
+ * ```ts
27
+ * // app.config.server.ts
28
+ * provideMkTranslate({
29
+ * ...browserTranslateConfig,
30
+ * loader: mkFsTranslateLoader({
31
+ * dirs: [join(import.meta.dirname, '../browser/assets/i18n'), join(process.cwd(), 'src/assets/i18n')],
32
+ * }),
33
+ * })
34
+ * ```
35
+ */
36
+ declare function mkFsTranslateLoader(options: MkFsTranslateLoaderOptions): MkTranslateLoaderFactory;
37
+
38
+ export { mkFsTranslateLoader };
39
+ export type { MkFsTranslateLoaderOptions };