@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.
@@ -67,6 +67,7 @@ function mkTransferArrayItem(from, to, fromIndex, toIndex) {
67
67
  */
68
68
  class MkDragDropRegistry {
69
69
  lists = new Map();
70
+ zones = new Map();
70
71
  /** Register (or replace) the list published under `id`. */
71
72
  register(id, list) {
72
73
  this.lists.set(id, list);
@@ -94,6 +95,38 @@ class MkDragDropRegistry {
94
95
  const connected = list.connectedTo();
95
96
  return this.all().filter((l) => l === list || (connected.includes(l.id()) && !l.mkDropListDisabled()));
96
97
  }
98
+ /** Register (or replace) the zone published under `id`. */
99
+ registerZone(id, zone) {
100
+ this.zones.set(id, zone);
101
+ }
102
+ /** Remove `zone` from the registry if it is still the holder of `id`. */
103
+ unregisterZone(id, zone) {
104
+ if (this.zones.get(id) === zone)
105
+ this.zones.delete(id);
106
+ }
107
+ /** Look up a zone by its `mkDropZoneId`. */
108
+ getZone(id) {
109
+ return this.zones.get(id);
110
+ }
111
+ /** Every enabled zone named in `list`'s `mkDropListConnectedTo`, in registration order. */
112
+ connectedZones(list) {
113
+ const connected = list.connectedTo();
114
+ return [...this.zones.values()].filter((z) => connected.includes(z.id()) && !z.mkDropZoneDisabled());
115
+ }
116
+ /**
117
+ * The keyboard travel group for `list`: its connected lists **and** zones,
118
+ * in document order, so arrow keys walk targets the way they appear on
119
+ * screen regardless of when each registered.
120
+ */
121
+ travelGroup(list) {
122
+ const targets = [...this.connectedGroup(list), ...this.connectedZones(list)];
123
+ return targets.sort((a, b) => {
124
+ if (a.element === b.element)
125
+ return 0;
126
+ const pos = a.element.compareDocumentPosition(b.element);
127
+ return pos & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
128
+ });
129
+ }
97
130
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDragDropRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
98
131
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDragDropRegistry, providedIn: 'root' });
99
132
  }
@@ -199,6 +232,12 @@ const SETTLE_MS = 180;
199
232
  * it up, **Arrow** keys to move it (crossing into connected lists at the ends /
200
233
  * across the perpendicular axis), **Space/Enter** to drop, **Escape** to cancel.
201
234
  *
235
+ * Besides lists, an item can be released on a connected `[mkDropZone]` — a
236
+ * target that reports *where* it was dropped instead of an index (a timeline,
237
+ * a priority band, a "focus on this" pane). Zones sit in the same keyboard
238
+ * travel group as lists, in document order; while an item hovers a zone no
239
+ * placeholder is shown and the zone streams `mkDropZoneMoved` events.
240
+ *
202
241
  * Touch: a swipe scrolls the page as usual — the drag only arms after a
203
242
  * long-press ({@link mkDragTouchDelay}, default 300 ms). While armed the item
204
243
  * gets the `mk-drag--armed` class so consumers can style the lift moment.
