@@ -3,7 +3,7 @@ import type { Interactable } from "@interactjs/types";
33import { toPng } from " html-to-image" ;
44import interact from " interactjs" ;
55import Simplebar from " simplebar-core" ;
6- import { onMounted , onUnmounted , ref , useTemplateRef } from " vue" ;
6+ import { onBeforeUnmount , onMounted , ref , useTemplateRef } from " vue" ;
77
88import DynamicContentRasterizer from " @/components/DynamicContentRasterizer.vue" ;
99
@@ -13,6 +13,7 @@ interface Item {
1313}
1414
1515const items = defineModel <Item []>(" items" , { required: true });
16+ const currentDropPosition = defineModel <number >(" currentDropPosition" );
1617const props = defineProps <{
1718 autoScrollContainerSelector? : string ;
1819}>();
@@ -22,7 +23,7 @@ const movingItemAsPngData = ref("");
2223const movingItemHeight = ref (0 );
2324const movingItemY = ref (0 );
2425const container = useTemplateRef (" container" );
25- let interactable : Interactable ;
26+ let draggable : Interactable ;
2627let direction: " up" | " down" | " none" = " none" ;
2728let autoScrollContainer: HTMLElement = document .body ;
2829
@@ -64,21 +65,55 @@ function capturedStyles() {
6465 };
6566}
6667
67- function makeModifier() {
68- const containerBounds = container .value ! .getBoundingClientRect ();
69-
70- return interact .modifiers .restrict ({
71- restriction : (x , y , { element }) => {
72- const content = element ?.parentElement ?.parentElement ?.querySelector (" .content" );
73- const { height } = content ?.getBoundingClientRect () ?? { height: 0 };
74- return {
75- top: containerBounds ?.top - height ,
76- right: containerBounds ?.right ,
77- bottom: containerBounds ?.bottom + height ,
78- left: containerBounds ?.left ,
79- };
80- },
68+ function setDropIndicatorPosition(y : number ) {
69+ const itemBounds = Array .from (container .value ! .querySelectorAll (" .item" )).map ((item , index ) => {
70+ const { top, height } = item .getBoundingClientRect ();
71+ const center = top + height / 2 ;
72+ return {
73+ index ,
74+ distance: Math .abs (y - center ),
75+ center ,
76+ };
8177 });
78+ const closestItemBelow = itemBounds .reduce ((closest , item ) => {
79+ if (item .distance < closest .distance ) {
80+ return item ;
81+ }
82+ return closest ;
83+ }, itemBounds [0 ]);
84+
85+ if (y > closestItemBelow .center ) {
86+ dropIndicatorPosition .value = closestItemBelow .index ;
87+ } else {
88+ dropIndicatorPosition .value = closestItemBelow .index - 1 ;
89+ }
90+ }
91+
92+ function onDragOver(event : DragEvent ) {
93+ // scroll the container if needed
94+ const scrollBounds = autoScrollContainer .getBoundingClientRect ();
95+ const distanceToTop = Math .abs (event .pageY - scrollBounds .top );
96+ const distanceToBottom = Math .abs (event .pageY - scrollBounds .bottom );
97+ const threshold = 150 ;
98+ const speed = 5 ;
99+ if (distanceToTop < threshold ) {
100+ autoScrollContainer .scrollTop -= speed ;
101+ } else if (distanceToBottom < threshold ) {
102+ const maxScroll = autoScrollContainer .scrollHeight - scrollBounds .height ;
103+ autoScrollContainer .scrollTop = Math .min (maxScroll , autoScrollContainer .scrollTop + speed );
104+ }
105+
106+ // show drop indicator to the closest item
107+ setDropIndicatorPosition (event .pageY );
108+
109+ if (dropIndicatorPosition .value !== null ) {
110+ currentDropPosition .value = dropIndicatorPosition .value + 1 ;
111+ }
112+ }
113+
114+ function onDragLeave() {
115+ currentDropPosition .value = - 1 ;
116+ dropIndicatorPosition .value = null ;
82117}
83118
84119onMounted (() => {
@@ -101,14 +136,14 @@ onMounted(() => {
101136 autoScrollContainer = container .value ! .parentElement ?? document .body ;
102137 }
103138
104- interactable = interact (" .handle" ).draggable ({
139+ draggable = interact (" .handle" ).draggable ({
105140 autoScroll: {
106141 enabled: true ,
107142 container: autoScrollContainer ,
143+ speed: 900 ,
108144 },
109145 startAxis: " y" ,
110146 lockAxis: " y" ,
111- modifiers: [makeModifier ()],
112147 listeners: {
113148 async start(event ) {
114149 // make a rasterized copy of the moving element
@@ -134,40 +169,13 @@ onMounted(() => {
134169 event .clientY + autoScrollContainer ! .scrollTop - paddingTop - containerY ;
135170
136171 // set the drop indicator item index
137- const itemBounds = Array .from (container .value ! .querySelectorAll (" .item" )).map (
138- (item , index ) => {
139- const { top, height } = item .getBoundingClientRect ();
140- const center = top + height / 2 ;
141- return {
142- index ,
143- distance: Math .abs (event .pageY - center ),
144- };
145- }
146- );
147- const closestItemBelow = itemBounds .reduce ((closest , item ) => {
148- if (item .distance < closest .distance ) {
149- return item ;
150- }
151- return closest ;
152- }, itemBounds [0 ]);
153- // if the first item is the closest we may need to move the drop indicator up
154- if (closestItemBelow .index === 0 ) {
155- // does the user want to move the item to the top?
156- const bounds = container .value ! .getBoundingClientRect ();
157- if (event .pageY < bounds .top ) {
158- dropIndicatorPosition .value = - 1 ;
159- } else {
160- dropIndicatorPosition .value = 0 ;
161- }
162- } else {
163- dropIndicatorPosition .value = closestItemBelow .index ;
164- }
172+ setDropIndicatorPosition (event .pageY );
165173 },
166174 end() {
167175 // change the model order
168176 if (items .value && movingItemIndex .value !== null && dropIndicatorPosition .value !== null ) {
169177 // did user dropped the item in its previous position ?
170- if (Math .abs (dropIndicatorPosition .value - movingItemIndex .value ) > 1 ) {
178+ if (Math .abs (dropIndicatorPosition .value - movingItemIndex .value ) >= 1 ) {
171179 // move the item to its new position
172180 const destinationIndex =
173181 dropIndicatorPosition .value > movingItemIndex .value
@@ -186,10 +194,17 @@ onMounted(() => {
186194 },
187195 },
188196 });
197+
198+ container .value ! .addEventListener (" dragover" , onDragOver );
199+ container .value ! .addEventListener (" dragleave" , onDragLeave );
200+ window .addEventListener (" dragend" , onDragLeave );
189201});
190202
191- onUnmounted (() => {
192- interactable .unset ();
203+ onBeforeUnmount (() => {
204+ container .value ! .removeEventListener (" dragover" , onDragOver );
205+ container .value ! .removeEventListener (" dragleave" , onDragLeave );
206+ window .removeEventListener (" dragend" , onDragLeave );
207+ draggable .unset ();
193208});
194209 </script >
195210
@@ -236,7 +251,7 @@ onUnmounted(() => {
236251}
237252
238253.draggable {
239- --content-left-margin : 15 px ;
254+ --content-left-margin : 18 px ;
240255
241256 position : relative ;
242257 display : flex ;
@@ -249,18 +264,23 @@ onUnmounted(() => {
249264
250265 & .handle {
251266 position : absolute ;
252- top : 0 ;
267+ top : -4 px ;
253268 left : 0 ;
269+ display : flex ;
270+ color : hsl (from var (--color-primary ) h s calc (l * 1.5 ));
254271 cursor : move ;
272+ font-size : calc (var (--text-size-normal ) * 1.7 );
255273 opacity : 0 ;
256274 transition : opacity var (--transition-duration ) var (--transition-easing );
257275 }
258276
259277 & .content-wrapper {
278+ width : calc (100% - var (--content-left-margin ));
260279 margin-left : var (--content-left-margin );
261280 }
262281
263282 & .content {
283+ width : 100% ;
264284 transition : opacity var (--transition-duration ) var (--transition-easing );
265285
266286 &.moving {
0 commit comments