-
+
diff --git a/src/views/components/calendar/CalendarSchedules.tsx b/src/views/components/calendar/CalendarSchedules.tsx
index 6a5052423..cce0c2c37 100644
--- a/src/views/components/calendar/CalendarSchedules.tsx
+++ b/src/views/components/calendar/CalendarSchedules.tsx
@@ -5,7 +5,7 @@ import List from '@views/components/common/List';
import ScheduleListItem from '@views/components/common/ScheduleListItem';
import Text from '@views/components/common/Text/Text';
import useSchedules, { getActiveSchedule, switchSchedule } from '@views/hooks/useSchedules';
-import React from 'react';
+import React, { useRef } from 'react';
import AddSchedule from '~icons/material-symbols/add';
@@ -20,6 +20,7 @@ import { usePrompt } from '../common/DialogProvider/DialogProvider';
export function CalendarSchedules() {
const [, schedules] = useSchedules();
const showDialog = usePrompt();
+ const containerRef = useRef
(null);
const handleAddSchedule = () => {
if (schedules.length >= 10) {
@@ -48,7 +49,7 @@ export function CalendarSchedules() {
};
return (
-
+
MY SCHEDULES
@@ -62,6 +63,7 @@ export function CalendarSchedules() {
gap={10}
draggables={schedules}
itemKey={s => s.id}
+ boundingRef={containerRef}
onReordered={reordered => {
const activeSchedule = getActiveSchedule();
const activeIndex = reordered.findIndex(s => s.id === activeSchedule.id);
diff --git a/src/views/components/common/List.tsx b/src/views/components/common/List.tsx
index 78c83bb3f..8372e23df 100644
--- a/src/views/components/common/List.tsx
+++ b/src/views/components/common/List.tsx
@@ -1,6 +1,11 @@
-import type { DraggableProvided, DraggableProvidedDragHandleProps, OnDragEndResponder } from '@hello-pangea/dnd';
+import type {
+ DraggableProvided,
+ DraggableProvidedDragHandleProps,
+ OnDragEndResponder,
+ OnDragStartResponder,
+} from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
-import type { ReactElement } from 'react';
+import type { ReactElement, RefObject } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import ExtensionRoot from './ExtensionRoot/ExtensionRoot';
@@ -18,6 +23,7 @@ export interface ListProps {
onReordered: (elements: T[]) => void;
itemKey: (item: T) => React.Key;
gap?: number; // Impacts the spacing between items in the list
+ boundingRef?: RefObject; // Element that clamps the y-value for draggable items
}
function wrap(draggableElements: T[], keyTransform: ListProps['itemKey']) {
@@ -72,51 +78,74 @@ function Item(props: {
* @example
*
*/
-function List({ draggables, itemKey, children, onReordered, gap }: ListProps): JSX.Element {
+function List({ draggables, itemKey, children, onReordered, gap, boundingRef }: ListProps): JSX.Element {
const [items, setItems] = useState(wrap(draggables, itemKey));
-
+ const [activeItem, setActiveItem] = useState(null);
+ const boundingEl = boundingRef?.current;
const transformFunction = children;
useEffect(() => {
setItems(wrap(draggables, itemKey));
-
// This is on purpose
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [draggables]);
+ useEffect(() => {
+ if (!activeItem || !boundingEl) return;
+
+ const handleMouseMove = (e: MouseEvent) => {
+ if (!boundingEl) return;
+
+ const bounds = boundingEl.getBoundingClientRect();
+ const isOutsideBounds = e.clientY < bounds.top || e.clientY > bounds.bottom;
+
+ if (isOutsideBounds) {
+ // force end of drag
+ window.dispatchEvent(
+ new KeyboardEvent('keydown', {
+ key: 'Escape',
+ keyCode: 27,
+ which: 27,
+ bubbles: true,
+ })
+ );
+ }
+ };
+
+ document.addEventListener('mousemove', handleMouseMove);
+ return () => document.removeEventListener('mousemove', handleMouseMove);
+ }, [activeItem, boundingEl]);
+
const onDragEnd: OnDragEndResponder = useCallback(
({ destination, source }) => {
+ setActiveItem(null);
+
if (!destination) return;
if (source.index === destination.index) return;
// will reorder in place
const reordered = reorder(items, source.index, destination.index);
-
setItems(reordered);
onReordered(reordered.map(item => item.content));
},
-
// This is on purpose
// eslint-disable-next-line react-hooks/exhaustive-deps
[items]
);
+ const onDragStart: OnDragStartResponder = useCallback(() => {
+ const el = document.querySelector('.is-dragging');
+ setActiveItem(el);
+ }, []);
+
return (
-
+
{
- let { style } = provided.draggableProps;
- const transform = style?.transform;
-
- if (snapshot.isDragging && transform) {
- let [, , y] = transform.match(/translate\(([-\d]+)px, ([-\d]+)px\)/) || [];
-
- style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
- }
-
+ const { style } = provided.draggableProps;
return (
- ({ draggables, itemKey, children, onReordered, gap }: ListProps<
{...draggableProps}
style={{
...draggableProps.style,
- // if last item, don't add margin
marginBottom: `${gap}px`,
}}
>