mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
chore: drag to reorder release plan template milestones (#9176)
https://linear.app/unleash/issue/2-2821/drag-to-reorder-template-milestones This PR introduces reordering release plan template milestones by dragging and dropping them. Was a bit undecided on the approach, but it seems like using an old `useDragItem` hook we have is pretty elegant and behaves as expected. I suggest reviewers try it out themselves. Includes a slight refactor to `useDragItem`, which so far is only used here and in environments. I manually tested, but I suggest trying that one out as well just in case. 
This commit is contained in:
parent
ec014c0fdf
commit
25e8f80f21
@ -1,4 +1,4 @@
|
|||||||
import { type MoveListItem, useDragItem } from 'hooks/useDragItem';
|
import { type OnMoveItem, useDragItem } from 'hooks/useDragItem';
|
||||||
import type { Row } from 'react-table';
|
import type { Row } from 'react-table';
|
||||||
import { styled, TableRow } from '@mui/material';
|
import { styled, TableRow } from '@mui/material';
|
||||||
import { TableCell } from 'component/common/Table';
|
import { TableCell } from 'component/common/Table';
|
||||||
@ -18,10 +18,10 @@ const StyledTableRow = styled(TableRow)(() => ({
|
|||||||
|
|
||||||
interface IEnvironmentRowProps {
|
interface IEnvironmentRowProps {
|
||||||
row: Row;
|
row: Row;
|
||||||
moveListItem: MoveListItem;
|
onMoveItem: OnMoveItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EnvironmentRow = ({ row, moveListItem }: IEnvironmentRowProps) => {
|
export const EnvironmentRow = ({ row, onMoveItem }: IEnvironmentRowProps) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const dragHandleRef = useRef(null);
|
const dragHandleRef = useRef(null);
|
||||||
const { searchQuery } = useSearchHighlightContext();
|
const { searchQuery } = useSearchHighlightContext();
|
||||||
@ -29,15 +29,17 @@ export const EnvironmentRow = ({ row, moveListItem }: IEnvironmentRowProps) => {
|
|||||||
|
|
||||||
const dragItemRef = useDragItem<HTMLTableRowElement>(
|
const dragItemRef = useDragItem<HTMLTableRowElement>(
|
||||||
row.index,
|
row.index,
|
||||||
moveListItem,
|
onMoveItem,
|
||||||
dragHandleRef,
|
dragHandleRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCell = (cell: any, ref: ForwardedRef<HTMLElement>) => {
|
const renderCell = (cell: any, ref: ForwardedRef<HTMLElement>) => {
|
||||||
|
const { key, ...cellProps } = cell.getCellProps();
|
||||||
if (draggable && cell.column.isDragHandle) {
|
if (draggable && cell.column.isDragHandle) {
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
{...cell.getCellProps()}
|
key={key}
|
||||||
|
{...cellProps}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className='drag-handle'
|
className='drag-handle'
|
||||||
>
|
>
|
||||||
@ -46,7 +48,7 @@ export const EnvironmentRow = ({ row, moveListItem }: IEnvironmentRowProps) => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<TableCell {...cell.getCellProps()}>
|
<TableCell key={key} {...cellProps}>
|
||||||
{cell.render('Cell')}
|
{cell.render('Cell')}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import { Alert, styled, TableBody } from '@mui/material';
|
import { Alert, styled, TableBody } from '@mui/material';
|
||||||
import type { MoveListItem } from 'hooks/useDragItem';
|
import type { OnMoveItem } from 'hooks/useDragItem';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useEnvironmentApi, {
|
import useEnvironmentApi, {
|
||||||
createSortOrderPayload,
|
createSortOrderPayload,
|
||||||
@ -38,17 +38,20 @@ export const EnvironmentTable = () => {
|
|||||||
const { environments, mutateEnvironments } = useEnvironments();
|
const { environments, mutateEnvironments } = useEnvironments();
|
||||||
const isFeatureEnabled = useUiFlag('EEA');
|
const isFeatureEnabled = useUiFlag('EEA');
|
||||||
|
|
||||||
const moveListItem: MoveListItem = useCallback(
|
const onMoveItem: OnMoveItem = useCallback(
|
||||||
async (dragIndex: number, dropIndex: number, save = false) => {
|
async (dragIndex: number, dropIndex: number, save = false) => {
|
||||||
const copy = [...environments];
|
const oldEnvironments = environments || [];
|
||||||
const tmp = copy[dragIndex];
|
const newEnvironments = [...oldEnvironments];
|
||||||
copy.splice(dragIndex, 1);
|
const movedEnvironment = newEnvironments.splice(dragIndex, 1)[0];
|
||||||
copy.splice(dropIndex, 0, tmp);
|
newEnvironments.splice(dropIndex, 0, movedEnvironment);
|
||||||
await mutateEnvironments(copy);
|
|
||||||
|
await mutateEnvironments(newEnvironments);
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
try {
|
try {
|
||||||
await changeSortOrder(createSortOrderPayload(copy));
|
await changeSortOrder(
|
||||||
|
createSortOrderPayload(newEnvironments),
|
||||||
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
@ -136,7 +139,7 @@ export const EnvironmentTable = () => {
|
|||||||
return (
|
return (
|
||||||
<EnvironmentRow
|
<EnvironmentRow
|
||||||
row={row as any}
|
row={row as any}
|
||||||
moveListItem={moveListItem}
|
onMoveItem={onMoveItem}
|
||||||
key={row.original.name}
|
key={row.original.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -9,19 +9,22 @@ import {
|
|||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
FormHelperText,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Delete from '@mui/icons-material/DeleteOutlined';
|
import Delete from '@mui/icons-material/DeleteOutlined';
|
||||||
import type {
|
import type {
|
||||||
IReleasePlanMilestonePayload,
|
IReleasePlanMilestonePayload,
|
||||||
IReleasePlanMilestoneStrategy,
|
IReleasePlanMilestoneStrategy,
|
||||||
} from 'interfaces/releasePlans';
|
} from 'interfaces/releasePlans';
|
||||||
import { type DragEventHandler, type RefObject, useState } from 'react';
|
import { type DragEventHandler, type RefObject, useRef, useState } from 'react';
|
||||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||||
import { MilestoneCardName } from './MilestoneCardName';
|
import { MilestoneCardName } from './MilestoneCardName';
|
||||||
import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenu/MilestoneStrategyMenuCards';
|
import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenu/MilestoneStrategyMenuCards';
|
||||||
import { MilestoneStrategyDraggableItem } from './MilestoneStrategyDraggableItem';
|
import { MilestoneStrategyDraggableItem } from './MilestoneStrategyDraggableItem';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import { ReleasePlanTemplateAddStrategyForm } from '../../MilestoneStrategy/ReleasePlanTemplateAddStrategyForm';
|
import { ReleasePlanTemplateAddStrategyForm } from '../../MilestoneStrategy/ReleasePlanTemplateAddStrategyForm';
|
||||||
|
import DragIndicator from '@mui/icons-material/DragIndicator';
|
||||||
|
import { type OnMoveItem, useDragItem } from 'hooks/useDragItem';
|
||||||
|
|
||||||
const StyledMilestoneCard = styled(Card, {
|
const StyledMilestoneCard = styled(Card, {
|
||||||
shouldForwardProp: (prop) => prop !== 'hasError',
|
shouldForwardProp: (prop) => prop !== 'hasError',
|
||||||
@ -110,13 +113,25 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
|||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface IMilestoneCardProps {
|
const StyledDragIcon = styled(IconButton)(({ theme }) => ({
|
||||||
|
padding: 0,
|
||||||
|
cursor: 'grab',
|
||||||
|
transition: 'color 0.2s ease-in-out',
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
'& > svg': {
|
||||||
|
color: 'action.active',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface IMilestoneCardProps {
|
||||||
milestone: IReleasePlanMilestonePayload;
|
milestone: IReleasePlanMilestonePayload;
|
||||||
milestoneChanged: (milestone: IReleasePlanMilestonePayload) => void;
|
milestoneChanged: (milestone: IReleasePlanMilestonePayload) => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
removable: boolean;
|
removable: boolean;
|
||||||
onDeleteMilestone: () => void;
|
onDeleteMilestone: () => void;
|
||||||
|
index: number;
|
||||||
|
onMoveItem: OnMoveItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MilestoneCard = ({
|
export const MilestoneCard = ({
|
||||||
@ -126,6 +141,8 @@ export const MilestoneCard = ({
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
removable,
|
removable,
|
||||||
onDeleteMilestone,
|
onDeleteMilestone,
|
||||||
|
index,
|
||||||
|
onMoveItem,
|
||||||
}: IMilestoneCardProps) => {
|
}: IMilestoneCardProps) => {
|
||||||
const [anchor, setAnchor] = useState<Element>();
|
const [anchor, setAnchor] = useState<Element>();
|
||||||
const [dragItem, setDragItem] = useState<{
|
const [dragItem, setDragItem] = useState<{
|
||||||
@ -141,6 +158,20 @@ export const MilestoneCard = ({
|
|||||||
? 'MilestoneStrategyMenuPopover'
|
? 'MilestoneStrategyMenuPopover'
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const dragHandleRef = useRef(null);
|
||||||
|
|
||||||
|
const dragItemRef = useDragItem<HTMLTableRowElement>(
|
||||||
|
index,
|
||||||
|
onMoveItem,
|
||||||
|
dragHandleRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dragHandle = (
|
||||||
|
<StyledDragIcon ref={dragHandleRef} disableRipple size='small'>
|
||||||
|
<DragIndicator titleAccess='Drag to reorder' />
|
||||||
|
</StyledDragIcon>
|
||||||
|
);
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setAnchor(undefined);
|
setAnchor(undefined);
|
||||||
};
|
};
|
||||||
@ -217,7 +248,7 @@ export const MilestoneCard = ({
|
|||||||
setAddUpdateStrategyOpen(true);
|
setAddUpdateStrategyOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragOver =
|
const onStrategyDragOver =
|
||||||
(targetId: string) =>
|
(targetId: string) =>
|
||||||
(
|
(
|
||||||
ref: RefObject<HTMLDivElement>,
|
ref: RefObject<HTMLDivElement>,
|
||||||
@ -253,7 +284,7 @@ export const MilestoneCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragStartRef =
|
const onStrategyDragStartRef =
|
||||||
(
|
(
|
||||||
ref: RefObject<HTMLDivElement>,
|
ref: RefObject<HTMLDivElement>,
|
||||||
index: number,
|
index: number,
|
||||||
@ -275,7 +306,7 @@ export const MilestoneCard = ({
|
|||||||
event.dataTransfer.setDragImage(ref.current, 20, 20);
|
event.dataTransfer.setDragImage(ref.current, 20, 20);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onDragEnd = () => {
|
const onStrategyDragEnd = () => {
|
||||||
setDragItem(null);
|
setDragItem(null);
|
||||||
onReOrderStrategies();
|
onReOrderStrategies();
|
||||||
};
|
};
|
||||||
@ -313,10 +344,12 @@ export const MilestoneCard = ({
|
|||||||
Boolean(errors?.[milestone.id]) ||
|
Boolean(errors?.[milestone.id]) ||
|
||||||
Boolean(errors?.[`${milestone.id}_name`])
|
Boolean(errors?.[`${milestone.id}_name`])
|
||||||
}
|
}
|
||||||
|
ref={dragItemRef}
|
||||||
>
|
>
|
||||||
<StyledMilestoneCardBody>
|
<StyledMilestoneCardBody>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<StyledGridItem item xs={6} md={6}>
|
<StyledGridItem item xs={6} md={6}>
|
||||||
|
{dragHandle}
|
||||||
<MilestoneCardName
|
<MilestoneCardName
|
||||||
milestone={milestone}
|
milestone={milestone}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
@ -368,6 +401,10 @@ export const MilestoneCard = ({
|
|||||||
</StyledMilestoneCardBody>
|
</StyledMilestoneCardBody>
|
||||||
</StyledMilestoneCard>
|
</StyledMilestoneCard>
|
||||||
|
|
||||||
|
<FormHelperText error={Boolean(errors?.[milestone.id])}>
|
||||||
|
{errors?.[milestone.id]}
|
||||||
|
</FormHelperText>
|
||||||
|
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
label='Add strategy to template milestone'
|
label='Add strategy to template milestone'
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@ -398,7 +435,9 @@ export const MilestoneCard = ({
|
|||||||
>
|
>
|
||||||
<StyledAccordionSummary
|
<StyledAccordionSummary
|
||||||
expandIcon={<ExpandMore titleAccess='Toggle' />}
|
expandIcon={<ExpandMore titleAccess='Toggle' />}
|
||||||
|
ref={dragItemRef}
|
||||||
>
|
>
|
||||||
|
{dragHandle}
|
||||||
<MilestoneCardName
|
<MilestoneCardName
|
||||||
milestone={milestone}
|
milestone={milestone}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
@ -411,9 +450,9 @@ export const MilestoneCard = ({
|
|||||||
<div key={strg.id}>
|
<div key={strg.id}>
|
||||||
<MilestoneStrategyDraggableItem
|
<MilestoneStrategyDraggableItem
|
||||||
index={index}
|
index={index}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onStrategyDragEnd}
|
||||||
onDragStartRef={onDragStartRef}
|
onDragStartRef={onStrategyDragStartRef}
|
||||||
onDragOver={onDragOver(strg.id)}
|
onDragOver={onStrategyDragOver(strg.id)}
|
||||||
onDeleteClick={() =>
|
onDeleteClick={() =>
|
||||||
milestoneStrategyDeleted(strg.id)
|
milestoneStrategyDeleted(strg.id)
|
||||||
}
|
}
|
||||||
@ -463,6 +502,10 @@ export const MilestoneCard = ({
|
|||||||
</StyledAccordionDetails>
|
</StyledAccordionDetails>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
|
|
||||||
|
<FormHelperText error={Boolean(errors?.[milestone.id])}>
|
||||||
|
{errors?.[milestone.id]}
|
||||||
|
</FormHelperText>
|
||||||
|
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
label='Add strategy to template milestone'
|
label='Add strategy to template milestone'
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
||||||
import { MilestoneCard } from './MilestoneCard/MilestoneCard';
|
import { styled, Button } from '@mui/material';
|
||||||
import { styled, Button, FormHelperText } from '@mui/material';
|
|
||||||
import Add from '@mui/icons-material/Add';
|
import Add from '@mui/icons-material/Add';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import type { OnMoveItem } from 'hooks/useDragItem';
|
||||||
|
import { MilestoneCard } from './MilestoneCard/MilestoneCard';
|
||||||
|
|
||||||
interface IMilestoneListProps {
|
interface IMilestoneListProps {
|
||||||
milestones: IReleasePlanMilestonePayload[];
|
milestones: IReleasePlanMilestonePayload[];
|
||||||
@ -26,6 +28,24 @@ export const MilestoneList = ({
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
milestoneChanged,
|
milestoneChanged,
|
||||||
}: IMilestoneListProps) => {
|
}: IMilestoneListProps) => {
|
||||||
|
const onMoveItem: OnMoveItem = useCallback(
|
||||||
|
async (dragIndex: number, dropIndex: number) => {
|
||||||
|
if (dragIndex !== dropIndex) {
|
||||||
|
const oldMilestones = milestones || [];
|
||||||
|
const newMilestones = [...oldMilestones];
|
||||||
|
const movedMilestone = newMilestones.splice(dragIndex, 1)[0];
|
||||||
|
newMilestones.splice(dropIndex, 0, movedMilestone);
|
||||||
|
|
||||||
|
newMilestones.forEach((milestone, index) => {
|
||||||
|
milestone.sortOrder = index;
|
||||||
|
});
|
||||||
|
|
||||||
|
setMilestones(newMilestones);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[milestones],
|
||||||
|
);
|
||||||
|
|
||||||
const onDeleteMilestone = (milestoneId: string) => () => {
|
const onDeleteMilestone = (milestoneId: string) => () => {
|
||||||
setMilestones((prev) =>
|
setMilestones((prev) =>
|
||||||
prev
|
prev
|
||||||
@ -36,22 +56,18 @@ export const MilestoneList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{milestones.map((milestone) => (
|
{milestones.map((milestone, index) => (
|
||||||
<>
|
<MilestoneCard
|
||||||
<MilestoneCard
|
key={milestone.id}
|
||||||
key={milestone.id}
|
index={index}
|
||||||
milestone={milestone}
|
onMoveItem={onMoveItem}
|
||||||
milestoneChanged={milestoneChanged}
|
milestone={milestone}
|
||||||
errors={errors}
|
milestoneChanged={milestoneChanged}
|
||||||
clearErrors={clearErrors}
|
errors={errors}
|
||||||
removable={milestones.length > 1}
|
clearErrors={clearErrors}
|
||||||
onDeleteMilestone={onDeleteMilestone(milestone.id)}
|
removable={milestones.length > 1}
|
||||||
/>
|
onDeleteMilestone={onDeleteMilestone(milestone.id)}
|
||||||
|
/>
|
||||||
<FormHelperText error={Boolean(errors?.[milestone.id])}>
|
|
||||||
{errors?.[milestone.id]}
|
|
||||||
</FormHelperText>
|
|
||||||
</>
|
|
||||||
))}
|
))}
|
||||||
<StyledAddMilestoneButton
|
<StyledAddMilestoneButton
|
||||||
variant='text'
|
variant='text'
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { useRef, useEffect, type RefObject } from 'react';
|
import { useRef, useEffect, type RefObject } from 'react';
|
||||||
|
|
||||||
export type MoveListItem = (
|
export type OnMoveItem = (
|
||||||
dragIndex: number,
|
dragIndex: number,
|
||||||
dropIndex: number,
|
dropIndex: number,
|
||||||
save?: boolean,
|
save?: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
// The element being dragged in the browser.
|
||||||
|
let globalDraggedElement: HTMLElement | null;
|
||||||
|
|
||||||
export const useDragItem = <T extends HTMLElement>(
|
export const useDragItem = <T extends HTMLElement>(
|
||||||
listItemIndex: number,
|
listItemIndex: number,
|
||||||
moveListItem: MoveListItem,
|
onMoveItem: OnMoveItem,
|
||||||
handle?: RefObject<HTMLElement>,
|
handle?: RefObject<HTMLElement>,
|
||||||
): RefObject<T> => {
|
): RefObject<T> => {
|
||||||
const ref = useRef<T>(null);
|
const ref = useRef<T>(null);
|
||||||
@ -18,32 +21,30 @@ export const useDragItem = <T extends HTMLElement>(
|
|||||||
ref.current.dataset.index = String(listItemIndex);
|
ref.current.dataset.index = String(listItemIndex);
|
||||||
return addEventListeners(
|
return addEventListeners(
|
||||||
ref.current,
|
ref.current,
|
||||||
moveListItem,
|
onMoveItem,
|
||||||
handle?.current ?? undefined,
|
handle?.current ?? undefined,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [listItemIndex, moveListItem]);
|
}, [listItemIndex, onMoveItem]);
|
||||||
|
|
||||||
return ref;
|
return ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addEventListeners = (
|
const addEventListeners = (
|
||||||
el: HTMLElement,
|
el: HTMLElement,
|
||||||
moveListItem: MoveListItem,
|
onMoveItem: OnMoveItem,
|
||||||
handle?: HTMLElement,
|
handle?: HTMLElement,
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
|
const handleEl = handle ?? el;
|
||||||
|
|
||||||
const moveDraggedElement = (save: boolean) => {
|
const moveDraggedElement = (save: boolean) => {
|
||||||
if (globalDraggedElement) {
|
if (globalDraggedElement) {
|
||||||
moveListItem(
|
const fromIndex = Number(globalDraggedElement.dataset.index);
|
||||||
Number(globalDraggedElement.dataset.index),
|
const toIndex = Number(el.dataset.index);
|
||||||
Number(el.dataset.index),
|
onMoveItem(fromIndex, toIndex, save);
|
||||||
save,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEl = handle ?? el;
|
|
||||||
|
|
||||||
const onMouseEnter = (e: MouseEvent) => {
|
const onMouseEnter = (e: MouseEvent) => {
|
||||||
if (e.target === handleEl) {
|
if (e.target === handleEl) {
|
||||||
el.draggable = true;
|
el.draggable = true;
|
||||||
@ -72,6 +73,10 @@ const addEventListeners = (
|
|||||||
globalDraggedElement = null;
|
globalDraggedElement = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
globalDraggedElement = null;
|
||||||
|
};
|
||||||
|
|
||||||
handleEl.addEventListener('mouseenter', onMouseEnter);
|
handleEl.addEventListener('mouseenter', onMouseEnter);
|
||||||
handleEl.addEventListener('mouseleave', onMouseLeave);
|
handleEl.addEventListener('mouseleave', onMouseLeave);
|
||||||
if (handle) {
|
if (handle) {
|
||||||
@ -81,6 +86,7 @@ const addEventListeners = (
|
|||||||
el.addEventListener('dragenter', onDragEnter);
|
el.addEventListener('dragenter', onDragEnter);
|
||||||
el.addEventListener('dragover', onDragOver);
|
el.addEventListener('dragover', onDragOver);
|
||||||
el.addEventListener('drop', onDrop);
|
el.addEventListener('drop', onDrop);
|
||||||
|
el.addEventListener('dragend', onDragEnd);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
handleEl.removeEventListener('mouseenter', onMouseEnter);
|
handleEl.removeEventListener('mouseenter', onMouseEnter);
|
||||||
@ -92,8 +98,6 @@ const addEventListeners = (
|
|||||||
el.removeEventListener('dragenter', onDragEnter);
|
el.removeEventListener('dragenter', onDragEnter);
|
||||||
el.removeEventListener('dragover', onDragOver);
|
el.removeEventListener('dragover', onDragOver);
|
||||||
el.removeEventListener('drop', onDrop);
|
el.removeEventListener('drop', onDrop);
|
||||||
|
el.removeEventListener('dragend', onDragEnd);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// The element being dragged in the browser.
|
|
||||||
let globalDraggedElement: HTMLElement | null;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user