@flamingo-stack/openframe-frontend-core 0.0.555 → 0.0.556-snapshot.20260818165639
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/dist/{chunk-6ER3AYJM.js → chunk-25YLMIWU.js} +3 -3
- package/dist/{chunk-HO3Q7NXJ.js → chunk-26HAJBSQ.js} +2 -2
- package/dist/{chunk-P22HM2MO.cjs → chunk-33FOBAWS.cjs} +18 -18
- package/dist/{chunk-P22HM2MO.cjs.map → chunk-33FOBAWS.cjs.map} +1 -1
- package/dist/{chunk-N5A4AC2J.cjs → chunk-4EGNYOZA.cjs} +23 -23
- package/dist/{chunk-N5A4AC2J.cjs.map → chunk-4EGNYOZA.cjs.map} +1 -1
- package/dist/{chunk-A7ES3NZK.cjs → chunk-4WREPWRL.cjs} +3 -3
- package/dist/{chunk-A7ES3NZK.cjs.map → chunk-4WREPWRL.cjs.map} +1 -1
- package/dist/{chunk-CU6Q2KQT.cjs → chunk-5HNZTPAB.cjs} +12 -12
- package/dist/{chunk-CU6Q2KQT.cjs.map → chunk-5HNZTPAB.cjs.map} +1 -1
- package/dist/{chunk-MLVF3OXU.cjs → chunk-5JF6H7OJ.cjs} +76 -76
- package/dist/{chunk-MLVF3OXU.cjs.map → chunk-5JF6H7OJ.cjs.map} +1 -1
- package/dist/{chunk-UXW63WCV.cjs → chunk-64HHDGRN.cjs} +4 -4
- package/dist/{chunk-UXW63WCV.cjs.map → chunk-64HHDGRN.cjs.map} +1 -1
- package/dist/{chunk-KXLQITOX.cjs → chunk-6YXEZCVG.cjs} +5 -5
- package/dist/{chunk-KXLQITOX.cjs.map → chunk-6YXEZCVG.cjs.map} +1 -1
- package/dist/{chunk-SST2YI7S.cjs → chunk-6ZOVSD4M.cjs} +14 -14
- package/dist/{chunk-SST2YI7S.cjs.map → chunk-6ZOVSD4M.cjs.map} +1 -1
- package/dist/{chunk-M5TWHFIN.cjs → chunk-A6TAVIBX.cjs} +42 -42
- package/dist/{chunk-M5TWHFIN.cjs.map → chunk-A6TAVIBX.cjs.map} +1 -1
- package/dist/{chunk-J76LCSR4.cjs → chunk-BE6TRUKY.cjs} +40 -40
- package/dist/{chunk-J76LCSR4.cjs.map → chunk-BE6TRUKY.cjs.map} +1 -1
- package/dist/{chunk-Q6RC3YGW.cjs → chunk-DFJ7TWH6.cjs} +3 -3
- package/dist/{chunk-Q6RC3YGW.cjs.map → chunk-DFJ7TWH6.cjs.map} +1 -1
- package/dist/{chunk-B2GEKAAA.js → chunk-DRNSIK3V.js} +2 -2
- package/dist/{chunk-CTTY5EKH.js → chunk-FXVMRZIQ.js} +5 -5
- package/dist/{chunk-QZ64JOE2.js → chunk-G3NBTL4I.js} +3246 -2557
- package/dist/chunk-G3NBTL4I.js.map +1 -0
- package/dist/{chunk-TXV444YV.js → chunk-G7ROZAY5.js} +3 -3
- package/dist/{chunk-E56VNTBV.cjs → chunk-IPO24G7U.cjs} +1775 -1086
- package/dist/chunk-IPO24G7U.cjs.map +1 -0
- package/dist/{chunk-CVCQW7WG.js → chunk-LJWTXODF.js} +3 -3
- package/dist/{chunk-P7MSXPGL.js → chunk-NNGQ6ONX.js} +8 -8
- package/dist/{chunk-XOKRFZBD.cjs → chunk-QCBBX6DO.cjs} +97 -97
- package/dist/{chunk-XOKRFZBD.cjs.map → chunk-QCBBX6DO.cjs.map} +1 -1
- package/dist/{chunk-GSONSIWG.js → chunk-R7A7WHXP.js} +60 -35
- package/dist/chunk-R7A7WHXP.js.map +1 -0
- package/dist/{chunk-L36R3G7Z.js → chunk-RPCY2FAC.js} +6 -6
- package/dist/{chunk-QMQQ37RW.js → chunk-RRWWAH37.js} +2 -2
- package/dist/{chunk-ODRMPI3Y.js → chunk-SBKX2VSG.js} +2 -2
- package/dist/{chunk-KQKU4VXB.cjs → chunk-STHVHAV4.cjs} +82 -57
- package/dist/chunk-STHVHAV4.cjs.map +1 -0
- package/dist/{chunk-E2IPBWGT.js → chunk-UTEQHK3H.js} +6 -6
- package/dist/{chunk-6QRKCVYR.cjs → chunk-V5EYGEX7.cjs} +7 -7
- package/dist/{chunk-6QRKCVYR.cjs.map → chunk-V5EYGEX7.cjs.map} +1 -1
- package/dist/{chunk-K432ES7N.js → chunk-VJL46NHW.js} +4 -4
- package/dist/{chunk-QTO3WYB2.cjs → chunk-VJTD2DGF.cjs} +3 -3
- package/dist/{chunk-QTO3WYB2.cjs.map → chunk-VJTD2DGF.cjs.map} +1 -1
- package/dist/{chunk-VGLDA77S.js → chunk-VRX6Y6A7.js} +2 -2
- package/dist/{chunk-OR67WC32.js → chunk-WBQ3NOH7.js} +2 -2
- package/dist/{chunk-UL3WQWTF.js → chunk-Y6TXTBIQ.js} +2 -2
- package/dist/{chunk-YAOBDOI5.cjs → chunk-YHRE4JBF.cjs} +32 -32
- package/dist/{chunk-YAOBDOI5.cjs.map → chunk-YHRE4JBF.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/board/aim.d.ts +57 -0
- package/dist/components/features/board/aim.d.ts.map +1 -0
- package/dist/components/features/board/board-column-header.d.ts.map +1 -1
- package/dist/components/features/board/board-column.d.ts +8 -4
- package/dist/components/features/board/board-column.d.ts.map +1 -1
- package/dist/components/features/board/board.d.ts +15 -4
- package/dist/components/features/board/board.d.ts.map +1 -1
- package/dist/components/features/board/drop-aim.d.ts +34 -0
- package/dist/components/features/board/drop-aim.d.ts.map +1 -0
- package/dist/components/features/board/lane-geometry.d.ts +22 -0
- package/dist/components/features/board/lane-geometry.d.ts.map +1 -0
- package/dist/components/features/board/pending-move.d.ts +77 -0
- package/dist/components/features/board/pending-move.d.ts.map +1 -0
- package/dist/components/features/board/resolve-drop.d.ts +19 -0
- package/dist/components/features/board/resolve-drop.d.ts.map +1 -0
- package/dist/components/features/board/ticket-card.d.ts +25 -9
- package/dist/components/features/board/ticket-card.d.ts.map +1 -1
- package/dist/components/features/board/use-board-collapse.d.ts.map +1 -1
- package/dist/components/features/board/use-lane-scroll-anchor.d.ts +27 -0
- package/dist/components/features/board/use-lane-scroll-anchor.d.ts.map +1 -0
- package/dist/components/features/index.cjs +10 -4
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +1 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +9 -3
- package/dist/components/features/sortable-list/index.d.ts +4 -0
- package/dist/components/features/sortable-list/index.d.ts.map +1 -0
- package/dist/components/features/sortable-list/slot-geometry.d.ts +57 -0
- package/dist/components/features/sortable-list/slot-geometry.d.ts.map +1 -0
- package/dist/components/features/sortable-list/sortable-list.d.ts +50 -0
- package/dist/components/features/sortable-list/sortable-list.d.ts.map +1 -0
- package/dist/components/features/sortable-list/use-sortable-item.d.ts +29 -0
- package/dist/components/features/sortable-list/use-sortable-item.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +25 -25
- package/dist/components/help-center-pages/index.js +16 -16
- package/dist/components/index.cjs +144 -138
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +20 -14
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +35 -35
- package/dist/components/meeting-scheduler/index.js +5 -5
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +8 -8
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +10 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -3
- package/dist/nats/index.cjs +2 -2
- package/dist/nats/index.js +1 -1
- package/dist/nats/nats.d.ts.map +1 -1
- package/dist/nats/shared-connection.d.ts +13 -6
- package/dist/nats/shared-connection.d.ts.map +1 -1
- package/dist/utils/auto-scroll-ancestors.d.ts +43 -0
- package/dist/utils/auto-scroll-ancestors.d.ts.map +1 -0
- package/dist/utils/drag-effect.d.ts +21 -0
- package/dist/utils/drag-effect.d.ts.map +1 -0
- package/dist/utils/scroll-parent.d.ts +11 -0
- package/dist/utils/scroll-parent.d.ts.map +1 -0
- package/package.json +6 -3
- package/src/__tests__/board-aim.test.ts +152 -0
- package/src/__tests__/board-pending-move.test.ts +113 -0
- package/src/__tests__/board-resolve-drop.test.ts +68 -0
- package/src/__tests__/sortable-slot-geometry.test.ts +105 -0
- package/src/components/features/board/.board-column.md +17 -4
- package/src/components/features/board/.board.md +11 -7
- package/src/components/features/board/.ticket-card.md +27 -5
- package/src/components/features/board/aim.ts +131 -0
- package/src/components/features/board/board-column-header.tsx +2 -9
- package/src/components/features/board/board-column.tsx +137 -66
- package/src/components/features/board/board.tsx +501 -258
- package/src/components/features/board/drop-aim.ts +50 -0
- package/src/components/features/board/lane-geometry.ts +44 -0
- package/src/components/features/board/pending-move.ts +116 -0
- package/src/components/features/board/resolve-drop.ts +28 -0
- package/src/components/features/board/ticket-card.tsx +256 -47
- package/src/components/features/board/use-board-collapse.ts +16 -14
- package/src/components/features/board/use-lane-scroll-anchor.ts +79 -0
- package/src/components/features/index.ts +1 -0
- package/src/components/features/sortable-list/.sortable-list.md +82 -0
- package/src/components/features/sortable-list/index.ts +3 -0
- package/src/components/features/sortable-list/slot-geometry.ts +100 -0
- package/src/components/features/sortable-list/sortable-list.tsx +270 -0
- package/src/components/features/sortable-list/use-sortable-item.ts +92 -0
- package/src/components/ui/square-avatar.tsx +5 -0
- package/src/nats/__tests__/reconnect-lifecycle.test.tsx +814 -0
- package/src/nats/nats.ts +56 -13
- package/src/nats/shared-connection.ts +132 -36
- package/src/stories/.TicketCard.stories.md +6 -12
- package/src/stories/Board.stories.tsx +16 -7
- package/src/stories/TicketCard.stories.tsx +38 -76
- package/src/utils/auto-scroll-ancestors.ts +90 -0
- package/src/utils/drag-effect.ts +26 -0
- package/src/utils/scroll-parent.ts +23 -0
- package/dist/chunk-E56VNTBV.cjs.map +0 -1
- package/dist/chunk-GSONSIWG.js.map +0 -1
- package/dist/chunk-KQKU4VXB.cjs.map +0 -1
- package/dist/chunk-QZ64JOE2.js.map +0 -1
- /package/dist/{chunk-6ER3AYJM.js.map → chunk-25YLMIWU.js.map} +0 -0
- /package/dist/{chunk-HO3Q7NXJ.js.map → chunk-26HAJBSQ.js.map} +0 -0
- /package/dist/{chunk-B2GEKAAA.js.map → chunk-DRNSIK3V.js.map} +0 -0
- /package/dist/{chunk-CTTY5EKH.js.map → chunk-FXVMRZIQ.js.map} +0 -0
- /package/dist/{chunk-TXV444YV.js.map → chunk-G7ROZAY5.js.map} +0 -0
- /package/dist/{chunk-CVCQW7WG.js.map → chunk-LJWTXODF.js.map} +0 -0
- /package/dist/{chunk-P7MSXPGL.js.map → chunk-NNGQ6ONX.js.map} +0 -0
- /package/dist/{chunk-L36R3G7Z.js.map → chunk-RPCY2FAC.js.map} +0 -0
- /package/dist/{chunk-QMQQ37RW.js.map → chunk-RRWWAH37.js.map} +0 -0
- /package/dist/{chunk-ODRMPI3Y.js.map → chunk-SBKX2VSG.js.map} +0 -0
- /package/dist/{chunk-E2IPBWGT.js.map → chunk-UTEQHK3H.js.map} +0 -0
- /package/dist/{chunk-K432ES7N.js.map → chunk-VJL46NHW.js.map} +0 -0
- /package/dist/{chunk-VGLDA77S.js.map → chunk-VRX6Y6A7.js.map} +0 -0
- /package/dist/{chunk-OR67WC32.js.map → chunk-WBQ3NOH7.js.map} +0 -0
- /package/dist/{chunk-UL3WQWTF.js.map → chunk-Y6TXTBIQ.js.map} +0 -0
package/dist/nats/index.js
CHANGED
package/dist/nats/nats.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nats.d.ts","sourceRoot":"","sources":["../../src/nats/nats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"nats.d.ts","sourceRoot":"","sources":["../../src/nats/nats.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,17 +23,24 @@ export interface NatsReconnectionBackoff {
|
|
|
23
23
|
export interface SharedConnection {
|
|
24
24
|
wsUrl: string;
|
|
25
25
|
client: NatsClient;
|
|
26
|
-
connectPromise: Promise<void> | null;
|
|
27
26
|
refCount: number;
|
|
28
27
|
closeTimer: ReturnType<typeof setTimeout> | null;
|
|
29
28
|
retryTimer: ReturnType<typeof setTimeout> | null;
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* The lifecycle driving reconnect, held as its own `scheduleRetry`. When set,
|
|
31
|
+
* other consumers observe status only and skip their own scheduleRetry —
|
|
32
|
+
* otherwise every disconnect starts one backoff schedule per attached
|
|
33
|
+
* consumer, all dialling the same connection on their own clocks.
|
|
35
34
|
*/
|
|
36
|
-
retryOwner:
|
|
35
|
+
retryOwner: (() => void) | null;
|
|
36
|
+
/**
|
|
37
|
+
* The `scheduleRetry` of every lifecycle currently observing this connection,
|
|
38
|
+
* in attach order — the same value that goes into `retryOwner`, so a
|
|
39
|
+
* lifecycle has one identity rather than two. Only the owner drives
|
|
40
|
+
* reconnect, so when it gives the loop up the roster is what a successor is
|
|
41
|
+
* found in; see {@link handOffRetry}.
|
|
42
|
+
*/
|
|
43
|
+
retrySchedulers: Set<() => void>;
|
|
37
44
|
}
|
|
38
45
|
export interface AcquireClientOptions {
|
|
39
46
|
name?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-connection.d.ts","sourceRoot":"","sources":["../../src/nats/shared-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAA;AAEjG,eAAO,MAAM,aAAa;;;;;;;;CAQhB,CAAA;AAEV,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,UAAU,CAAA;IAClB,
|
|
1
|
+
{"version":3,"file":"shared-connection.d.ts","sourceRoot":"","sources":["../../src/nats/shared-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAA;AAEjG,eAAO,MAAM,aAAa;;;;;;;;CAQhB,CAAA;AAEV,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAA;IAChD,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAA;IAChD;;;;;OAKG;IACH,UAAU,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B;;;;;;OAMG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAWD;;;;;kEAKkE;AAClE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAG7D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,gBAAgB,CA0CxF;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAqB5E;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,gBAAgB,GAAG,IAAI,CAG9F;AAaD,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,gBAAgB,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9C,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAChC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACnE;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAA;CAChD;AAED,MAAM,WAAW,yBAAyB;IACxC,iFAAiF;IACjF,IAAI,IAAI,IAAI,CAAA;CACb;AA0BD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,0BAA0B,GAAG,yBAAyB,CAwLvG"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers drag auto-scroll for one scroller, both **inside** its edges and
|
|
3
|
+
* **past** them.
|
|
4
|
+
*
|
|
5
|
+
* Pragmatic's over-element scrolling only engages in a band just inside the
|
|
6
|
+
* edge, so a drag that has already left the container stops scrolling it — you
|
|
7
|
+
* have to hunt for the strip where it works. dnd-kit kept scrolling however far
|
|
8
|
+
* out you went, and `unsafeOverflowAutoScrollForElements` is Pragmatic's own
|
|
9
|
+
* answer for that; "unsafe" here only means it can reach a container the pointer
|
|
10
|
+
* is not over, which is exactly what is wanted.
|
|
11
|
+
*/
|
|
12
|
+
export declare function autoScrollBothWays(element: HTMLElement): () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Auto-scroll for one of several scrollers standing side by side — a board lane.
|
|
15
|
+
*
|
|
16
|
+
* Same as {@link autoScrollBothWays} except that its reach past the edges is
|
|
17
|
+
* vertical only, and that is the whole point. Lanes sit shoulder to shoulder, so
|
|
18
|
+
* a reach that also spreads sideways puts a pointer below the board inside
|
|
19
|
+
* EVERY lane's overflow region at once and scrolls all of them together. Keeping
|
|
20
|
+
* the region to the lane's own width means the pointer is below exactly one
|
|
21
|
+
* lane, and only that lane moves — which is what a board is expected to do.
|
|
22
|
+
*/
|
|
23
|
+
export declare function autoScrollColumn(element: HTMLElement): () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Registers auto-scroll on every scrollable ancestor of `element`, and on the
|
|
26
|
+
* window.
|
|
27
|
+
*
|
|
28
|
+
* dnd-kit walked the ancestor chain for you; Pragmatic makes each scroller opt
|
|
29
|
+
* in by name, which is why dragging towards the edge of the screen stopped
|
|
30
|
+
* scrolling anything after the migration. Registering the window alone would
|
|
31
|
+
* have fixed nothing either: the app's real scroller is the layout's `<main>`,
|
|
32
|
+
* not the document — so the chain has to be walked.
|
|
33
|
+
*
|
|
34
|
+
* Registration is passive; a scroller only moves while a drag is actually near
|
|
35
|
+
* its edge, so it is safe to call this once on mount.
|
|
36
|
+
*
|
|
37
|
+
* Register this ONCE per page region, not once per thing that can be dragged:
|
|
38
|
+
* Pragmatic keeps one entry per registration and each entry scrolls on its own,
|
|
39
|
+
* so registering the same `<main>` from five lanes scrolls it five times as
|
|
40
|
+
* fast.
|
|
41
|
+
*/
|
|
42
|
+
export declare function autoScrollAncestors(element: HTMLElement): () => void;
|
|
43
|
+
//# sourceMappingURL=auto-scroll-ancestors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-scroll-ancestors.d.ts","sourceRoot":"","sources":["../../src/utils/auto-scroll-ancestors.ts"],"names":[],"mappings":"AA8BA;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAKnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAKjE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAMpE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds a native drag started from `element` to the "move" effect, from its very
|
|
3
|
+
* first frame.
|
|
4
|
+
*
|
|
5
|
+
* The browser picks the drag cursor from `dataTransfer.dropEffect`, and the
|
|
6
|
+
* value it starts each drag with comes from `effectAllowed`. Pragmatic never
|
|
7
|
+
* writes `effectAllowed`, so it stays `uninitialized` — which the browser reads
|
|
8
|
+
* as "all", whose initial `dropEffect` is `copy`. That is the green plus cursor
|
|
9
|
+
* flashing for one frame at the start of every drag, before the first `dragover`
|
|
10
|
+
* has had a chance to say otherwise.
|
|
11
|
+
*
|
|
12
|
+
* This is a raw listener rather than a Pragmatic callback because `effectAllowed`
|
|
13
|
+
* can only be written during the `dragstart` event itself, and none of
|
|
14
|
+
* Pragmatic's callbacks are handed the event.
|
|
15
|
+
*
|
|
16
|
+
* Distinct from `preventUnhandled`, which answers the other half of the same
|
|
17
|
+
* question: this sets what the drag starts as, that keeps it from falling back
|
|
18
|
+
* to "no drop" over everything that is not a drop target.
|
|
19
|
+
*/
|
|
20
|
+
export declare function holdMoveDragEffect(element: HTMLElement): () => void;
|
|
21
|
+
//# sourceMappingURL=drag-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-effect.d.ts","sourceRoot":"","sources":["../../src/utils/drag-effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAMnE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Every scrollable ancestor of `element`, innermost first. */
|
|
2
|
+
export declare function scrollableAncestorsOf(element: HTMLElement): HTMLElement[];
|
|
3
|
+
/**
|
|
4
|
+
* The nearest scrollable ancestor, or `null`.
|
|
5
|
+
*
|
|
6
|
+
* Stands in for "the working area this thing lives in": in an app whose layout
|
|
7
|
+
* scrolls a `<main>` rather than the document, that element is exactly the
|
|
8
|
+
* region a user considers to be the page.
|
|
9
|
+
*/
|
|
10
|
+
export declare function nearestScrollableAncestor(element: HTMLElement): HTMLElement | null;
|
|
11
|
+
//# sourceMappingURL=scroll-parent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-parent.d.ts","sourceRoot":"","sources":["../../src/utils/scroll-parent.ts"],"names":[],"mappings":"AAGA,+DAA+D;AAC/D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,EAAE,CAOzE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAElF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.556-snapshot.20260818165639",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -321,6 +321,9 @@
|
|
|
321
321
|
}
|
|
322
322
|
},
|
|
323
323
|
"dependencies": {
|
|
324
|
+
"@atlaskit/pragmatic-drag-and-drop": "^3.0.0",
|
|
325
|
+
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^3.0.1",
|
|
326
|
+
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.1.0",
|
|
324
327
|
"@dnd-kit/core": "^6.3.1",
|
|
325
328
|
"@dnd-kit/sortable": "^10.0.0",
|
|
326
329
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -379,6 +382,7 @@
|
|
|
379
382
|
"react-easy-crop": "^5.5.0",
|
|
380
383
|
"react-hook-form": "^7.62.0",
|
|
381
384
|
"react-markdown": "^10.1.0",
|
|
385
|
+
"react-remove-scroll": "^2.7.1",
|
|
382
386
|
"react-resizable-panels": "^3.0.4",
|
|
383
387
|
"react-scroll": "^1.9.0",
|
|
384
388
|
"recharts": "^3.1.0",
|
|
@@ -393,8 +397,7 @@
|
|
|
393
397
|
"tailwindcss-animate": "^1.0.7",
|
|
394
398
|
"unist-util-visit": "^5.0.0",
|
|
395
399
|
"use-stick-to-bottom": "^1.1.4",
|
|
396
|
-
"vaul": "^1.1.2"
|
|
397
|
-
"react-remove-scroll": "^2.7.1"
|
|
400
|
+
"vaul": "^1.1.2"
|
|
398
401
|
},
|
|
399
402
|
"devDependencies": {
|
|
400
403
|
"@biomejs/biome": "2.4.4",
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'
|
|
3
|
+
import { aimAnchor, aimFromTarget, aimToChange, initialAim, moveAim } from '../components/features/board/aim'
|
|
4
|
+
import type { BoardColumnDef } from '../components/features/board/types'
|
|
5
|
+
|
|
6
|
+
const ticket = (id: string) => ({ id, title: id, ticketNumber: `#${id}`, status: 'open' })
|
|
7
|
+
|
|
8
|
+
const COLUMNS: BoardColumnDef[] = [
|
|
9
|
+
{ id: 'open', label: 'Open', color: '#5ea700', tickets: ['a1', 'a2', 'a3'].map(ticket) },
|
|
10
|
+
{ id: 'locked', label: 'Locked', color: '#888888', tickets: ['c1'].map(ticket), dropDisabled: true },
|
|
11
|
+
{ id: 'done', label: 'Done', color: '#1f6feb', tickets: ['b1', 'b2'].map(ticket) },
|
|
12
|
+
{ id: 'empty', label: 'Empty', color: '#333333', tickets: [] },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const accepts = (column: BoardColumnDef) => !column.dropDisabled
|
|
16
|
+
|
|
17
|
+
describe('initialAim', () => {
|
|
18
|
+
it('starts a card exactly where it already sits', () => {
|
|
19
|
+
expect(initialAim(COLUMNS, 'a2')).toEqual({ columnId: 'open', index: 1 })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('has nowhere to start from for a card that is on no lane', () => {
|
|
23
|
+
expect(initialAim(COLUMNS, 'nope')).toBeNull()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('moveAim', () => {
|
|
28
|
+
const aim = { columnId: 'open', index: 1 }
|
|
29
|
+
|
|
30
|
+
it('steps through slots with up and down', () => {
|
|
31
|
+
expect(moveAim(COLUMNS, 'a2', aim, 'ArrowUp', accepts)).toEqual({ columnId: 'open', index: 0 })
|
|
32
|
+
expect(moveAim(COLUMNS, 'a2', aim, 'ArrowDown', accepts)).toEqual({ columnId: 'open', index: 2 })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// The dragged card is not among the slots it can take, so a lane of three has
|
|
36
|
+
// three landing places for it, not four.
|
|
37
|
+
it('stops at the ends of a lane', () => {
|
|
38
|
+
expect(moveAim(COLUMNS, 'a2', { columnId: 'open', index: 0 }, 'ArrowUp', accepts)).toEqual({
|
|
39
|
+
columnId: 'open',
|
|
40
|
+
index: 0,
|
|
41
|
+
})
|
|
42
|
+
expect(moveAim(COLUMNS, 'a2', { columnId: 'open', index: 2 }, 'ArrowDown', accepts)).toEqual({
|
|
43
|
+
columnId: 'open',
|
|
44
|
+
index: 2,
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('skips a lane that will not take the card rather than stopping at it', () => {
|
|
49
|
+
expect(moveAim(COLUMNS, 'a2', aim, 'ArrowRight', accepts)).toEqual({ columnId: 'done', index: 1 })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('keeps the slot it had, as far as the new lane allows', () => {
|
|
53
|
+
expect(moveAim(COLUMNS, 'a2', { columnId: 'open', index: 2 }, 'ArrowRight', accepts)).toEqual({
|
|
54
|
+
columnId: 'done',
|
|
55
|
+
index: 2,
|
|
56
|
+
})
|
|
57
|
+
expect(moveAim(COLUMNS, 'a2', { columnId: 'done', index: 2 }, 'ArrowRight', accepts)).toEqual({
|
|
58
|
+
columnId: 'empty',
|
|
59
|
+
index: 0,
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('stays put at the ends of the board', () => {
|
|
64
|
+
const first = { columnId: 'open', index: 1 }
|
|
65
|
+
expect(moveAim(COLUMNS, 'a2', first, 'ArrowLeft', accepts)).toBe(first)
|
|
66
|
+
const last = { columnId: 'empty', index: 0 }
|
|
67
|
+
expect(moveAim(COLUMNS, 'a2', last, 'ArrowRight', accepts)).toBe(last)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('aimFromTarget', () => {
|
|
72
|
+
const overCard = (ticketId: string, columnId: string, edge: 'top' | 'bottom') => {
|
|
73
|
+
const element = {
|
|
74
|
+
getBoundingClientRect: () => ({ top: 0, left: 0, right: 300, bottom: 100, width: 300, height: 100, x: 0, y: 0 }),
|
|
75
|
+
} as unknown as Element
|
|
76
|
+
return attachClosestEdge(
|
|
77
|
+
{ type: 'ticket', ticketId, columnId },
|
|
78
|
+
{ input: { clientX: 150, clientY: edge === 'top' ? 5 : 95 } as never, element, allowedEdges: ['top', 'bottom'] },
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The flicker this exists to stop: a hit test answers with whichever card the
|
|
83
|
+
// pointer happens to be over, so ONE landing place arrives as "below a3" one
|
|
84
|
+
// frame and "above b-nothing" the next. As an index they are the same slot, so
|
|
85
|
+
// the line has one position to be drawn at instead of two to jump between.
|
|
86
|
+
it('reads the same slot from either side of one boundary', () => {
|
|
87
|
+
const below = aimFromTarget(COLUMNS, 'a2', overCard('a1', 'open', 'bottom'))
|
|
88
|
+
const above = aimFromTarget(COLUMNS, 'a2', overCard('a3', 'open', 'top'))
|
|
89
|
+
expect(below).toEqual(above)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('counts slots among the other cards, never the one being moved', () => {
|
|
93
|
+
expect(aimFromTarget(COLUMNS, 'a2', overCard('a3', 'open', 'bottom'))).toEqual({ columnId: 'open', index: 2 })
|
|
94
|
+
expect(aimFromTarget(COLUMNS, 'a2', overCard('a1', 'open', 'top'))).toEqual({ columnId: 'open', index: 0 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('reads a lane target as past the last card', () => {
|
|
98
|
+
expect(aimFromTarget(COLUMNS, 'a2', { type: 'column', columnId: 'done' })).toEqual({ columnId: 'done', index: 2 })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('has no answer for a lane the board does not have', () => {
|
|
102
|
+
expect(aimFromTarget(COLUMNS, 'a2', { type: 'column', columnId: 'gone' })).toBeNull()
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('aimAnchor', () => {
|
|
107
|
+
it('hangs the preview above the card it would push down', () => {
|
|
108
|
+
expect(aimAnchor(COLUMNS, 'a2', { columnId: 'open', index: 0 })).toEqual({ ticketId: 'a1', edge: 'top' })
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('hangs it below the last card when it would land at the end', () => {
|
|
112
|
+
expect(aimAnchor(COLUMNS, 'a2', { columnId: 'open', index: 2 })).toEqual({ ticketId: 'a3', edge: 'bottom' })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('has nothing to hang off in an empty lane', () => {
|
|
116
|
+
expect(aimAnchor(COLUMNS, 'a2', { columnId: 'empty', index: 0 })).toEqual({ ticketId: null, edge: null })
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('aimToChange', () => {
|
|
121
|
+
it('reports nothing when the card would land back where it started', () => {
|
|
122
|
+
expect(aimToChange(COLUMNS, 'a2', { columnId: 'open', index: 1 })).toBeNull()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('reports a move within one lane', () => {
|
|
126
|
+
expect(aimToChange(COLUMNS, 'a2', { columnId: 'open', index: 0 })).toEqual({
|
|
127
|
+
ticketId: 'a2',
|
|
128
|
+
fromColumnId: 'open',
|
|
129
|
+
toColumnId: 'open',
|
|
130
|
+
afterTicketId: null,
|
|
131
|
+
beforeTicketId: 'a1',
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('reports a move to another lane', () => {
|
|
136
|
+
expect(aimToChange(COLUMNS, 'a2', { columnId: 'done', index: 1 })).toEqual({
|
|
137
|
+
ticketId: 'a2',
|
|
138
|
+
fromColumnId: 'open',
|
|
139
|
+
toColumnId: 'done',
|
|
140
|
+
afterTicketId: 'b1',
|
|
141
|
+
beforeTicketId: 'b2',
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('reports a move into an empty lane', () => {
|
|
146
|
+
expect(aimToChange(COLUMNS, 'a2', { columnId: 'empty', index: 0 })).toMatchObject({
|
|
147
|
+
toColumnId: 'empty',
|
|
148
|
+
afterTicketId: null,
|
|
149
|
+
beforeTicketId: null,
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { applyPendingMove, hasMoveSettled, type PendingMove } from '../components/features/board/pending-move'
|
|
3
|
+
import type { BoardColumnDef } from '../components/features/board/types'
|
|
4
|
+
|
|
5
|
+
const ticket = (id: string) => ({ id, title: id, ticketNumber: `#${id}`, status: 'open' })
|
|
6
|
+
|
|
7
|
+
const columns = (open: string[], done: string[]): BoardColumnDef[] => [
|
|
8
|
+
{ id: 'open', label: 'Open', color: '#5ea700', tickets: open.map(ticket) },
|
|
9
|
+
{ id: 'done', label: 'Done', color: '#1f6feb', tickets: done.map(ticket) },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
/** `a2` was dragged out of Open and dropped into Done, straight after `b1`. */
|
|
13
|
+
const MOVE: PendingMove = { ticketId: 'a2', fromColumnId: 'open', toColumnId: 'done', afterTicketId: 'b1' }
|
|
14
|
+
|
|
15
|
+
describe('hasMoveSettled', () => {
|
|
16
|
+
it('is unsettled while the card is still in the lane it came from', () => {
|
|
17
|
+
expect(hasMoveSettled(columns(['a1', 'a2', 'a3'], ['b1', 'b2']), MOVE)).toBe(false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('settles when the card turns up exactly where it was dropped', () => {
|
|
21
|
+
expect(hasMoveSettled(columns(['a1', 'a3'], ['b1', 'a2', 'b2']), MOVE)).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// The failure this test exists for: each lane polls on its own timer and takes
|
|
25
|
+
// live updates, so the lane a card came FROM changes for reasons that have
|
|
26
|
+
// nothing to do with the move. Reading any of that as "landed" uncovers the
|
|
27
|
+
// card in its old place — the flash the whole mechanism exists to remove.
|
|
28
|
+
it('stays unsettled when the lane it came from changes for unrelated reasons', () => {
|
|
29
|
+
expect(hasMoveSettled(columns(['a0', 'a1', 'a2', 'a3'], ['b1', 'b2']), MOVE)).toBe(false)
|
|
30
|
+
expect(hasMoveSettled(columns(['a2'], ['b1', 'b2']), MOVE)).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// A server free to sort the destination lane its own way will never reproduce
|
|
34
|
+
// the dropped index. Holding out for one it will not send means the board
|
|
35
|
+
// shows a position it has already been told is wrong, until the timeout jerks
|
|
36
|
+
// it into place seconds later.
|
|
37
|
+
it('settles anywhere in the new lane once the old one has let go', () => {
|
|
38
|
+
expect(hasMoveSettled(columns(['a1', 'a3'], ['a2', 'b1', 'b2']), MOVE)).toBe(true)
|
|
39
|
+
expect(hasMoveSettled(columns(['a1', 'a3'], ['b1', 'b2', 'a2']), MOVE)).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// The two lanes are separate queries and answer at their own pace. Handing
|
|
43
|
+
// back while both still list the card shows it in two places at once.
|
|
44
|
+
it('stays unsettled while both lanes still list the card', () => {
|
|
45
|
+
expect(hasMoveSettled(columns(['a1', 'a2', 'a3'], ['b1', 'a2', 'b2']), MOVE)).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('recognises a drop at the head of a lane', () => {
|
|
49
|
+
const toHead: PendingMove = { ticketId: 'a2', fromColumnId: 'open', toColumnId: 'done', afterTicketId: null }
|
|
50
|
+
expect(hasMoveSettled(columns(['a1', 'a3'], ['a2', 'b1']), toHead)).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Within one lane the card is in that lane whether the move landed or not, so
|
|
54
|
+
// here the exact slot is the only thing that can answer.
|
|
55
|
+
it('holds a reorder inside one lane to the exact slot', () => {
|
|
56
|
+
const withinLane: PendingMove = { ticketId: 'a1', fromColumnId: 'open', toColumnId: 'open', afterTicketId: 'a2' }
|
|
57
|
+
expect(hasMoveSettled(columns(['a1', 'a2', 'a3'], []), withinLane)).toBe(false)
|
|
58
|
+
expect(hasMoveSettled(columns(['a2', 'a1', 'a3'], []), withinLane)).toBe(true)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('is unsettled when the lane it was dropped into is gone', () => {
|
|
62
|
+
expect(hasMoveSettled([{ id: 'open', label: 'Open', color: '#5ea700', tickets: [] }], MOVE)).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('applyPendingMove', () => {
|
|
67
|
+
it('shows the card in the lane it was dropped into, in the slot it was dropped in', () => {
|
|
68
|
+
const moved = applyPendingMove(columns(['a1', 'a2', 'a3'], ['b1', 'b2']), MOVE)
|
|
69
|
+
expect(moved[0].tickets.map(t => t.id)).toEqual(['a1', 'a3'])
|
|
70
|
+
expect(moved[1].tickets.map(t => t.id)).toEqual(['b1', 'a2', 'b2'])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('drops the card at the head of the lane', () => {
|
|
74
|
+
const toHead: PendingMove = { ticketId: 'a2', fromColumnId: 'open', toColumnId: 'done', afterTicketId: null }
|
|
75
|
+
const moved = applyPendingMove(columns(['a1', 'a2'], ['b1']), toHead)
|
|
76
|
+
expect(moved[1].tickets.map(t => t.id)).toEqual(['a2', 'b1'])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('reorders inside one lane', () => {
|
|
80
|
+
const within: PendingMove = { ticketId: 'a1', fromColumnId: 'open', toColumnId: 'open', afterTicketId: 'a3' }
|
|
81
|
+
const moved = applyPendingMove(columns(['a1', 'a2', 'a3'], []), within)
|
|
82
|
+
expect(moved[0].tickets.map(t => t.id)).toEqual(['a2', 'a3', 'a1'])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// The whole point of the optimistic view: once the host agrees, applying the
|
|
86
|
+
// move to its answer has to be a no-op, or handing back would move the card
|
|
87
|
+
// a second time.
|
|
88
|
+
it('is idempotent once the host has caught up', () => {
|
|
89
|
+
const settled = columns(['a1', 'a3'], ['b1', 'a2', 'b2'])
|
|
90
|
+
expect(hasMoveSettled(settled, MOVE)).toBe(true)
|
|
91
|
+
const moved = applyPendingMove(settled, MOVE)
|
|
92
|
+
expect(moved[0].tickets.map(t => t.id)).toEqual(['a1', 'a3'])
|
|
93
|
+
expect(moved[1].tickets.map(t => t.id)).toEqual(['b1', 'a2', 'b2'])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('appends when the card it was dropped after is gone', () => {
|
|
97
|
+
const moved = applyPendingMove(columns(['a1', 'a2'], ['b2']), MOVE)
|
|
98
|
+
expect(moved[1].tickets.map(t => t.id)).toEqual(['b2', 'a2'])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('leaves untouched lanes identical, so memoized lanes can skip the render', () => {
|
|
102
|
+
const before = columns(['a1', 'a2'], ['b1'])
|
|
103
|
+
const extra = [...before, { id: 'cold', label: 'Cold', color: '#333', tickets: [ticket('c1')] }]
|
|
104
|
+
const moved = applyPendingMove(extra, MOVE)
|
|
105
|
+
expect(moved[2]).toBe(extra[2])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('changes nothing when the card or the lane is unknown', () => {
|
|
109
|
+
const before = columns(['a1'], ['b1'])
|
|
110
|
+
expect(applyPendingMove(before, { ...MOVE, ticketId: 'ghost' })).toBe(before)
|
|
111
|
+
expect(applyPendingMove(before, { ...MOVE, toColumnId: 'ghost' })).toBe(before)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { resolveBoardDrop } from '../components/features/board/resolve-drop'
|
|
4
|
+
import type { BoardColumnDef } from '../components/features/board/types'
|
|
5
|
+
|
|
6
|
+
const ticket = (id: string) => ({ id, title: id, ticketNumber: `#${id}`, status: 'open' })
|
|
7
|
+
|
|
8
|
+
const COLUMNS: BoardColumnDef[] = [
|
|
9
|
+
{ id: 'open', label: 'Open', color: '#5ea700', tickets: ['a1', 'a2', 'a3'].map(ticket) },
|
|
10
|
+
{ id: 'done', label: 'Done', color: '#1f6feb', tickets: ['b1', 'b2'].map(ticket) },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
/** A card drop target, with the edge attached the way the hitbox does it. */
|
|
14
|
+
function overCard(ticketId: string, columnId: string, edge: 'top' | 'bottom') {
|
|
15
|
+
const height = 100
|
|
16
|
+
const top = edge === 'top' ? 0 : 0
|
|
17
|
+
const element = {
|
|
18
|
+
getBoundingClientRect: () => ({ top, left: 0, right: 300, bottom: top + height, width: 300, height, x: 0, y: top }),
|
|
19
|
+
} as unknown as Element
|
|
20
|
+
return attachClosestEdge(
|
|
21
|
+
{ type: 'ticket', ticketId, columnId },
|
|
22
|
+
// A pointer near the top of the box resolves to 'top', near the bottom to
|
|
23
|
+
// 'bottom' — that is the hitbox's own rule, so the fixture uses it rather
|
|
24
|
+
// than hand-writing the symbol it stores the edge under.
|
|
25
|
+
{ input: { clientX: 150, clientY: edge === 'top' ? top + 5 : top + height - 5 } as never, element, allowedEdges: ['top', 'bottom'] },
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const overLane = (columnId: string) => ({ type: 'column', columnId })
|
|
30
|
+
const dragging = (ticketId: string, columnId: string) => ({ type: 'ticket', ticketId, columnId })
|
|
31
|
+
|
|
32
|
+
describe('resolveBoardDrop', () => {
|
|
33
|
+
it('reports nothing when the card is released outside every target', () => {
|
|
34
|
+
expect(resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: undefined })).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('lands above the card whose top edge is closest', () => {
|
|
38
|
+
const change = resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: overCard('b2', 'done', 'top') })
|
|
39
|
+
expect(change).toMatchObject({ toColumnId: 'done', afterTicketId: 'b1', beforeTicketId: 'b2' })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('lands below the card whose bottom edge is closest', () => {
|
|
43
|
+
const change = resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: overCard('b1', 'done', 'bottom') })
|
|
44
|
+
expect(change).toMatchObject({ toColumnId: 'done', afterTicketId: 'b1', beforeTicketId: 'b2' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('appends when the card is dropped on the lane rather than on a card', () => {
|
|
48
|
+
const change = resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: overLane('done') })
|
|
49
|
+
expect(change).toMatchObject({ toColumnId: 'done', afterTicketId: 'b2', beforeTicketId: null })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('does not count the dragged card as its own neighbour', () => {
|
|
53
|
+
// Moving a1 below a2 within its own lane: if a1 were still in the list, the
|
|
54
|
+
// neighbour would come out as a1 itself and the move would be a no-op.
|
|
55
|
+
const change = resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: overCard('a2', 'open', 'bottom') })
|
|
56
|
+
expect(change).toMatchObject({ afterTicketId: 'a2', beforeTicketId: 'a3' })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('reports nothing when the card is dropped back where it already was', () => {
|
|
60
|
+
// a2 released just below a1 — exactly its current slot.
|
|
61
|
+
expect(resolveBoardDrop(COLUMNS, { source: dragging('a2', 'open'), target: overCard('a1', 'open', 'bottom') })).toBeNull()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('treats a drop on the first card top edge as the head of the lane', () => {
|
|
65
|
+
const change = resolveBoardDrop(COLUMNS, { source: dragging('a1', 'open'), target: overCard('b1', 'done', 'top') })
|
|
66
|
+
expect(change).toMatchObject({ afterTicketId: null, beforeTicketId: 'b1' })
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { type DragSlots, resolveSlot, shiftFor } from '../components/features/sortable-list/slot-geometry'
|
|
3
|
+
|
|
4
|
+
// Four items of different heights, 24px apart — mixed heights on purpose, since
|
|
5
|
+
// that is where "nearest neighbour's centre" and "nearest resting place" differ.
|
|
6
|
+
const HEIGHTS = [50, 70, 90, 60]
|
|
7
|
+
const GAP = 24
|
|
8
|
+
|
|
9
|
+
const TOPS = [0, 74, 168, 282]
|
|
10
|
+
const BOTTOMS = [50, 144, 258, 342]
|
|
11
|
+
|
|
12
|
+
const slotsFor = (from: number): DragSlots => ({
|
|
13
|
+
tops: TOPS,
|
|
14
|
+
bottoms: BOTTOMS,
|
|
15
|
+
height: HEIGHTS[from],
|
|
16
|
+
from,
|
|
17
|
+
step: HEIGHTS[from] + GAP,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The first offset, walking from `start` in `direction`, at which the held slot
|
|
22
|
+
* changes. `start` matters: the return boundary has to be walked back from where
|
|
23
|
+
* the item actually IS once it holds the new slot, not from zero.
|
|
24
|
+
*/
|
|
25
|
+
function boundary(slots: DragSlots, held: number, direction: 1 | -1, start = 0): number {
|
|
26
|
+
for (let offset = start; Math.abs(offset) <= 600; offset += direction) {
|
|
27
|
+
if (resolveSlot(slots, held, offset) !== held) return offset
|
|
28
|
+
}
|
|
29
|
+
throw new Error('no boundary found')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('resolveSlot', () => {
|
|
33
|
+
const slots = slotsFor(0)
|
|
34
|
+
|
|
35
|
+
it('holds its own slot until the item has travelled half a slot', () => {
|
|
36
|
+
expect(resolveSlot(slots, 0, 0)).toBe(0)
|
|
37
|
+
expect(resolveSlot(slots, 0, 40)).toBe(0)
|
|
38
|
+
expect(resolveSlot(slots, 0, 80)).toBe(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('walks a slot at a time rather than jumping to the end', () => {
|
|
42
|
+
expect(resolveSlot(slots, 1, 160)).toBe(1)
|
|
43
|
+
expect(resolveSlot(slots, 1, 200)).toBe(2)
|
|
44
|
+
expect(resolveSlot(slots, 2, 300)).toBe(3)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// The complaint this exists to answer: without the handicap the item ends up
|
|
48
|
+
// sitting exactly ON the boundary it just crossed, so leaving a slot costs
|
|
49
|
+
// half an item of dragging and coming back costs a pixel.
|
|
50
|
+
it('costs the same movement to leave a slot and to come back', () => {
|
|
51
|
+
const out = boundary(slots, 0, 1)
|
|
52
|
+
const back = boundary(slots, 1, -1, out)
|
|
53
|
+
expect(out).toBeGreaterThan(back)
|
|
54
|
+
|
|
55
|
+
// Both sit the same distance either side of the midpoint between the two
|
|
56
|
+
// resting places — 25 and 119 here, so 72, i.e. an offset of 47.
|
|
57
|
+
const midpoint = 47
|
|
58
|
+
expect(out - midpoint).toBeCloseTo(midpoint - back, 0)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('measures against resting places, not the neighbours own centres', () => {
|
|
62
|
+
// Landing at index 1 puts a 50px item against item 1's BOTTOM edge (144),
|
|
63
|
+
// i.e. centred at 119 — not at item 1's own centre of 109.
|
|
64
|
+
expect(resolveSlot(slots, 0, 94)).toBe(1)
|
|
65
|
+
expect(resolveSlot(slots, 1, 94)).toBe(1)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('is symmetric for a drag that starts at the end of the list', () => {
|
|
69
|
+
const fromLast = slotsFor(3)
|
|
70
|
+
expect(resolveSlot(fromLast, 3, 0)).toBe(3)
|
|
71
|
+
expect(resolveSlot(fromLast, 3, -120)).toBe(2)
|
|
72
|
+
expect(resolveSlot(fromLast, 2, -10)).toBe(3)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('shiftFor', () => {
|
|
77
|
+
it('stands the passed items aside by exactly one slot', () => {
|
|
78
|
+
const slots = slotsFor(0)
|
|
79
|
+
expect(shiftFor(slots, 0, 2)).toBe(0)
|
|
80
|
+
expect(shiftFor(slots, 1, 2)).toBe(-slots.step)
|
|
81
|
+
expect(shiftFor(slots, 2, 2)).toBe(-slots.step)
|
|
82
|
+
expect(shiftFor(slots, 3, 2)).toBe(0)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('pushes the other way when the item is dragged upwards', () => {
|
|
86
|
+
const slots = slotsFor(3)
|
|
87
|
+
expect(shiftFor(slots, 3, 1)).toBe(0)
|
|
88
|
+
expect(shiftFor(slots, 2, 1)).toBe(slots.step)
|
|
89
|
+
expect(shiftFor(slots, 1, 1)).toBe(slots.step)
|
|
90
|
+
expect(shiftFor(slots, 0, 1)).toBe(0)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('moves nothing while the drop is still aimed at the slot it came from', () => {
|
|
94
|
+
const slots = slotsFor(1)
|
|
95
|
+
expect([0, 1, 2, 3].map(index => shiftFor(slots, index, 1))).toEqual([0, 0, 0, 0])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Every displaced item travels the same distance whatever its own height:
|
|
99
|
+
// removing the item from one slot and inserting it at another moves each item
|
|
100
|
+
// in between by exactly the dragged item's height plus the gap.
|
|
101
|
+
it('travels the dragged items height regardless of the displaced items own', () => {
|
|
102
|
+
const slots = slotsFor(0)
|
|
103
|
+
expect(new Set([1, 2, 3].map(index => shiftFor(slots, index, 3))).size).toBe(1)
|
|
104
|
+
})
|
|
105
|
+
})
|