@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.
Files changed (187) hide show
  1. package/dist/{chunk-6ER3AYJM.js → chunk-25YLMIWU.js} +3 -3
  2. package/dist/{chunk-HO3Q7NXJ.js → chunk-26HAJBSQ.js} +2 -2
  3. package/dist/{chunk-P22HM2MO.cjs → chunk-33FOBAWS.cjs} +18 -18
  4. package/dist/{chunk-P22HM2MO.cjs.map → chunk-33FOBAWS.cjs.map} +1 -1
  5. package/dist/{chunk-N5A4AC2J.cjs → chunk-4EGNYOZA.cjs} +23 -23
  6. package/dist/{chunk-N5A4AC2J.cjs.map → chunk-4EGNYOZA.cjs.map} +1 -1
  7. package/dist/{chunk-A7ES3NZK.cjs → chunk-4WREPWRL.cjs} +3 -3
  8. package/dist/{chunk-A7ES3NZK.cjs.map → chunk-4WREPWRL.cjs.map} +1 -1
  9. package/dist/{chunk-CU6Q2KQT.cjs → chunk-5HNZTPAB.cjs} +12 -12
  10. package/dist/{chunk-CU6Q2KQT.cjs.map → chunk-5HNZTPAB.cjs.map} +1 -1
  11. package/dist/{chunk-MLVF3OXU.cjs → chunk-5JF6H7OJ.cjs} +76 -76
  12. package/dist/{chunk-MLVF3OXU.cjs.map → chunk-5JF6H7OJ.cjs.map} +1 -1
  13. package/dist/{chunk-UXW63WCV.cjs → chunk-64HHDGRN.cjs} +4 -4
  14. package/dist/{chunk-UXW63WCV.cjs.map → chunk-64HHDGRN.cjs.map} +1 -1
  15. package/dist/{chunk-KXLQITOX.cjs → chunk-6YXEZCVG.cjs} +5 -5
  16. package/dist/{chunk-KXLQITOX.cjs.map → chunk-6YXEZCVG.cjs.map} +1 -1
  17. package/dist/{chunk-SST2YI7S.cjs → chunk-6ZOVSD4M.cjs} +14 -14
  18. package/dist/{chunk-SST2YI7S.cjs.map → chunk-6ZOVSD4M.cjs.map} +1 -1
  19. package/dist/{chunk-M5TWHFIN.cjs → chunk-A6TAVIBX.cjs} +42 -42
  20. package/dist/{chunk-M5TWHFIN.cjs.map → chunk-A6TAVIBX.cjs.map} +1 -1
  21. package/dist/{chunk-J76LCSR4.cjs → chunk-BE6TRUKY.cjs} +40 -40
  22. package/dist/{chunk-J76LCSR4.cjs.map → chunk-BE6TRUKY.cjs.map} +1 -1
  23. package/dist/{chunk-Q6RC3YGW.cjs → chunk-DFJ7TWH6.cjs} +3 -3
  24. package/dist/{chunk-Q6RC3YGW.cjs.map → chunk-DFJ7TWH6.cjs.map} +1 -1
  25. package/dist/{chunk-B2GEKAAA.js → chunk-DRNSIK3V.js} +2 -2
  26. package/dist/{chunk-CTTY5EKH.js → chunk-FXVMRZIQ.js} +5 -5
  27. package/dist/{chunk-QZ64JOE2.js → chunk-G3NBTL4I.js} +3246 -2557
  28. package/dist/chunk-G3NBTL4I.js.map +1 -0
  29. package/dist/{chunk-TXV444YV.js → chunk-G7ROZAY5.js} +3 -3
  30. package/dist/{chunk-E56VNTBV.cjs → chunk-IPO24G7U.cjs} +1775 -1086
  31. package/dist/chunk-IPO24G7U.cjs.map +1 -0
  32. package/dist/{chunk-CVCQW7WG.js → chunk-LJWTXODF.js} +3 -3
  33. package/dist/{chunk-P7MSXPGL.js → chunk-NNGQ6ONX.js} +8 -8
  34. package/dist/{chunk-XOKRFZBD.cjs → chunk-QCBBX6DO.cjs} +97 -97
  35. package/dist/{chunk-XOKRFZBD.cjs.map → chunk-QCBBX6DO.cjs.map} +1 -1
  36. package/dist/{chunk-GSONSIWG.js → chunk-R7A7WHXP.js} +60 -35
  37. package/dist/chunk-R7A7WHXP.js.map +1 -0
  38. package/dist/{chunk-L36R3G7Z.js → chunk-RPCY2FAC.js} +6 -6
  39. package/dist/{chunk-QMQQ37RW.js → chunk-RRWWAH37.js} +2 -2
  40. package/dist/{chunk-ODRMPI3Y.js → chunk-SBKX2VSG.js} +2 -2
  41. package/dist/{chunk-KQKU4VXB.cjs → chunk-STHVHAV4.cjs} +82 -57
  42. package/dist/chunk-STHVHAV4.cjs.map +1 -0
  43. package/dist/{chunk-E2IPBWGT.js → chunk-UTEQHK3H.js} +6 -6
  44. package/dist/{chunk-6QRKCVYR.cjs → chunk-V5EYGEX7.cjs} +7 -7
  45. package/dist/{chunk-6QRKCVYR.cjs.map → chunk-V5EYGEX7.cjs.map} +1 -1
  46. package/dist/{chunk-K432ES7N.js → chunk-VJL46NHW.js} +4 -4
  47. package/dist/{chunk-QTO3WYB2.cjs → chunk-VJTD2DGF.cjs} +3 -3
  48. package/dist/{chunk-QTO3WYB2.cjs.map → chunk-VJTD2DGF.cjs.map} +1 -1
  49. package/dist/{chunk-VGLDA77S.js → chunk-VRX6Y6A7.js} +2 -2
  50. package/dist/{chunk-OR67WC32.js → chunk-WBQ3NOH7.js} +2 -2
  51. package/dist/{chunk-UL3WQWTF.js → chunk-Y6TXTBIQ.js} +2 -2
  52. package/dist/{chunk-YAOBDOI5.cjs → chunk-YHRE4JBF.cjs} +32 -32
  53. package/dist/{chunk-YAOBDOI5.cjs.map → chunk-YHRE4JBF.cjs.map} +1 -1
  54. package/dist/components/case-studies/index.cjs +10 -10
  55. package/dist/components/case-studies/index.js +4 -4
  56. package/dist/components/chat/index.cjs +4 -4
  57. package/dist/components/chat/index.js +3 -3
  58. package/dist/components/contact/index.cjs +5 -5
  59. package/dist/components/contact/index.js +4 -4
  60. package/dist/components/docs/index.cjs +7 -7
  61. package/dist/components/docs/index.js +6 -6
  62. package/dist/components/embeds/index.cjs +5 -5
  63. package/dist/components/embeds/index.js +4 -4
  64. package/dist/components/faq/index.cjs +6 -6
  65. package/dist/components/faq/index.js +5 -5
  66. package/dist/components/features/board/aim.d.ts +57 -0
  67. package/dist/components/features/board/aim.d.ts.map +1 -0
  68. package/dist/components/features/board/board-column-header.d.ts.map +1 -1
  69. package/dist/components/features/board/board-column.d.ts +8 -4
  70. package/dist/components/features/board/board-column.d.ts.map +1 -1
  71. package/dist/components/features/board/board.d.ts +15 -4
  72. package/dist/components/features/board/board.d.ts.map +1 -1
  73. package/dist/components/features/board/drop-aim.d.ts +34 -0
  74. package/dist/components/features/board/drop-aim.d.ts.map +1 -0
  75. package/dist/components/features/board/lane-geometry.d.ts +22 -0
  76. package/dist/components/features/board/lane-geometry.d.ts.map +1 -0
  77. package/dist/components/features/board/pending-move.d.ts +77 -0
  78. package/dist/components/features/board/pending-move.d.ts.map +1 -0
  79. package/dist/components/features/board/resolve-drop.d.ts +19 -0
  80. package/dist/components/features/board/resolve-drop.d.ts.map +1 -0
  81. package/dist/components/features/board/ticket-card.d.ts +25 -9
  82. package/dist/components/features/board/ticket-card.d.ts.map +1 -1
  83. package/dist/components/features/board/use-board-collapse.d.ts.map +1 -1
  84. package/dist/components/features/board/use-lane-scroll-anchor.d.ts +27 -0
  85. package/dist/components/features/board/use-lane-scroll-anchor.d.ts.map +1 -0
  86. package/dist/components/features/index.cjs +10 -4
  87. package/dist/components/features/index.cjs.map +1 -1
  88. package/dist/components/features/index.d.ts +1 -0
  89. package/dist/components/features/index.d.ts.map +1 -1
  90. package/dist/components/features/index.js +9 -3
  91. package/dist/components/features/sortable-list/index.d.ts +4 -0
  92. package/dist/components/features/sortable-list/index.d.ts.map +1 -0
  93. package/dist/components/features/sortable-list/slot-geometry.d.ts +57 -0
  94. package/dist/components/features/sortable-list/slot-geometry.d.ts.map +1 -0
  95. package/dist/components/features/sortable-list/sortable-list.d.ts +50 -0
  96. package/dist/components/features/sortable-list/sortable-list.d.ts.map +1 -0
  97. package/dist/components/features/sortable-list/use-sortable-item.d.ts +29 -0
  98. package/dist/components/features/sortable-list/use-sortable-item.d.ts.map +1 -0
  99. package/dist/components/help-center-pages/index.cjs +25 -25
  100. package/dist/components/help-center-pages/index.js +16 -16
  101. package/dist/components/index.cjs +144 -138
  102. package/dist/components/index.cjs.map +1 -1
  103. package/dist/components/index.js +20 -14
  104. package/dist/components/index.js.map +1 -1
  105. package/dist/components/meeting-scheduler/index.cjs +35 -35
  106. package/dist/components/meeting-scheduler/index.js +5 -5
  107. package/dist/components/navigation/index.cjs +4 -4
  108. package/dist/components/navigation/index.js +3 -3
  109. package/dist/components/onboarding-guides/index.cjs +8 -8
  110. package/dist/components/onboarding-guides/index.js +7 -7
  111. package/dist/components/related-content/index.cjs +6 -6
  112. package/dist/components/related-content/index.js +5 -5
  113. package/dist/components/tickets/index.cjs +8 -8
  114. package/dist/components/tickets/index.js +7 -7
  115. package/dist/components/ui/index.cjs +4 -4
  116. package/dist/components/ui/index.js +3 -3
  117. package/dist/components/ui/square-avatar.d.ts.map +1 -1
  118. package/dist/hooks/index.cjs +3 -3
  119. package/dist/hooks/index.js +2 -2
  120. package/dist/index.cjs +10 -4
  121. package/dist/index.cjs.map +1 -1
  122. package/dist/index.js +9 -3
  123. package/dist/nats/index.cjs +2 -2
  124. package/dist/nats/index.js +1 -1
  125. package/dist/nats/nats.d.ts.map +1 -1
  126. package/dist/nats/shared-connection.d.ts +13 -6
  127. package/dist/nats/shared-connection.d.ts.map +1 -1
  128. package/dist/utils/auto-scroll-ancestors.d.ts +43 -0
  129. package/dist/utils/auto-scroll-ancestors.d.ts.map +1 -0
  130. package/dist/utils/drag-effect.d.ts +21 -0
  131. package/dist/utils/drag-effect.d.ts.map +1 -0
  132. package/dist/utils/scroll-parent.d.ts +11 -0
  133. package/dist/utils/scroll-parent.d.ts.map +1 -0
  134. package/package.json +6 -3
  135. package/src/__tests__/board-aim.test.ts +152 -0
  136. package/src/__tests__/board-pending-move.test.ts +113 -0
  137. package/src/__tests__/board-resolve-drop.test.ts +68 -0
  138. package/src/__tests__/sortable-slot-geometry.test.ts +105 -0
  139. package/src/components/features/board/.board-column.md +17 -4
  140. package/src/components/features/board/.board.md +11 -7
  141. package/src/components/features/board/.ticket-card.md +27 -5
  142. package/src/components/features/board/aim.ts +131 -0
  143. package/src/components/features/board/board-column-header.tsx +2 -9
  144. package/src/components/features/board/board-column.tsx +137 -66
  145. package/src/components/features/board/board.tsx +501 -258
  146. package/src/components/features/board/drop-aim.ts +50 -0
  147. package/src/components/features/board/lane-geometry.ts +44 -0
  148. package/src/components/features/board/pending-move.ts +116 -0
  149. package/src/components/features/board/resolve-drop.ts +28 -0
  150. package/src/components/features/board/ticket-card.tsx +256 -47
  151. package/src/components/features/board/use-board-collapse.ts +16 -14
  152. package/src/components/features/board/use-lane-scroll-anchor.ts +79 -0
  153. package/src/components/features/index.ts +1 -0
  154. package/src/components/features/sortable-list/.sortable-list.md +82 -0
  155. package/src/components/features/sortable-list/index.ts +3 -0
  156. package/src/components/features/sortable-list/slot-geometry.ts +100 -0
  157. package/src/components/features/sortable-list/sortable-list.tsx +270 -0
  158. package/src/components/features/sortable-list/use-sortable-item.ts +92 -0
  159. package/src/components/ui/square-avatar.tsx +5 -0
  160. package/src/nats/__tests__/reconnect-lifecycle.test.tsx +814 -0
  161. package/src/nats/nats.ts +56 -13
  162. package/src/nats/shared-connection.ts +132 -36
  163. package/src/stories/.TicketCard.stories.md +6 -12
  164. package/src/stories/Board.stories.tsx +16 -7
  165. package/src/stories/TicketCard.stories.tsx +38 -76
  166. package/src/utils/auto-scroll-ancestors.ts +90 -0
  167. package/src/utils/drag-effect.ts +26 -0
  168. package/src/utils/scroll-parent.ts +23 -0
  169. package/dist/chunk-E56VNTBV.cjs.map +0 -1
  170. package/dist/chunk-GSONSIWG.js.map +0 -1
  171. package/dist/chunk-KQKU4VXB.cjs.map +0 -1
  172. package/dist/chunk-QZ64JOE2.js.map +0 -1
  173. /package/dist/{chunk-6ER3AYJM.js.map → chunk-25YLMIWU.js.map} +0 -0
  174. /package/dist/{chunk-HO3Q7NXJ.js.map → chunk-26HAJBSQ.js.map} +0 -0
  175. /package/dist/{chunk-B2GEKAAA.js.map → chunk-DRNSIK3V.js.map} +0 -0
  176. /package/dist/{chunk-CTTY5EKH.js.map → chunk-FXVMRZIQ.js.map} +0 -0
  177. /package/dist/{chunk-TXV444YV.js.map → chunk-G7ROZAY5.js.map} +0 -0
  178. /package/dist/{chunk-CVCQW7WG.js.map → chunk-LJWTXODF.js.map} +0 -0
  179. /package/dist/{chunk-P7MSXPGL.js.map → chunk-NNGQ6ONX.js.map} +0 -0
  180. /package/dist/{chunk-L36R3G7Z.js.map → chunk-RPCY2FAC.js.map} +0 -0
  181. /package/dist/{chunk-QMQQ37RW.js.map → chunk-RRWWAH37.js.map} +0 -0
  182. /package/dist/{chunk-ODRMPI3Y.js.map → chunk-SBKX2VSG.js.map} +0 -0
  183. /package/dist/{chunk-E2IPBWGT.js.map → chunk-UTEQHK3H.js.map} +0 -0
  184. /package/dist/{chunk-K432ES7N.js.map → chunk-VJL46NHW.js.map} +0 -0
  185. /package/dist/{chunk-VGLDA77S.js.map → chunk-VRX6Y6A7.js.map} +0 -0
  186. /package/dist/{chunk-OR67WC32.js.map → chunk-WBQ3NOH7.js.map} +0 -0
  187. /package/dist/{chunk-UL3WQWTF.js.map → chunk-Y6TXTBIQ.js.map} +0 -0
@@ -12,7 +12,7 @@ import {
12
12
  useNatsJsonSubscription,
13
13
  useNatsSubscription,
14
14
  useOptionalNats
15
- } from "../chunk-GSONSIWG.js";
15
+ } from "../chunk-R7A7WHXP.js";
16
16
  import "../chunk-GGWZFCYS.js";
17
17
  export {
18
18
  NATS_DEFAULTS,
@@ -1 +1 @@
1
- {"version":3,"file":"nats.d.ts","sourceRoot":"","sources":["../../src/nats/nats.ts"],"names":[],"mappings":"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"}
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
- * Identity of the consumer driving reconnect. When set, other consumers
32
- * should observe status only and skip their own scheduleRetry — otherwise
33
- * each disconnect triggers multiple concurrent connect() calls racing over
34
- * connectPromise.
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: object | null;
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,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpC,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,MAAM,GAAG,IAAI,CAAA;CAC1B;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;AAWD,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;AAID,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,0BAA0B,GAAG,yBAAyB,CAuHvG"}
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.555",
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
+ })