funda-ui 4.5.657 → 4.5.671
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.
- package/ColorPicker/index.js +3 -1
- package/Date/index.d.ts +1 -0
- package/Date/index.js +17 -2
- package/DragDropList/index.css +188 -0
- package/DragDropList/index.d.ts +44 -0
- package/DragDropList/index.js +1587 -0
- package/Input/index.d.ts +2 -0
- package/Input/index.js +14 -1
- package/LICENSE +21 -0
- package/MasonryLayout/index.d.ts +2 -0
- package/MasonryLayout/index.js +115 -5
- package/MultipleSelect/index.css +237 -144
- package/MultipleSelect/index.d.ts +24 -10
- package/MultipleSelect/index.js +2240 -1225
- package/README.md +3 -1
- package/RangeSlider/index.js +14 -1
- package/Textarea/index.d.ts +2 -0
- package/Textarea/index.js +14 -1
- package/Tree/index.d.ts +1 -0
- package/Tree/index.js +29 -0
- package/Utils/useBoundedDrag.d.ts +125 -0
- package/Utils/useBoundedDrag.js +380 -0
- package/Utils/useDragDropPosition.d.ts +169 -0
- package/Utils/useDragDropPosition.js +456 -0
- package/Utils/useIsMobile.d.ts +2 -0
- package/Utils/useIsMobile.js +168 -0
- package/all.d.ts +1 -0
- package/all.js +1 -1
- package/lib/cjs/ColorPicker/index.js +3 -1
- package/lib/cjs/Date/index.d.ts +1 -0
- package/lib/cjs/Date/index.js +17 -2
- package/lib/cjs/DragDropList/index.d.ts +44 -0
- package/lib/cjs/DragDropList/index.js +1587 -0
- package/lib/cjs/Input/index.d.ts +2 -0
- package/lib/cjs/Input/index.js +14 -1
- package/lib/cjs/MasonryLayout/index.d.ts +2 -0
- package/lib/cjs/MasonryLayout/index.js +115 -5
- package/lib/cjs/MultipleSelect/index.d.ts +24 -10
- package/lib/cjs/MultipleSelect/index.js +2240 -1225
- package/lib/cjs/RangeSlider/index.js +14 -1
- package/lib/cjs/Textarea/index.d.ts +2 -0
- package/lib/cjs/Textarea/index.js +14 -1
- package/lib/cjs/Tree/index.d.ts +1 -0
- package/lib/cjs/Tree/index.js +29 -0
- package/lib/cjs/Utils/useBoundedDrag.d.ts +125 -0
- package/lib/cjs/Utils/useBoundedDrag.js +380 -0
- package/lib/cjs/Utils/useDragDropPosition.d.ts +169 -0
- package/lib/cjs/Utils/useDragDropPosition.js +456 -0
- package/lib/cjs/Utils/useIsMobile.d.ts +2 -0
- package/lib/cjs/Utils/useIsMobile.js +168 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -1
- package/lib/css/DragDropList/index.css +188 -0
- package/lib/css/MultipleSelect/index.css +237 -144
- package/lib/esm/ColorPicker/index.tsx +53 -49
- package/lib/esm/Date/index.tsx +3 -0
- package/lib/esm/DragDropList/index.scss +245 -0
- package/lib/esm/DragDropList/index.tsx +494 -0
- package/lib/esm/Input/index.tsx +17 -3
- package/lib/esm/MasonryLayout/index.tsx +125 -7
- package/lib/esm/MultipleSelect/index.scss +288 -183
- package/lib/esm/MultipleSelect/index.tsx +305 -166
- package/lib/esm/MultipleSelect/utils/func.ts +21 -1
- package/lib/esm/Tabs/Tabs.tsx +1 -1
- package/lib/esm/Textarea/index.tsx +18 -1
- package/lib/esm/Tree/TreeList.tsx +32 -0
- package/lib/esm/Tree/index.tsx +3 -0
- package/lib/esm/Utils/hooks/useBoundedDrag.tsx +301 -0
- package/lib/esm/Utils/hooks/useDragDropPosition.tsx +420 -0
- package/lib/esm/Utils/hooks/useIsMobile.tsx +56 -0
- package/lib/esm/index.js +1 -0
- package/package.json +1 -1
- package/lib/esm/MultipleSelect/ItemList.tsx +0 -323
|
@@ -34,8 +34,28 @@ export function multiSelControlOptionExist(arr: any[], val: any) {
|
|
|
34
34
|
* @returns
|
|
35
35
|
*/
|
|
36
36
|
export function uniqueArr(arr: any[]) {
|
|
37
|
-
return arr.filter((item: any, index: number, self: any[]) => index === self.findIndex((t) => (t.
|
|
37
|
+
return arr.filter((item: any, index: number, self: any[]) => index === self.findIndex((t) => (t.id == item.id)));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Remove Duplicate objects from Options
|
|
42
|
+
* @param {Array} arr
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export function uniqueOpt(arr: any[]) {
|
|
46
|
+
return arr.flat().filter((item: any, index: number, self: any[]) => index === self.findIndex((t) => (t.id === item.id)));
|
|
47
|
+
}
|
|
40
48
|
|
|
41
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Sort JSON arrays according to the order of the numeric arrays
|
|
52
|
+
* @param {Array<Number>|Array<String>} orderArray
|
|
53
|
+
* @param {Array<*>} arr
|
|
54
|
+
* @param {String} field
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
export function sortedJsonArray(orderArray: number[] | string[], arr: any[], field: string = 'value') {
|
|
58
|
+
return orderArray.map((orderId: number | string) =>
|
|
59
|
+
arr.find(item => item[field] === orderId)
|
|
60
|
+
);
|
|
61
|
+
}
|
package/lib/esm/Tabs/Tabs.tsx
CHANGED
|
@@ -18,7 +18,7 @@ export type TabsProps = {
|
|
|
18
18
|
animTransitionDuration?: number;
|
|
19
19
|
/** -- */
|
|
20
20
|
style?: React.CSSProperties;
|
|
21
|
-
onChange?: (nav:
|
|
21
|
+
onChange?: (nav: HTMLElement, targetId: string, index: number, persistentIndex: number) => void;
|
|
22
22
|
onLoad?: (func: Function) => void;
|
|
23
23
|
children: React.ReactNode | React.ReactNode[];
|
|
24
24
|
|
|
@@ -50,6 +50,8 @@ export type TextareaProps = {
|
|
|
50
50
|
onBlur?: (e: any, el: any) => void;
|
|
51
51
|
onFocus?: (e: any, el: any) => void;
|
|
52
52
|
onPressEnter?: (e: any, el: any) => void;
|
|
53
|
+
onKeyDown?: (e: any, el: any) => void;
|
|
54
|
+
onKeyUp?: (e: any, el: any) => void;
|
|
53
55
|
onResize?: (el: any, params: number[]) => void;
|
|
54
56
|
|
|
55
57
|
};
|
|
@@ -96,6 +98,8 @@ const Textarea = forwardRef((props: TextareaProps, externalRef: any) => {
|
|
|
96
98
|
onBlur,
|
|
97
99
|
onFocus,
|
|
98
100
|
onPressEnter,
|
|
101
|
+
onKeyDown,
|
|
102
|
+
onKeyUp,
|
|
99
103
|
onResize,
|
|
100
104
|
...attributes
|
|
101
105
|
} = props;
|
|
@@ -288,7 +292,12 @@ const Textarea = forwardRef((props: TextareaProps, externalRef: any) => {
|
|
|
288
292
|
set: (value: string, cb?: any) => {
|
|
289
293
|
setChangedVal(`${value}`);
|
|
290
294
|
cb?.();
|
|
291
|
-
}
|
|
295
|
+
},
|
|
296
|
+
aiPredictReset: () => {
|
|
297
|
+
setTimeout(() => { // Avoid conflicts with other asynchronous states, resulting in invalid clearing
|
|
298
|
+
setCurrentSuggestion('');
|
|
299
|
+
}, 0);
|
|
300
|
+
},
|
|
292
301
|
}),
|
|
293
302
|
[contentRef]
|
|
294
303
|
);
|
|
@@ -363,6 +372,10 @@ const Textarea = forwardRef((props: TextareaProps, externalRef: any) => {
|
|
|
363
372
|
}
|
|
364
373
|
|
|
365
374
|
function handleKeyPressed(event: KeyboardEvent<HTMLTextAreaElement>) {
|
|
375
|
+
|
|
376
|
+
onKeyDown?.(event, valRef.current);
|
|
377
|
+
|
|
378
|
+
|
|
366
379
|
if (typeof (onKeyPressedCallback) === 'function') {
|
|
367
380
|
const newData: any = onKeyPressedCallback(event, valRef.current);
|
|
368
381
|
if (newData) setChangedVal(newData); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
|
|
@@ -418,6 +431,9 @@ const Textarea = forwardRef((props: TextareaProps, externalRef: any) => {
|
|
|
418
431
|
|
|
419
432
|
}
|
|
420
433
|
|
|
434
|
+
function handleKeyUp(event: KeyboardEvent<HTMLTextAreaElement>) {
|
|
435
|
+
onKeyUp?.(event, valRef.current);
|
|
436
|
+
}
|
|
421
437
|
|
|
422
438
|
useEffect(() => {
|
|
423
439
|
|
|
@@ -534,6 +550,7 @@ const Textarea = forwardRef((props: TextareaProps, externalRef: any) => {
|
|
|
534
550
|
}
|
|
535
551
|
}}
|
|
536
552
|
onKeyDown={handleKeyPressed}
|
|
553
|
+
onKeyUp={handleKeyUp}
|
|
537
554
|
disabled={disabled || null}
|
|
538
555
|
required={required || null}
|
|
539
556
|
readOnly={readOnly || null}
|
|
@@ -40,6 +40,7 @@ export type TreeListProps = {
|
|
|
40
40
|
getCheckedData?: any[];
|
|
41
41
|
updategetCheckedData?: any;
|
|
42
42
|
onSelect?: (e: any, val: any, func: Function) => void;
|
|
43
|
+
onDoubleSelect?: (e: any, val: any, func: Function) => void;
|
|
43
44
|
onCollapse?: (e: any, val: any, func: Function) => void;
|
|
44
45
|
onCheck?: (val: any) => void;
|
|
45
46
|
evInitValue?: (key: React.Key | null, fetch: FetchConfig | null, firstRender: boolean) => void;
|
|
@@ -64,6 +65,7 @@ export default function TreeList(props: TreeListProps) {
|
|
|
64
65
|
getCheckedData,
|
|
65
66
|
updategetCheckedData,
|
|
66
67
|
onSelect,
|
|
68
|
+
onDoubleSelect,
|
|
67
69
|
onCollapse,
|
|
68
70
|
onCheck,
|
|
69
71
|
evInitValue
|
|
@@ -285,6 +287,34 @@ export default function TreeList(props: TreeListProps) {
|
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
|
|
290
|
+
function handleDoubleSelect(e: any) {
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
e.stopPropagation();
|
|
293
|
+
|
|
294
|
+
const hyperlink = e.currentTarget;
|
|
295
|
+
|
|
296
|
+
if ( hyperlink.classList.contains('selected') ) {
|
|
297
|
+
activeClass(hyperlink, 'remove', 'selected');
|
|
298
|
+
} else {
|
|
299
|
+
[].slice.call(hyperlink.closest('.tree-diagram__wrapper').querySelectorAll('li > a')).forEach((node: any) => {
|
|
300
|
+
activeClass(node, 'remove', 'selected');
|
|
301
|
+
});
|
|
302
|
+
activeClass(hyperlink, 'add', 'selected');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
onDoubleSelect?.(e, {
|
|
307
|
+
key: hyperlink.dataset.key,
|
|
308
|
+
slug: hyperlink.dataset.slug,
|
|
309
|
+
link: hyperlink.dataset.link,
|
|
310
|
+
optiondata: hyperlink.dataset.optiondata
|
|
311
|
+
}, typeof evInitValue !== 'function' ? ()=>void(0) : evInitValue);
|
|
312
|
+
|
|
313
|
+
if ( disableArrow ) {
|
|
314
|
+
handleCollapse(e);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
288
318
|
|
|
289
319
|
function titleArrowGenerator() {
|
|
290
320
|
return disableArrow ? loadingIcon : null;
|
|
@@ -522,6 +552,7 @@ export default function TreeList(props: TreeListProps) {
|
|
|
522
552
|
href={item.link === '#' ? `${item.link}-${i}` : item.link}
|
|
523
553
|
aria-expanded="false"
|
|
524
554
|
onClick={handleSelect}
|
|
555
|
+
onDoubleClick={handleDoubleSelect}
|
|
525
556
|
data-link={item.link}
|
|
526
557
|
data-slug={item.slug}
|
|
527
558
|
data-key={item.key}
|
|
@@ -553,6 +584,7 @@ export default function TreeList(props: TreeListProps) {
|
|
|
553
584
|
first={false}
|
|
554
585
|
arrow={arrow}
|
|
555
586
|
onSelect={onSelect}
|
|
587
|
+
onDoubleSelect={onDoubleSelect}
|
|
556
588
|
onCollapse={onCollapse}
|
|
557
589
|
onCheck={onCheck}
|
|
558
590
|
disableArrow={disableArrow}
|
package/lib/esm/Tree/index.tsx
CHANGED
|
@@ -75,6 +75,7 @@ export type TreeProps = {
|
|
|
75
75
|
/** -- */
|
|
76
76
|
id?: string;
|
|
77
77
|
onSelect?: (e: any, val: any, func: Function) => void;
|
|
78
|
+
onDoubleSelect?: (e: any, val: any, func: Function) => void;
|
|
78
79
|
onCollapse?: (e: any, val: any, func: Function) => void;
|
|
79
80
|
onCheck?: (val: any) => void;
|
|
80
81
|
};
|
|
@@ -95,6 +96,7 @@ const Tree = (props: TreeProps) => {
|
|
|
95
96
|
data,
|
|
96
97
|
retrieveData,
|
|
97
98
|
onSelect,
|
|
99
|
+
onDoubleSelect,
|
|
98
100
|
onCollapse,
|
|
99
101
|
onCheck
|
|
100
102
|
} = props;
|
|
@@ -374,6 +376,7 @@ const Tree = (props: TreeProps) => {
|
|
|
374
376
|
data={Array.isArray(retrieveData) && retrieveData.length > 0 ? filterRetriveData(flatList, retrieveData) : list}
|
|
375
377
|
childClassName={childClassName || 'tree-diagram-default-nav'}
|
|
376
378
|
onSelect={onSelect}
|
|
379
|
+
onDoubleSelect={onDoubleSelect}
|
|
377
380
|
onCollapse={onCollapse}
|
|
378
381
|
onCheck={onCheck}
|
|
379
382
|
evInitValue={initDefaultValue}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded Drag
|
|
3
|
+
*
|
|
4
|
+
* @usage:
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const App = () => {
|
|
8
|
+
const [items, setItems] = useState<ListItem[]>([]);
|
|
9
|
+
// ... other states and refs
|
|
10
|
+
|
|
11
|
+
const deepCloneWithReactNode = (obj: any): any => {
|
|
12
|
+
if (obj === null || typeof obj !== 'object') {
|
|
13
|
+
return obj;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Handle array
|
|
17
|
+
if (Array.isArray(obj)) {
|
|
18
|
+
return obj.map(item => deepCloneWithReactNode(item));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Handle object
|
|
22
|
+
const clonedObj: any = {};
|
|
23
|
+
for (const key in obj) {
|
|
24
|
+
if (key === 'appendControl') {
|
|
25
|
+
clonedObj[key] = obj[key];
|
|
26
|
+
} else {
|
|
27
|
+
clonedObj[key] = deepCloneWithReactNode(obj[key]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return clonedObj;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const getItemWithChildrenIndices = (items: ListItem[], startIndex: number): number[] => {
|
|
35
|
+
const indices = [startIndex];
|
|
36
|
+
const startItem = items[startIndex];
|
|
37
|
+
const startDepth = startItem.depth || 0;
|
|
38
|
+
|
|
39
|
+
// Check if subsequent items are child items
|
|
40
|
+
for (let i = startIndex + 1; i < items.length; i++) {
|
|
41
|
+
const currentItem = items[i];
|
|
42
|
+
const currentDepth = currentItem.depth || 0;
|
|
43
|
+
if (currentDepth > startDepth) {
|
|
44
|
+
indices.push(i);
|
|
45
|
+
} else {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return indices;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
const { isDragging, dragHandlers } = useBoundedDrag({
|
|
55
|
+
dragMode,
|
|
56
|
+
boundarySelector: '.custom-draggable-list',
|
|
57
|
+
itemSelector:'.custom-draggable-list__item',
|
|
58
|
+
dragHandleSelector: '.custom-draggable-list__handle',
|
|
59
|
+
onDragStart: (index: number) => {
|
|
60
|
+
// Additional drag start logic if needed
|
|
61
|
+
},
|
|
62
|
+
onDragOver: (dragIndex: number | null, dropIndex: number | null) => {
|
|
63
|
+
// Additional drag over logic if needed
|
|
64
|
+
},
|
|
65
|
+
onDragEnd: (dragIndex: number | null, dropIndex: number | null) => {
|
|
66
|
+
if (dragIndex !== null && dropIndex !== null && dragIndex !== dropIndex) {
|
|
67
|
+
// Handle item movement
|
|
68
|
+
const newItems = deepCloneWithReactNode(items);
|
|
69
|
+
const itemsToMove = getItemWithChildrenIndices(newItems, dragIndex);
|
|
70
|
+
const itemsBeingMoved = itemsToMove.map(index => newItems[index]);
|
|
71
|
+
|
|
72
|
+
// ... rest of your existing drag end logic ...
|
|
73
|
+
|
|
74
|
+
setItems(updatedItems);
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Update your JSX to use the new handlers
|
|
81
|
+
return (
|
|
82
|
+
<ul className="custom-draggable-list">
|
|
83
|
+
{items.map((item: any, index: number) => (
|
|
84
|
+
<li
|
|
85
|
+
// ... other props
|
|
86
|
+
draggable={!draggable ? undefined : editingItem !== item.id && "true"}
|
|
87
|
+
onDragStart={!draggable ? undefined : (e) => dragHandlers.handleDragStart(e, index)}
|
|
88
|
+
onDragOver={!draggable ? undefined : dragHandlers.handleDragOver}
|
|
89
|
+
onDragEnd={!draggable ? undefined : dragHandlers.handleDragEnd}
|
|
90
|
+
onTouchStart={!draggable ? undefined : (e) => dragHandlers.handleDragStart(e, index)}
|
|
91
|
+
onTouchMove={!draggable ? undefined : dragHandlers.handleDragOver}
|
|
92
|
+
onTouchEnd={!draggable ? undefined : dragHandlers.handleDragEnd}
|
|
93
|
+
>
|
|
94
|
+
<li className="custom-draggable-list__item">
|
|
95
|
+
<span className="custom-draggable-list__handle">☰</span>
|
|
96
|
+
<i>content {indec}<i>
|
|
97
|
+
</li>
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
import { useRef, useState } from 'react';
|
|
106
|
+
|
|
107
|
+
export interface TouchOffset {
|
|
108
|
+
x: number;
|
|
109
|
+
y: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface BoundedDragOptions {
|
|
113
|
+
dragMode?: 'handle' | 'block';
|
|
114
|
+
boundarySelector?: string;
|
|
115
|
+
itemSelector?: string;
|
|
116
|
+
dragHandleSelector?: string;
|
|
117
|
+
onDragStart?: (index: number) => void;
|
|
118
|
+
onDragOver?: (dragIndex: number | null, dropIndex: number | null) => void;
|
|
119
|
+
onDragEnd?: (dragIndex: number | null, dropIndex: number | null) => void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const useBoundedDrag = (options: BoundedDragOptions = {}) => {
|
|
123
|
+
const {
|
|
124
|
+
dragMode = 'handle',
|
|
125
|
+
boundarySelector = '.custom-draggable-list',
|
|
126
|
+
itemSelector = '.custom-draggable-list__item',
|
|
127
|
+
dragHandleSelector = '.custom-draggable-list__handle',
|
|
128
|
+
onDragStart,
|
|
129
|
+
onDragOver,
|
|
130
|
+
onDragEnd
|
|
131
|
+
} = options;
|
|
132
|
+
|
|
133
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
134
|
+
const dragItem = useRef<number | null>(null);
|
|
135
|
+
const dragOverItem = useRef<number | null>(null);
|
|
136
|
+
const dragNode = useRef<HTMLElement | null>(null);
|
|
137
|
+
const touchOffset = useRef<TouchOffset>({ x: 0, y: 0 });
|
|
138
|
+
const currentHoverItem = useRef<HTMLElement | null>(null);
|
|
139
|
+
|
|
140
|
+
const handleDragStart = (e: React.DragEvent | React.TouchEvent, position: number) => {
|
|
141
|
+
const isTouch = 'touches' in e;
|
|
142
|
+
const target = e.target as HTMLElement;
|
|
143
|
+
|
|
144
|
+
// For block mode or handle mode check
|
|
145
|
+
if (dragMode === 'handle') {
|
|
146
|
+
const handle = target.closest(dragHandleSelector);
|
|
147
|
+
if (!handle) {
|
|
148
|
+
if (!isTouch) e.preventDefault();
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Find the draggable item
|
|
154
|
+
const listItem = target.closest(itemSelector) as HTMLElement;
|
|
155
|
+
if (!listItem) return;
|
|
156
|
+
|
|
157
|
+
// Check boundary
|
|
158
|
+
const boundary = listItem.closest(boundarySelector);
|
|
159
|
+
if (!boundary) return;
|
|
160
|
+
|
|
161
|
+
dragItem.current = position;
|
|
162
|
+
onDragStart?.(position);
|
|
163
|
+
|
|
164
|
+
if (isTouch) {
|
|
165
|
+
e.preventDefault(); // Prevent scrolling
|
|
166
|
+
const touch = (e as React.TouchEvent).touches[0];
|
|
167
|
+
const rect = listItem.getBoundingClientRect();
|
|
168
|
+
const boundaryRect = boundary.getBoundingClientRect();
|
|
169
|
+
|
|
170
|
+
// Calculate offset relative to the boundary
|
|
171
|
+
touchOffset.current = {
|
|
172
|
+
x: touch.clientX - rect.left,
|
|
173
|
+
y: touch.clientY - rect.top
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Clone the item for dragging
|
|
177
|
+
dragNode.current = listItem.cloneNode(true) as HTMLElement;
|
|
178
|
+
dragNode.current.classList.add('dragging');
|
|
179
|
+
|
|
180
|
+
// Style the clone
|
|
181
|
+
Object.assign(dragNode.current.style, {
|
|
182
|
+
position: 'fixed',
|
|
183
|
+
width: `${rect.width}px`,
|
|
184
|
+
height: `${rect.height}px`,
|
|
185
|
+
left: `${rect.left}px`,
|
|
186
|
+
top: `${rect.top}px`,
|
|
187
|
+
zIndex: '1000',
|
|
188
|
+
pointerEvents: 'none',
|
|
189
|
+
transform: 'scale(1.05)',
|
|
190
|
+
transition: 'transform 0.1s',
|
|
191
|
+
opacity: '0.9'
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
document.body.appendChild(dragNode.current);
|
|
195
|
+
setIsDragging(true);
|
|
196
|
+
listItem.classList.add('dragging-placeholder');
|
|
197
|
+
} else {
|
|
198
|
+
// ... desktop drag logic remains the same ...
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleDragOver = (e: React.DragEvent | React.TouchEvent) => {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
const isTouch = 'touches' in e;
|
|
205
|
+
|
|
206
|
+
if (!isTouch) {
|
|
207
|
+
(e as React.DragEvent).dataTransfer.dropEffect = 'move';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get the current pointer/touch position
|
|
211
|
+
const point = isTouch ?
|
|
212
|
+
(e as React.TouchEvent).touches[0] :
|
|
213
|
+
{ clientX: (e as React.DragEvent).clientX, clientY: (e as React.DragEvent).clientY };
|
|
214
|
+
|
|
215
|
+
// Update dragged element position for touch events
|
|
216
|
+
if (isTouch && isDragging && dragNode.current) {
|
|
217
|
+
dragNode.current.style.left = `${point.clientX - touchOffset.current.x}px`;
|
|
218
|
+
dragNode.current.style.top = `${point.clientY - touchOffset.current.y}px`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Find the element below the pointer/touch
|
|
222
|
+
const elemBelow = document.elementFromPoint(
|
|
223
|
+
point.clientX,
|
|
224
|
+
point.clientY
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (!elemBelow) return;
|
|
228
|
+
|
|
229
|
+
// Find the closest list item
|
|
230
|
+
const listItem = elemBelow.closest(itemSelector) as HTMLElement;
|
|
231
|
+
if (!listItem || listItem === currentHoverItem.current) return;
|
|
232
|
+
|
|
233
|
+
// Check boundary
|
|
234
|
+
const boundary = listItem.closest(boundarySelector);
|
|
235
|
+
if (!boundary) return;
|
|
236
|
+
|
|
237
|
+
// Update hover states
|
|
238
|
+
if (currentHoverItem.current) {
|
|
239
|
+
currentHoverItem.current.classList.remove('drag-over', 'drag-over-top', 'drag-over-bottom');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
currentHoverItem.current = listItem;
|
|
243
|
+
listItem.classList.add('drag-over');
|
|
244
|
+
|
|
245
|
+
// Calculate position in list
|
|
246
|
+
const position = Array.from(listItem.parentNode!.children).indexOf(listItem);
|
|
247
|
+
dragOverItem.current = position;
|
|
248
|
+
|
|
249
|
+
// Determine drop position (top/bottom)
|
|
250
|
+
const rect = listItem.getBoundingClientRect();
|
|
251
|
+
const middleY = rect.top + rect.height / 2;
|
|
252
|
+
|
|
253
|
+
if (point.clientY < middleY) {
|
|
254
|
+
listItem.classList.add('drag-over-top');
|
|
255
|
+
} else {
|
|
256
|
+
listItem.classList.add('drag-over-bottom');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
onDragOver?.(dragItem.current, dragOverItem.current);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const handleDragEnd = (e: React.DragEvent | React.TouchEvent) => {
|
|
263
|
+
const isTouch = 'touches' in e;
|
|
264
|
+
if (isTouch && !isDragging) return;
|
|
265
|
+
|
|
266
|
+
onDragEnd?.(dragItem.current, dragOverItem.current);
|
|
267
|
+
|
|
268
|
+
// Cleanup
|
|
269
|
+
if (dragNode.current) {
|
|
270
|
+
dragNode.current.remove();
|
|
271
|
+
dragNode.current = null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
document.querySelectorAll(itemSelector).forEach(item => {
|
|
275
|
+
(item as HTMLElement).style.opacity = '1';
|
|
276
|
+
item.classList.remove(
|
|
277
|
+
'dragging',
|
|
278
|
+
'dragging-placeholder',
|
|
279
|
+
'drag-over',
|
|
280
|
+
'drag-over-top',
|
|
281
|
+
'drag-over-bottom'
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
setIsDragging(false);
|
|
286
|
+
currentHoverItem.current = null;
|
|
287
|
+
dragItem.current = null;
|
|
288
|
+
dragOverItem.current = null;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
isDragging,
|
|
293
|
+
dragHandlers: {
|
|
294
|
+
handleDragStart,
|
|
295
|
+
handleDragOver,
|
|
296
|
+
handleDragEnd
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export default useBoundedDrag;
|