File size: 5,052 Bytes
f56a29b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | import { useCallback } from 'react';
import { uniq } from 'lodash';
import { useCanvasStore } from '@/lib/store';
import { useKeyboardStore } from '@/lib/store/keyboard';
import type { PPTElement } from '@/lib/types/slides';
/**
* Hook for handling element selection in Canvas
* Supports single selection, multi-selection (Ctrl/Shift), and group selection
*/
export function useSelectElement(
elementListRef: React.RefObject<PPTElement[]>,
moveElement: (e: React.MouseEvent | React.TouchEvent, element: PPTElement) => void,
) {
const activeElementIdList = useCanvasStore.use.activeElementIdList();
const activeGroupElementId = useCanvasStore.use.activeGroupElementId();
const handleElementId = useCanvasStore.use.handleElementId();
const editorAreaFocus = useCanvasStore.use.editorAreaFocus();
const setActiveElementIdList = useCanvasStore.use.setActiveElementIdList();
const setHandleElementId = useCanvasStore.use.setHandleElementId();
const setActiveGroupElementId = useCanvasStore.use.setActiveGroupElementId();
const setEditorAreaFocus = useCanvasStore.use.setEditorAreaFocus();
const ctrlOrShiftKeyActive = useKeyboardStore((state) => state.ctrlOrShiftKeyActive());
// Select element
// startMove indicates whether to enter move state after selection
const selectElement = useCallback(
(e: React.MouseEvent | React.TouchEvent, element: PPTElement, startMove = true) => {
if (!editorAreaFocus) setEditorAreaFocus(true);
// If the target element is not currently selected, set it as selected
// If Ctrl or Shift is held, enter multi-select mode: add target to current selection; otherwise select only the target
// If the target is a group member, also select the other members of that group
if (!activeElementIdList.includes(element.id)) {
let newActiveIdList: string[] = [];
if (ctrlOrShiftKeyActive) {
newActiveIdList = [...activeElementIdList, element.id];
} else {
newActiveIdList = [element.id];
}
if (element.groupId) {
const groupMembersId: string[] = [];
elementListRef.current.forEach((el: PPTElement) => {
if (el.groupId === element.groupId) groupMembersId.push(el.id);
});
newActiveIdList = [...newActiveIdList, ...groupMembersId];
}
setActiveElementIdList(uniq(newActiveIdList));
setHandleElementId(element.id);
}
// If the target element is already selected with Ctrl/Shift held, deselect it
// Unless it's the last selected element, or the group it belongs to is the last selected group
// If the target is a group member, also deselect other members of that group
else if (ctrlOrShiftKeyActive) {
let newActiveIdList: string[] = [];
if (element.groupId) {
const groupMembersId: string[] = [];
elementListRef.current.forEach((el: PPTElement) => {
if (el.groupId === element.groupId) groupMembersId.push(el.id);
});
newActiveIdList = activeElementIdList.filter((id) => !groupMembersId.includes(id));
} else {
newActiveIdList = activeElementIdList.filter((id) => id !== element.id);
}
if (newActiveIdList.length > 0) {
setActiveElementIdList(newActiveIdList);
}
}
// If the target is already selected but not the current handle element, make it the handle element
else if (handleElementId !== element.id) {
setHandleElementId(element.id);
}
// If the target is already the handle element, clicking again sets it as the active group element
else if (activeGroupElementId !== element.id) {
const startPageX =
e.nativeEvent instanceof MouseEvent
? e.nativeEvent.pageX
: 'changedTouches' in e
? e.changedTouches[0].pageX
: 0;
const startPageY =
e.nativeEvent instanceof MouseEvent
? e.nativeEvent.pageY
: 'changedTouches' in e
? e.changedTouches[0].pageY
: 0;
const target = e.target as HTMLElement;
const handleMouseUp = (e: MouseEvent) => {
const currentPageX = e.pageX;
const currentPageY = e.pageY;
if (startPageX === currentPageX && startPageY === currentPageY) {
setActiveGroupElementId(element.id);
target.onmouseup = null;
}
};
target.onmouseup = handleMouseUp;
}
if (startMove) moveElement(e, element);
},
// eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally excludes elementListRef (stable ref) to avoid infinite re-creation
[
editorAreaFocus,
activeElementIdList,
ctrlOrShiftKeyActive,
handleElementId,
activeGroupElementId,
setEditorAreaFocus,
setActiveElementIdList,
setHandleElementId,
setActiveGroupElementId,
moveElement,
],
);
return {
selectElement,
};
}
|