@@ -290,6 +329,8 @@ class MkDrag {
290
329
  targetIndex = 0;
291
330
  homeIndex = 0;
292
331
  placeholder = null;
332
+ /** The zone the item hovers (pointer) or sits on (keyboard); `null` while a list is the target. */
333
+ targetZone = null;
293
334
  // --- pointer session state ---
294
335
  pointerId = null;
295
336
  started = false;
@@ -361,6 +402,12 @@ class MkDrag {
361
402
  cachedGroup = [];
362
403
  /** List bounds snapshotted at lift / after invalidation. */
363
404
  listRects = new Map();
405
+ /** Connected zones resolved once at lift, and their bounds. */
406
+ cachedZones = [];
407
+ zoneRects = new Map();
408
+ /** Last pointer position a frame applied — the drop position on a zone. */
409
+ lastX = 0;
410
+ lastY = 0;
364
411
  /** Item bounds per list, aligned with `itemElementsExcept(this)`. */
365
412
  itemRects = new Map();
366
413
  /** Lists whose snapshots a placeholder move invalidated (re-measured next frame). */
@@ -564,17 +611,30 @@ class MkDrag {
564
611
  }
565
612
  const x = this.pendingX;
566
613
  const y = this.pendingY;
567
- const list = this.listUnderPoint(x, y) ?? this.targetList;
568
- const index = list ? this.indexInList(list, x, y) : this.targetIndex;
614
+ this.lastX = x;
615
+ this.lastY = y;
616
+ const hit = this.targetUnderPoint(x, y);
569
617
  // Writes: follow the cursor, then settle the placeholder.
570
618
  if (this.preview) {
571
619
  const dx = x - this.offsetX - this.originLeft;
572
620
  const dy = y - this.offsetY - this.originTop;
573
621
  this.preview.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
574
622
  }
623
+ if (hit && !(hit instanceof MkDropList)) {
624
+ this.hoverZone(hit, x, y);
625
+ return;
626
+ }
627
+ // Over nothing: the last target keeps the item (a zone included).
628
+ if (!hit && this.targetZone)
629
+ return;
630
+ const list = hit ?? this.targetList;
575
631
  if (!list)
576
632
  return;
577
- if (list !== this.targetList) {
633
+ const index = this.indexInList(list, x, y);
634
+ const wasOnZone = this.targetZone !== null;
635
+ if (wasOnZone)
636
+ this.leaveZone();
637
+ if (list !== this.targetList || wasOnZone) {
578
638
  this.targetList?.setReceiving(false);
579
639
  this.targetList = list;
580
640
  list.setReceiving(true);
@@ -582,6 +642,64 @@ class MkDrag {
582
642
  this.targetIndex = index;
583
643
  this.syncPlaceholder();
584
644
  }
645
+ /** Pointer entered / moved over `zone`: no placeholder, the zone gets the position. */
646
+ hoverZone(zone, x, y) {
647
+ const hover = this.zoneHover(zone, x, y, true);
648
+ if (zone === this.targetZone) {
649
+ zone.emitMoved(hover);
650
+ return;
651
+ }
652
+ this.leaveZone();
653
+ this.targetList?.setReceiving(false);
654
+ this.detachPlaceholder();
655
+ this.targetZone = zone;
656
+ zone.setReceiving(true);
657
+ zone.emitEntered(hover);
658
+ }
659
+ /** Leave the current zone, if any (the zone is told, so it can clear its preview). */
660
+ leaveZone() {
661
+ const zone = this.targetZone;
662
+ if (!zone)
663
+ return;
664
+ this.targetZone = null;
665
+ zone.setReceiving(false);
666
+ zone.emitLeft(this);
667
+ }
668
+ /**
669
+ * Take the placeholder out of the lists while the item is over a zone. The
670
+ * next `syncPlaceholder` re-inserts it (the idempotence guard is reset), and
671
+ * the list it left is re-measured because its layout just changed.
672
+ */
673
+ detachPlaceholder() {
674
+ const ph = this.placeholder;
675
+ if (!ph)
676
+ return;
677
+ const prev = this.lastSyncList;
678
+ ph.remove();
679
+ this.lastSyncList = null;
680
+ this.lastSyncIndex = -1;
681
+ if (prev)
682
+ this.dirtyLists.add(prev);
683
+ }
684
+ /** Position of the item over `zone` — cached bounds on the pointer path, live otherwise. */
685
+ zoneHover(zone, x, y, isPointerEvent) {
686
+ const r = this.zoneRects.get(zone) ?? zone.element.getBoundingClientRect();
687
+ const clamp = (v) => Math.min(1, Math.max(0, v));
688
+ return {
689
+ item: this,
690
+ zone,
691
+ x,
692
+ y,
693
+ offsetX: x - r.left,
694
+ offsetY: y - r.top,
695
+ fractionX: r.width ? clamp((x - r.left) / r.width) : 0,
696
+ fractionY: r.height ? clamp((y - r.top) / r.height) : 0,
697
+ isPointerEvent,
698
+ };
699
+ }
700
+ zoneEvent(zone, x, y, isPointerEvent, previousContainer, previousIndex) {
701
+ return { ...this.zoneHover(zone, x, y, isPointerEvent), previousContainer, previousIndex };
702
+ }
585
703
  finishPointer(cancel) {
586
704
  if (this.pointerId !== null) {
587
705
  try {
@@ -603,7 +721,8 @@ class MkDrag {
603
721
  // reflects exactly where the pointer ended, not the last painted frame.
604
722
  this.flushMoveFrame(!cancel);
605
723
  const settle = () => this.commitPointer(cancel);
606
- if (cancel || this.prefersReducedMotion() || !this.preview) {
724
+ // A zone has no placeholder to settle onto — commit straight away.
725
+ if (cancel || this.targetZone || this.prefersReducedMotion() || !this.preview) {
607
726
  settle();
608
727
  return;
609
728
  }
@@ -632,10 +751,23 @@ class MkDrag {
632
751
  commitPointer(cancel) {
633
752
  if (this.destroyed)
634
753
  return;
754
+ const zone = this.targetZone;
635
755
  const container = this.targetList;
636
756
  const previousContainer = this.home;
637
757
  const currentIndex = this.targetIndex;
638
758
  const previousIndex = this.homeIndex;
759
+ if (!cancel && zone && previousContainer) {
760
+ // Build the event before cleanup clears the cached bounds; detach the
761
+ // zone first so cleanup does not report a "left".
762
+ const event = this.zoneEvent(zone, this.lastX, this.lastY, true, previousContainer, previousIndex);
763
+ this.targetZone = null;
764
+ zone.setReceiving(false);
765
+ this.cleanupDom();
766
+ this.dragging.set(false);
767
+ zone.emitDrop(event);
768
+ this.announceDroppedInZone(zone, 'polite');
769
+ return;
770
+ }
639
771
  this.cleanupDom();
640
772
  this.dragging.set(false);
641
773
  if (cancel || !container || !previousContainer) {
@@ -664,6 +796,9 @@ class MkDrag {
664
796
  return;
665
797
  }
666
798
  // Picked up: capture the movement / drop / cancel keys.
799
+ // On a zone there is no position to step through — every arrow walks the
800
+ // travel group (previous / next target), Space/Enter drops at the centre.
801
+ const onZone = this.targetZone !== null;
667
802
  const horizontal = this.targetList?.mkDropListOrientation() === 'horizontal';
668
803
  switch (key) {
669
804
  case ' ':
@@ -677,19 +812,19 @@ class MkDrag {
677
812
  break;
678
813
  case 'ArrowUp':
679
814
  e.preventDefault();
680
- horizontal ? this.stepList(-1) : this.stepPrimary(-1);
815
+ onZone || horizontal ? this.stepTarget(-1) : this.stepPrimary(-1);
681
816
  break;
682
817
  case 'ArrowDown':
683
818
  e.preventDefault();
684
- horizontal ? this.stepList(1) : this.stepPrimary(1);
819
+ onZone || horizontal ? this.stepTarget(1) : this.stepPrimary(1);
685
820
  break;
686
821
  case 'ArrowLeft':
687
822
  e.preventDefault();
688
- horizontal ? this.stepPrimary(-1) : this.stepList(-1);
823
+ onZone || !horizontal ? this.stepTarget(-1) : this.stepPrimary(-1);
689
824
  break;
690
825
  case 'ArrowRight':
691
826
  e.preventDefault();
692
- horizontal ? this.stepPrimary(1) : this.stepList(1);
827
+ onZone || !horizontal ? this.stepTarget(1) : this.stepPrimary(1);
693
828
  break;
694
829
  default:
695
830
  break;
@@ -744,28 +879,66 @@ class MkDrag {
744
879
  this.syncPlaceholder();
745
880
  this.announceMove(false);
746
881
  }
747
- stepList(step) {
748
- const list = this.targetList;
749
- if (!list)
882
+ /**
883
+ * Cross to the previous / next target on the perpendicular axis: connected
884
+ * lists **and** zones, in document order (see `MkDragDropRegistry.travelGroup`).
885
+ */
886
+ stepTarget(step) {
887
+ const current = this.targetZone ?? this.targetList;
888
+ if (!current || !this.home)
889
+ return;
890
+ const group = this.registry.travelGroup(this.home);
891
+ const i = group.indexOf(current);
892
+ if (i < 0)
750
893
  return;
751
- const adj = this.adjacentList(list, step);
752
- if (!adj)
894
+ const next = group[i + step];
895
+ if (!next)
753
896
  return;
754
- this.moveToList(adj, Math.min(this.targetIndex, this.maxIndex(adj)), true);
897
+ if (next instanceof MkDropList) {
898
+ const wasOnZone = this.targetZone !== null;
899
+ this.leaveZone();
900
+ this.moveToList(next, Math.min(this.targetIndex, this.maxIndex(next)), true, wasOnZone);
901
+ }
902
+ else {
903
+ this.moveToZone(next);
904
+ }
755
905
  }
756
- moveToList(list, index, crossed) {
906
+ moveToZone(zone) {
907
+ this.leaveZone();
908
+ this.targetList?.setReceiving(false);
909
+ this.detachPlaceholder();
910
+ this.targetZone = zone;
911
+ zone.setReceiving(true);
912
+ const r = zone.element.getBoundingClientRect();
913
+ zone.emitEntered(this.zoneHover(zone, r.left + r.width / 2, r.top + r.height / 2, false));
914
+ this.announcer.announce(this.i18n.dndMovedToZone(zone.label()), 'assertive');
915
+ }
916
+ moveToList(list, index, crossed, fromZone = false) {
757
917
  this.targetList?.setReceiving(false);
758
918
  this.targetList = list;
759
919
  this.targetIndex = index;
760
920
  list.setReceiving(true);
761
921
  this.syncPlaceholder();
762
- this.announceMove(crossed);
922
+ // Coming back from a zone into the same list still crossed a target.
923
+ this.announceMove(crossed || fromZone);
763
924
  }
764
925
  dropKeyboard() {
926
+ const zone = this.targetZone;
765
927
  const container = this.targetList;
766
928
  const previousContainer = this.home;
767
929
  const currentIndex = this.targetIndex;
768
930
  const previousIndex = this.homeIndex;
931
+ if (zone && previousContainer) {
932
+ const r = zone.element.getBoundingClientRect();
933
+ const event = this.zoneEvent(zone, r.left + r.width / 2, r.top + r.height / 2, false, previousContainer, previousIndex);
934
+ this.targetZone = null;
935
+ zone.setReceiving(false);
936
+ this.cleanupDom();
937
+ this.lifted.set(false);
938
+ zone.emitDrop(event);
939
+ this.announceDroppedInZone(zone, 'assertive');
940
+ return;
941
+ }
769
942
  this.cleanupDom();
770
943
  this.lifted.set(false);
771
944
  if (!container || !previousContainer)
@@ -801,6 +974,10 @@ class MkDrag {
801
974
  announceDropped(index, politeness) {
802
975
  this.announcer.announce(this.i18n.dndDropped(index + 1), politeness);
803
976
  }
977
+ /** Confirmation after a drop on a zone. */
978
+ announceDroppedInZone(zone, politeness) {
979
+ this.announcer.announce(this.i18n.dndDroppedInZone(zone.label()), politeness);
980
+ }
804
981
  /** The drag was cancelled and the item snapped back. */
805
982
  announceCancelled(politeness) {
806
983
  this.announcer.announce(this.i18n.dndCancelled, politeness);
@@ -821,10 +998,15 @@ class MkDrag {
821
998
  /** Snapshot every connected list's bounds + item bounds (at lift / scroll). */
822
999
  snapshotRects() {
823
1000
  this.cachedGroup = this.home ? this.registry.connectedGroup(this.home) : [];
1001
+ this.cachedZones = this.home ? this.registry.connectedZones(this.home) : [];
824
1002
  this.listRects.clear();
825
1003
  this.itemRects.clear();
1004
+ this.zoneRects.clear();
826
1005
  for (const list of this.cachedGroup)
827
1006
  this.measureList(list);
1007
+ for (const zone of this.cachedZones) {
1008
+ this.zoneRects.set(zone, zone.element.getBoundingClientRect());
1009
+ }
828
1010
  }
829
1011
  /** (Re)measure one list's bounds and item bounds into the cache. */
830
1012
  measureList(list) {
@@ -832,25 +1014,31 @@ class MkDrag {
832
1014
  this.itemRects.set(list, list.itemElementsExcept(this).map((el) => el.getBoundingClientRect()));
833
1015
  }
834
1016
  /**
835
- * Which connected list (if any) the pointer is currently over. Pointer path
836
- * only — reads the rects snapshotted at lift, not live layout.
1017
+ * Which connected list or zone (if any) the pointer is currently over.
1018
+ * Pointer path only — reads the rects snapshotted at lift, not live layout.
837
1019
  */
838
- listUnderPoint(x, y) {
839
- // Every candidate whose bounds contain the point. Lists nested inside the
840
- // dragged item itself are never targets (an item cannot be dropped into
841
- // its own descendants).
1020
+ targetUnderPoint(x, y) {
1021
+ // Every candidate whose bounds contain the point. Targets nested inside the
1022
+ // dragged item itself are never hit (an item cannot be dropped into its
1023
+ // own descendants).
842
1024
  const hits = [];
1025
+ const inside = (r) => x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
843
1026
  for (const list of this.cachedGroup) {
844
1027
  if (list.element !== this.element && this.element.contains(list.element))
845
1028
  continue;
846
- const r = this.listRects.get(list) ?? list.element.getBoundingClientRect();
847
- if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom)
1029
+ if (inside(this.listRects.get(list) ?? list.element.getBoundingClientRect()))
848
1030
  hits.push(list);
849
1031
  }
1032
+ for (const zone of this.cachedZones) {
1033
+ if (this.element.contains(zone.element))
1034
+ continue;
1035
+ if (inside(this.zoneRects.get(zone) ?? zone.element.getBoundingClientRect()))
1036
+ hits.push(zone);
1037
+ }
850
1038
  if (hits.length <= 1)
851
1039
  return hits[0] ?? null;
852
- // Nested lists: the innermost hit wins — the one that contains no other hit.
853
- return (hits.find((list) => !hits.some((other) => other !== list && list.element.contains(other.element))) ??
1040
+ // Nested targets: the innermost hit wins — the one that contains no other hit.
1041
+ return (hits.find((t) => !hits.some((other) => other !== t && t.element.contains(other.element))) ??
854
1042
  hits[0]);
855
1043
  }
856
1044
  /**
@@ -960,9 +1148,12 @@ class MkDrag {
960
1148
  this.element.style.display = '';
961
1149
  this.home?.setReceiving(false);
962
1150
  this.targetList?.setReceiving(false);
1151
+ this.leaveZone();
963
1152
  this.cachedGroup = [];
1153
+ this.cachedZones = [];
964
1154
  this.listRects.clear();
965
1155
  this.itemRects.clear();
1156
+ this.zoneRects.clear();
966
1157
  this.dirtyLists.clear();
967
1158
  this.scrollDirty = false;
968
1159
  this.lastSyncList = null;
@@ -1209,6 +1400,127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1209
1400
  }, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative}:host(.mk-drop-list--receiving){outline:var(--mk-border-width-strong) solid var(--mk-primary-subtle-text);outline-offset:calc(-1 * var(--mk-border-width-strong));border-radius:var(--mk-radius-md);background-color:color-mix(in srgb,var(--mk-primary) 6%,transparent)}:host(.mk-drop-list--disabled){cursor:not-allowed}\n"] }]
1210
1401
  }], ctorParameters: () => [], propDecorators: { mkDropListData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListData", required: false }] }], mkDropListId: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListId", required: false }] }], mkDropListConnectedTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListConnectedTo", required: false }] }], mkDropListLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListLabel", required: false }] }], mkDropListLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListLabelledBy", required: false }] }], mkDropListOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListOrientation", required: false }] }], mkDropListDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropListDisabled", required: false }] }], mkDropListDropped: [{ type: i0.Output, args: ["mkDropListDropped"] }], drags: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkDrag), { isSignal: true }] }] } });
1211
1402
 
1403
+ /**
1404
+ * A drop **target** that is not a list: an item dragged out of a connected
1405
+ * `[mkDropList]` can be released anywhere on it, and the zone reports *where*
1406
+ * — client coordinates, the offset inside the zone and the 0–1 fraction along
1407
+ * each axis — so the consumer can turn a position into meaning: a time on a
1408
+ * timeline, a priority band, a "focus on this" pane, a trash can.
1409
+ *
1410
+ * Nothing reorders and no placeholder is shown while an item hovers a zone;
1411
+ * the zone gets the `mk-drop-zone--receiving` class and a stream of
1412
+ * {@link mkDropZoneMoved} events instead. Zones and lists can overlap — the
1413
+ * innermost target under the pointer wins, so a column can hold three
1414
+ * priority bands and still accept plain drops between the bands.
1415
+ *
1416
+ * Wire a zone exactly like another list: give it an id and name that id in
1417
+ * the source list's `mkDropListConnectedTo`.
1418
+ *
1419
+ * Keyboard: a lifted item reaches zones with the arrow keys that cross lists
1420
+ * (Left/Right in a vertical list, Up/Down in a horizontal one) — zones sit in
1421
+ * the same DOM-ordered travel group as connected lists; Space/Enter drops at
1422
+ * the zone's centre. Every step is announced.
1423
+ *
1424
+ * ```html
1425
+ * <ul mkDropList mkDropListId="backlog" [mkDropListConnectedTo]="['now', 'rail']" …>
1426
+ * <section mkDropZone mkDropZoneId="now" mkDropZoneLabel="Focus now"
1427
+ * (mkDropZoneDropped)="focus($event.item.mkDragData())">
1428
+ * <div mkDropZone mkDropZoneId="rail" mkDropZoneLabel="Today"
1429
+ * (mkDropZoneMoved)="preview($event.fractionY)"
1430
+ * (mkDropZoneDropped)="schedule($event.item.mkDragData(), $event.fractionY)">
1431
+ * ```
1432
+ *
1433
+ * @typeParam Z the zone's own payload type (`mkDropZoneData`).
1434
+ * @typeParam T the dragged item's data type. A zone cannot infer it from a
1435
+ * binding the way a list does from `mkDropListData`, so it defaults to
1436
+ * `any` — a handler typed `(e: MkDropZoneEvent<Task>) => …` binds directly.
1437
+ */
1438
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see the class doc
1439
+ class MkDropZone {
1440
+ registry = inject(MkDragDropRegistry);
1441
+ /** The zone's host element (drop target bounds). */
1442
+ element = inject(ElementRef).nativeElement;
1443
+ /** Stable id source lists name in `mkDropListConnectedTo`. Auto-generated when omitted. */
1444
+ mkDropZoneId = input(/* @ts-ignore */
1445
+ ...(ngDevMode ? [undefined, { debugName: "mkDropZoneId" }] : /* istanbul ignore next */ []));
1446
+ /**
1447
+ * Human-readable name, used in screen-reader announcements when a lifted
1448
+ * item reaches the zone ("Moved to Focus now") and as the zone's accessible
1449
+ * name. Set it: the fallback is the id, which may be generated gibberish.
1450
+ */
1451
+ mkDropZoneLabel = input('', /* @ts-ignore */
1452
+ ...(ngDevMode ? [{ debugName: "mkDropZoneLabel" }] : /* istanbul ignore next */ []));
1453
+ /** Id of a visible element that names the zone (`aria-labelledby`); wins over the label as the accessible name. */
1454
+ mkDropZoneLabelledBy = input('', /* @ts-ignore */
1455
+ ...(ngDevMode ? [{ debugName: "mkDropZoneLabelledBy" }] : /* istanbul ignore next */ []));
1456
+ /** Arbitrary payload handed back on every hover and drop event. */
1457
+ mkDropZoneData = input(/* @ts-ignore */
1458
+ ...(ngDevMode ? [undefined, { debugName: "mkDropZoneData" }] : /* istanbul ignore next */ []));
1459
+ /** Disable dropping onto this zone (it leaves the travel group too). */
1460
+ mkDropZoneDisabled = input(false, { ...(ngDevMode ? { debugName: "mkDropZoneDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1461
+ /** An item entered the zone (pointer or keyboard). */
1462
+ mkDropZoneEntered = output();
1463
+ /** The pointer moved while over the zone (one per frame, pointer only). */
1464
+ mkDropZoneMoved = output();
1465
+ /** The item left the zone without dropping (moved on, or the drag was cancelled). */
1466
+ mkDropZoneLeft = output();
1467
+ /** The item was released on the zone. */
1468
+ mkDropZoneDropped = output();
1469
+ /** Resolved id (input or generated). */
1470
+ id = computed(() => this.mkDropZoneId() ?? this.autoId, /* @ts-ignore */
1471
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
1472
+ autoId = mkUniqueId('mk-drop-zone');
1473
+ /** Announceable name: the label when set, otherwise the resolved id. */
1474
+ label = computed(() => this.mkDropZoneLabel() || this.id(), /* @ts-ignore */
1475
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1476
+ /** A `role` written in the template is kept; a zone is otherwise a named `group`. */
1477
+ role = this.element.getAttribute('role') ?? 'group';
1478
+ staticAriaLabel = this.element.getAttribute('aria-label');
1479
+ ariaLabel = computed(() => this.mkDropZoneLabelledBy() ? null : this.mkDropZoneLabel() || this.staticAriaLabel || null, /* @ts-ignore */
1480
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1481
+ /** Highlight while a drag is hovering (or a keyboard-lifted item sits on) the zone. */
1482
+ _receiving = signal(false, /* @ts-ignore */
1483
+ ...(ngDevMode ? [{ debugName: "_receiving" }] : /* istanbul ignore next */ []));
1484
+ constructor() {
1485
+ effect((onCleanup) => {
1486
+ const id = this.id();
1487
+ this.registry.registerZone(id, this);
1488
+ onCleanup(() => this.registry.unregisterZone(id, this));
1489
+ });
1490
+ }
1491
+ /** Toggle the "receiving" highlight (called by the active drag). */
1492
+ setReceiving(value) {
1493
+ this._receiving.set(value);
1494
+ }
1495
+ /** Called by the active `MkDrag` — not part of the consumer API. */
1496
+ emitEntered(event) {
1497
+ this.mkDropZoneEntered.emit(event);
1498
+ }
1499
+ emitMoved(event) {
1500
+ this.mkDropZoneMoved.emit(event);
1501
+ }
1502
+ emitLeft(item) {
1503
+ this.mkDropZoneLeft.emit(item);
1504
+ }
1505
+ emitDrop(event) {
1506
+ this.mkDropZoneDropped.emit(event);
1507
+ }
1508
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDropZone, deps: [], target: i0.ɵɵFactoryTarget.Component });
1509
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkDropZone, isStandalone: true, selector: "[mkDropZone]", inputs: { mkDropZoneId: { classPropertyName: "mkDropZoneId", publicName: "mkDropZoneId", isSignal: true, isRequired: false, transformFunction: null }, mkDropZoneLabel: { classPropertyName: "mkDropZoneLabel", publicName: "mkDropZoneLabel", isSignal: true, isRequired: false, transformFunction: null }, mkDropZoneLabelledBy: { classPropertyName: "mkDropZoneLabelledBy", publicName: "mkDropZoneLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, mkDropZoneData: { classPropertyName: "mkDropZoneData", publicName: "mkDropZoneData", isSignal: true, isRequired: false, transformFunction: null }, mkDropZoneDisabled: { classPropertyName: "mkDropZoneDisabled", publicName: "mkDropZoneDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mkDropZoneEntered: "mkDropZoneEntered", mkDropZoneMoved: "mkDropZoneMoved", mkDropZoneLeft: "mkDropZoneLeft", mkDropZoneDropped: "mkDropZoneDropped" }, host: { properties: { "attr.role": "role", "attr.aria-label": "ariaLabel()", "attr.aria-labelledby": "mkDropZoneLabelledBy() || null", "attr.aria-disabled": "mkDropZoneDisabled() || null", "class.mk-drop-zone--receiving": "_receiving()", "class.mk-drop-zone--disabled": "mkDropZoneDisabled()" }, classAttribute: "mk-drop-zone" }, exportAs: ["mkDropZone"], ngImport: i0, template: '<ng-content />', isInline: true, styles: ["@charset \"UTF-8\";:host{display:block;position:relative}:host(.mk-drop-zone--receiving){outline:var(--mk-border-width-strong) dashed var(--mk-primary);outline-offset:calc(-1 * var(--mk-border-width-strong));border-radius:var(--mk-radius-md);background-color:color-mix(in srgb,var(--mk-primary) 8%,transparent)}:host(.mk-drop-zone--disabled){cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1510
+ }
1511
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDropZone, decorators: [{
1512
+ type: Component,
1513
+ args: [{ selector: '[mkDropZone]', exportAs: 'mkDropZone', template: '<ng-content />', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1514
+ class: 'mk-drop-zone',
1515
+ '[attr.role]': 'role',
1516
+ '[attr.aria-label]': 'ariaLabel()',
1517
+ '[attr.aria-labelledby]': 'mkDropZoneLabelledBy() || null',
1518
+ '[attr.aria-disabled]': 'mkDropZoneDisabled() || null',
1519
+ '[class.mk-drop-zone--receiving]': '_receiving()',
1520
+ '[class.mk-drop-zone--disabled]': 'mkDropZoneDisabled()',
1521
+ }, styles: ["@charset \"UTF-8\";:host{display:block;position:relative}:host(.mk-drop-zone--receiving){outline:var(--mk-border-width-strong) dashed var(--mk-primary);outline-offset:calc(-1 * var(--mk-border-width-strong));border-radius:var(--mk-radius-md);background-color:color-mix(in srgb,var(--mk-primary) 8%,transparent)}:host(.mk-drop-zone--disabled){cursor:not-allowed}\n"] }]
1522
+ }], ctorParameters: () => [], propDecorators: { mkDropZoneId: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropZoneId", required: false }] }], mkDropZoneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropZoneLabel", required: false }] }], mkDropZoneLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropZoneLabelledBy", required: false }] }], mkDropZoneData: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropZoneData", required: false }] }], mkDropZoneDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "mkDropZoneDisabled", required: false }] }], mkDropZoneEntered: [{ type: i0.Output, args: ["mkDropZoneEntered"] }], mkDropZoneMoved: [{ type: i0.Output, args: ["mkDropZoneMoved"] }], mkDropZoneLeft: [{ type: i0.Output, args: ["mkDropZoneLeft"] }], mkDropZoneDropped: [{ type: i0.Output, args: ["mkDropZoneDropped"] }] } });
1523
+
1212
1524
  /**
1213
1525
  * Thin convenience wrapper over a single `[mkDropList]` for the common
1214
1526
  * "reorderable list" case. Bind `items` two-way and provide an `<ng-template>`
@@ -1283,5 +1595,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1283
1595
  * Generated bundle index. Do not edit.
1284
1596
  */
1285
1597
 
1286
- export { MkDrag, MkDragDropRegistry, MkDragHandle, MkDropList, MkSortableList, mkMoveItemInArray, mkTransferArrayItem };
1598
+ export { MkDrag, MkDragDropRegistry, MkDragHandle, MkDropList, MkDropZone, MkSortableList, mkMoveItemInArray, mkTransferArrayItem };
1287
1599
  //# sourceMappingURL=mk-kit-ui-dnd.mjs.map