@mk-kit/ui 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/fesm2022/mk-kit-ui-core.mjs +2 -0
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dnd.mjs +339 -27
- package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-de.mjs +2 -0
- package/fesm2022/mk-kit-ui-locales-de.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-es.mjs +2 -0
- package/fesm2022/mk-kit-ui-locales-es.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-fr.mjs +2 -0
- package/fesm2022/mk-kit-ui-locales-fr.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-pl.mjs +2 -0
- package/fesm2022/mk-kit-ui-locales-pl.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-uk.mjs +2 -0
- package/fesm2022/mk-kit-ui-locales-uk.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-translate-server.mjs +55 -0
- package/fesm2022/mk-kit-ui-translate-server.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-translate.mjs +417 -0
- package/fesm2022/mk-kit-ui-translate.mjs.map +1 -0
- package/fesm2022/mk-kit-ui.mjs +1 -0
- package/fesm2022/mk-kit-ui.mjs.map +1 -1
- package/package.json +13 -1
- package/styles/presets/momentum.css +272 -0
- package/types/mk-kit-ui-core.d.ts +4 -0
- package/types/mk-kit-ui-dnd.d.ts +178 -6
- package/types/mk-kit-ui-translate-server.d.ts +39 -0
- package/types/mk-kit-ui-translate.d.ts +251 -0
- package/types/mk-kit-ui.d.ts +1 -0
|
@@ -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
|
-
|
|
568
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
815
|
+
onZone || horizontal ? this.stepTarget(-1) : this.stepPrimary(-1);
|
|
681
816
|
break;
|
|
682
817
|
case 'ArrowDown':
|
|
683
818
|
e.preventDefault();
|
|
684
|
-
horizontal ? this.
|
|
819
|
+
onZone || horizontal ? this.stepTarget(1) : this.stepPrimary(1);
|
|
685
820
|
break;
|
|
686
821
|
case 'ArrowLeft':
|
|
687
822
|
e.preventDefault();
|
|
688
|
-
horizontal ? this.
|
|
823
|
+
onZone || !horizontal ? this.stepTarget(-1) : this.stepPrimary(-1);
|
|
689
824
|
break;
|
|
690
825
|
case 'ArrowRight':
|
|
691
826
|
e.preventDefault();
|
|
692
|
-
horizontal ? this.
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
|
752
|
-
if (!
|
|
894
|
+
const next = group[i + step];
|
|
895
|
+
if (!next)
|
|
753
896
|
return;
|
|
754
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
839
|
-
// Every candidate whose bounds contain the point.
|
|
840
|
-
// dragged item itself are never
|
|
841
|
-
//
|
|
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
|
-
|
|
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
|
|
853
|
-
return (hits.find((
|
|
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
|