publ-echo 0.0.1

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.
Files changed (57) hide show
  1. package/README.md +29 -0
  2. package/bin/cli.js +8 -0
  3. package/bitbucket-pipelines.yml +35 -0
  4. package/package.json +51 -0
  5. package/public/favicon.ico +0 -0
  6. package/public/index.html +43 -0
  7. package/public/logo192.png +0 -0
  8. package/public/logo512.png +0 -0
  9. package/public/manifest.json +25 -0
  10. package/public/robots.txt +3 -0
  11. package/src/App.tsx +28 -0
  12. package/src/examples/ReactGridLayout/ReactGridLayoutShowcase01.tsx +80 -0
  13. package/src/examples/ReactGridLayout/index.ts +1 -0
  14. package/src/examples/ResponsiveGridLayout/ResponsiveGridLayoutShowcase01.tsx +114 -0
  15. package/src/examples/ResponsiveGridLayout/index.ts +1 -0
  16. package/src/examples/index.ts +2 -0
  17. package/src/examples/utils.ts +15 -0
  18. package/src/index.tsx +21 -0
  19. package/src/lib/Draggable/Draggable.tsx +303 -0
  20. package/src/lib/Draggable/DraggableCore.tsx +352 -0
  21. package/src/lib/Draggable/constants.ts +12 -0
  22. package/src/lib/Draggable/index.ts +2 -0
  23. package/src/lib/Draggable/types.ts +74 -0
  24. package/src/lib/Draggable/utils/domHelpers.ts +284 -0
  25. package/src/lib/Draggable/utils/getPrefix.ts +42 -0
  26. package/src/lib/Draggable/utils/positionHelpers.ts +49 -0
  27. package/src/lib/Draggable/utils/types.ts +41 -0
  28. package/src/lib/Draggable/utils/validationHelpers.ts +23 -0
  29. package/src/lib/GridItem/GridItem.tsx +493 -0
  30. package/src/lib/GridItem/index.ts +1 -0
  31. package/src/lib/GridItem/types.ts +121 -0
  32. package/src/lib/GridItem/utils/calculateUtils.ts +173 -0
  33. package/src/lib/GridLayoutEditor/ReactGridLayout.tsx +662 -0
  34. package/src/lib/GridLayoutEditor/ResponsiveGridLayout.tsx +204 -0
  35. package/src/lib/GridLayoutEditor/index.ts +9 -0
  36. package/src/lib/GridLayoutEditor/styles/styles.css +133 -0
  37. package/src/lib/GridLayoutEditor/types.ts +199 -0
  38. package/src/lib/GridLayoutEditor/utils/renderHelpers.ts +737 -0
  39. package/src/lib/GridLayoutEditor/utils/responsiveUtils.ts +111 -0
  40. package/src/lib/PreviewGLE/ReactGridLayoutPreview.tsx +46 -0
  41. package/src/lib/PreviewGLE/ResponsiveGridLayoutPreview.tsx +54 -0
  42. package/src/lib/PreviewGLE/index.ts +2 -0
  43. package/src/lib/Resizable/Resizable.tsx +323 -0
  44. package/src/lib/Resizable/ResizableBox.tsx +109 -0
  45. package/src/lib/Resizable/index.ts +1 -0
  46. package/src/lib/Resizable/styles/styles.css +76 -0
  47. package/src/lib/Resizable/types.ts +96 -0
  48. package/src/lib/Resizable/utils/cloneElement.ts +15 -0
  49. package/src/lib/components/WidthProvider.tsx +71 -0
  50. package/src/lib/components/index.ts +1 -0
  51. package/src/lib/components/types.ts +19 -0
  52. package/src/lib/index.ts +4 -0
  53. package/src/react-app-env.d.ts +1 -0
  54. package/src/reportWebVitals.ts +15 -0
  55. package/src/setupTests.ts +5 -0
  56. package/src/utils/types.ts +3 -0
  57. package/tsconfig.json +26 -0
@@ -0,0 +1,303 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import DraggableCore from "./DraggableCore";
3
+ import { Bounds, ControlPosition, DraggableEventHandler } from "./utils/types";
4
+ import {
5
+ AxisType,
6
+ DraggableCoreDefaultProps,
7
+ DraggablePositionType,
8
+ DraggableState,
9
+ PropsWithChildren,
10
+ } from "./types";
11
+ import {
12
+ canDragX,
13
+ canDragY,
14
+ cloneBounds,
15
+ createDraggableData,
16
+ } from "./utils/positionHelpers";
17
+ import { int, isNum } from "./utils/validationHelpers";
18
+ import {
19
+ createCSSTransform,
20
+ createSVGTransform,
21
+ innerHeight,
22
+ innerWidth,
23
+ outerHeight,
24
+ outerWidth,
25
+ } from "./utils/domHelpers";
26
+ import classNames from "classnames";
27
+
28
+ export type DraggableDefaultProps = {
29
+ axis?: AxisType;
30
+ bounds?: Bounds | string | false;
31
+ defaultClassName?: string;
32
+ defaultClassNameDragging?: string;
33
+ defaultClassNameDragged?: string;
34
+ defaultPosition?: ControlPosition;
35
+ scale?: number;
36
+ nodeRef?: React.RefObject<HTMLElement>;
37
+ className?: string;
38
+ style?: Object;
39
+ } & DraggableCoreDefaultProps &
40
+ DraggablePositionType;
41
+
42
+ const Draggable = ({
43
+ children,
44
+ axis = "both",
45
+ bounds = false,
46
+ defaultClassName = "react-draggable",
47
+ defaultClassNameDragging = "react-draggable-dragging",
48
+ defaultClassNameDragged = "react-draggable-dragged",
49
+ defaultPosition = { x: 0, y: 0 },
50
+ scale = 1,
51
+ ...props
52
+ }: PropsWithChildren<DraggableDefaultProps>) => {
53
+ const { position, positionOffset, ...draggableCoreProps } = props;
54
+
55
+ const draggableRef = useRef<HTMLElement>(null);
56
+
57
+ const [draggableState, setDraggableState] = useState<DraggableState>({
58
+ dragged: false,
59
+ dragging: false,
60
+ x: position ? position?.x : defaultPosition.x,
61
+ y: position ? position?.y : defaultPosition.y,
62
+ slackX: 0,
63
+ slackY: 0,
64
+ isElementSVG: false,
65
+ prevPropsPosition: position,
66
+ });
67
+
68
+ /**
69
+ * @when Draggable이 화면에 그려질 때
70
+ * @expected 해당 DOMNode가 SVGElement인지를 확인
71
+ * @clear 페이지를 나갔을 때 dragging을 false로 변경하여 drag되지 않도록 설정
72
+ */
73
+ useEffect(() => {
74
+ if (
75
+ typeof window.SVGElement !== "undefined" &&
76
+ findDOMNode() instanceof window.SVGElement
77
+ ) {
78
+ setDraggableState((prevState) => ({ ...prevState, isElementSVG: true }));
79
+ }
80
+
81
+ return () => {
82
+ setDraggableState((prevState) => ({ ...prevState, dragging: false }));
83
+ };
84
+ }, []);
85
+
86
+ let style = {};
87
+ let svgTransform = null;
88
+
89
+ const findDOMNode = (): HTMLElement | null => {
90
+ return props?.nodeRef?.current ?? draggableRef?.current;
91
+ };
92
+
93
+ const getBoundPosition = (
94
+ x: number,
95
+ y: number,
96
+ node?: HTMLElement | null
97
+ ): [number, number] => {
98
+ if (!bounds) return [x, y];
99
+
100
+ let newBounds = bounds;
101
+ newBounds = typeof bounds === "string" ? bounds : cloneBounds(bounds);
102
+
103
+ if (typeof newBounds === "string") {
104
+ const ownerWindow = node?.ownerDocument?.defaultView;
105
+
106
+ let boundNode;
107
+
108
+ if (newBounds === "parent") {
109
+ boundNode = node?.parentNode;
110
+ } else {
111
+ boundNode = node?.ownerDocument.querySelector(newBounds);
112
+ }
113
+
114
+ if (!(ownerWindow && boundNode instanceof ownerWindow.HTMLElement)) {
115
+ throw new Error(
116
+ 'Bounds selector "' + newBounds + '" could not find an element'
117
+ );
118
+ }
119
+
120
+ const boundNodeElement: HTMLElement = boundNode;
121
+ const nodeStyle = ownerWindow.getComputedStyle(node);
122
+ const boundNodeStyle = ownerWindow.getComputedStyle(boundNodeElement);
123
+
124
+ newBounds = {
125
+ left:
126
+ -node.offsetLeft +
127
+ int(boundNodeStyle.paddingLeft) +
128
+ int(nodeStyle.marginLeft),
129
+ top:
130
+ -node.offsetTop +
131
+ int(boundNodeStyle.paddingTop) +
132
+ int(nodeStyle.marginTop),
133
+ right:
134
+ innerWidth(boundNodeElement) -
135
+ outerWidth(node) -
136
+ node.offsetLeft +
137
+ int(boundNodeStyle.paddingRight) -
138
+ int(nodeStyle.marginRight),
139
+ bottom:
140
+ innerHeight(boundNodeElement) -
141
+ outerHeight(node) -
142
+ node.offsetTop +
143
+ int(boundNodeStyle.paddingBottom) -
144
+ int(nodeStyle.marginBottom),
145
+ };
146
+ }
147
+
148
+ if (isNum(newBounds.right)) x = Math.min(x, newBounds.right);
149
+ if (isNum(newBounds.bottom)) y = Math.min(y, newBounds.bottom);
150
+
151
+ if (isNum(newBounds.left)) x = Math.max(x, newBounds.left);
152
+ if (isNum(newBounds.top)) y = Math.max(y, newBounds.top);
153
+
154
+ return [x, y];
155
+ };
156
+
157
+ const handleDragStart: DraggableEventHandler = (e, coreData) => {
158
+ if (props.onStart) {
159
+ const shouldStart = props.onStart(
160
+ e,
161
+ createDraggableData(draggableState, scale, coreData)
162
+ );
163
+
164
+ if (shouldStart === false) return false;
165
+ }
166
+
167
+ setDraggableState((prev) => ({ ...prev, dragged: true, dragging: true }));
168
+ };
169
+
170
+ const handleDrag: DraggableEventHandler = (e, coreData, node) => {
171
+ if (!draggableState.dragging) return false;
172
+
173
+ const uiData = createDraggableData(draggableState, scale, coreData);
174
+ const newState: DraggableState = {
175
+ ...draggableState,
176
+ x: uiData.x,
177
+ y: uiData.y,
178
+ };
179
+
180
+ if (bounds) {
181
+ const { x, y } = newState;
182
+
183
+ newState.x += draggableState.slackX;
184
+ newState.y += draggableState.slackY;
185
+
186
+ const [newBoundPositionX, newBoundPositionY] = getBoundPosition(
187
+ newState.x,
188
+ newState.y,
189
+ node
190
+ );
191
+
192
+ newState.x = newBoundPositionX;
193
+ newState.y = newBoundPositionY;
194
+
195
+ newState.slackX = draggableState.slackX + (x - newState.x);
196
+ newState.slackY = draggableState.slackY + (y - newState.y);
197
+
198
+ uiData.x = newState.x;
199
+ uiData.y = newState.y;
200
+ uiData.deltaX = newState.x - draggableState.x;
201
+ uiData.deltaY = newState.y - draggableState.y;
202
+ }
203
+
204
+ if (props.onDrag) {
205
+ const shouldUpdate = props.onDrag(e, uiData);
206
+
207
+ if (shouldUpdate === false) return false;
208
+ }
209
+
210
+ setDraggableState((prevState) => ({
211
+ ...prevState,
212
+ x: newState.x,
213
+ y: newState.y,
214
+ }));
215
+ };
216
+
217
+ const handleDragStop: DraggableEventHandler = (e, coreData) => {
218
+ if (!draggableState.dragging) return false;
219
+
220
+ if (props.onStop) {
221
+ const shouldContinue = props.onStop(
222
+ e,
223
+ createDraggableData(draggableState, scale, coreData)
224
+ );
225
+
226
+ if (shouldContinue === false) return false;
227
+ }
228
+
229
+ if (position && Boolean(position)) {
230
+ const { x, y } = position;
231
+
232
+ setDraggableState((prevState) => ({
233
+ ...prevState,
234
+ x,
235
+ y,
236
+ dragging: false,
237
+ slackX: 0,
238
+ slackY: 0,
239
+ }));
240
+ } else {
241
+ setDraggableState((prevState) => ({
242
+ ...prevState,
243
+ dragging: false,
244
+ slackX: 0,
245
+ slackY: 0,
246
+ }));
247
+ }
248
+ };
249
+
250
+ const controlled = Boolean(position);
251
+ const draggable = !controlled || draggableState.dragging;
252
+
253
+ const validPosition = position || defaultPosition;
254
+ const transformOptions = {
255
+ x: canDragX(axis) && draggable ? draggableState.x : validPosition.x,
256
+ y: canDragY(axis) && draggable ? draggableState.y : validPosition.y,
257
+ };
258
+
259
+ if (draggableState.isElementSVG) {
260
+ svgTransform = createSVGTransform(transformOptions, positionOffset);
261
+ } else {
262
+ style = createCSSTransform(transformOptions, positionOffset);
263
+ }
264
+
265
+ const className = classNames(
266
+ props.className || "",
267
+ defaultClassName,
268
+ { [defaultClassNameDragging]: draggableState.dragging },
269
+ { [defaultClassNameDragged]: draggableState.dragged }
270
+ );
271
+
272
+ // NOTE - class형 컴포넌트에서 사용되는 getDerivedStateFromProps에 대응하기 위한 조건문입니다.
273
+ if (
274
+ position &&
275
+ (!draggableState.prevPropsPosition ||
276
+ position.x !== draggableState.prevPropsPosition.x ||
277
+ position.y !== draggableState.prevPropsPosition.y)
278
+ ) {
279
+ setDraggableState((prevState) => ({
280
+ ...prevState,
281
+ x: position.x,
282
+ y: position.y,
283
+ prevPropsPosition: { ...position },
284
+ }));
285
+ }
286
+
287
+ return (
288
+ <DraggableCore
289
+ {...draggableCoreProps}
290
+ onStart={handleDragStart}
291
+ onDrag={handleDrag}
292
+ onStop={handleDragStop}
293
+ >
294
+ {React.cloneElement(children, {
295
+ className,
296
+ style: { ...props.style, ...style },
297
+ transform: svgTransform,
298
+ })}
299
+ </DraggableCore>
300
+ );
301
+ };
302
+
303
+ export default Draggable;
@@ -0,0 +1,352 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import type {
3
+ ControlPosition,
4
+ EventHandler,
5
+ MouseTouchEvent,
6
+ } from "./utils/types";
7
+ import {
8
+ DraggableCoreDefaultProps,
9
+ DraggableCoreState,
10
+ DraggableData,
11
+ PropsWithChildren,
12
+ } from "./types";
13
+ import {
14
+ addEvent,
15
+ addUserSelectStyles,
16
+ getTouch,
17
+ getTouchIdentifier,
18
+ matchSelectorAndParentsTo,
19
+ offsetXYFromParent,
20
+ removeEvent,
21
+ removeUserSelectStyles,
22
+ } from "./utils/domHelpers";
23
+ import { isNum } from "./utils/validationHelpers";
24
+ import { snapToGrid } from "./utils/positionHelpers";
25
+ import { EVENTS } from "./constants";
26
+
27
+ type Props = DraggableCoreDefaultProps;
28
+
29
+ const DraggableCore = ({
30
+ allowAnyClick = false,
31
+ disabled = false,
32
+ enableUserSelectHack = true,
33
+ onStart = function () {},
34
+ onDrag = function () {},
35
+ onStop = function () {},
36
+ onMouseDown = function () {},
37
+ scale = 1,
38
+ children,
39
+ ...props
40
+ }: PropsWithChildren<Props>) => {
41
+ const draggableCoreRef = useRef<HTMLElement>(null);
42
+ const isMounted = useRef(false);
43
+
44
+ const [draggableCoreState, setDraggableCoreState] =
45
+ useState<DraggableCoreState>({
46
+ dragging: false,
47
+ lastX: NaN,
48
+ lastY: NaN,
49
+ touchIdentifier: null,
50
+ });
51
+
52
+ /**
53
+ * @when DraggableCore가 랜더링 했을 때
54
+ * @expected 해당 DOMNode에서 Drag가 동작할 수 있도록 event를 추가합니다.
55
+ * @clear 등록된 모든 EventListener들을 remove합니다.
56
+ */
57
+ useEffect(() => {
58
+ const thisNode = findDOMNode();
59
+
60
+ if (thisNode) {
61
+ addEvent(thisNode, EVENTS.TOUCH.START, handleTouchStart, {
62
+ passive: false,
63
+ });
64
+
65
+ if (draggableCoreState.dragging) {
66
+ addEvent(thisNode.ownerDocument, dragEventFor.MOVE, handleDrag);
67
+ addEvent(thisNode.ownerDocument, dragEventFor.STOP, handleDragStop);
68
+ } else {
69
+ removeEvent(thisNode.ownerDocument, dragEventFor.MOVE, handleDrag);
70
+ removeEvent(thisNode.ownerDocument, dragEventFor.STOP, handleDragStop);
71
+ }
72
+ }
73
+
74
+ return () => {
75
+ if (thisNode) {
76
+ const { ownerDocument } = thisNode;
77
+
78
+ removeEvent(ownerDocument, EVENTS.MOUSE.MOVE, handleDrag);
79
+ removeEvent(ownerDocument, EVENTS.MOUSE.STOP, handleDragStop);
80
+ removeEvent(ownerDocument, EVENTS.TOUCH.MOVE, handleDrag);
81
+ removeEvent(ownerDocument, EVENTS.TOUCH.STOP, handleDragStop);
82
+ removeEvent(thisNode, EVENTS.TOUCH.START, handleTouchStart, {
83
+ passive: false,
84
+ });
85
+ }
86
+ };
87
+ }, [handleDragStart, handleDrag, handleDragStop]);
88
+
89
+ /**
90
+ * @when DraggableCore가 랜더링 했을 때
91
+ * @expected mount된 상태를 true로 변경합니다.
92
+ * @clear mount된 상태를 false로 변경하고, 등록된 styles을 제거합니다.
93
+ */
94
+ useEffect(() => {
95
+ isMounted.current = true;
96
+
97
+ return () => {
98
+ isMounted.current = false;
99
+
100
+ const thisNode = findDOMNode();
101
+
102
+ if (thisNode) {
103
+ const { ownerDocument } = thisNode;
104
+
105
+ if (enableUserSelectHack) removeUserSelectStyles(ownerDocument);
106
+ }
107
+ };
108
+ // NOTE - react-draggable과 동일하게 동작하게 하기 위해서 dependency를 제거하였습니다.
109
+ }, []);
110
+
111
+ let dragEventFor = EVENTS.MOUSE;
112
+
113
+ // NOTE - draggable할 node element를 찾는 함수
114
+ const findDOMNode = (): HTMLElement | null => {
115
+ return props?.nodeRef?.current ?? draggableCoreRef?.current;
116
+ };
117
+
118
+ // NOTE - event 발생 시 {x, y} position을 가져오는 함수
119
+ const getControlPosition = (
120
+ e: MouseTouchEvent,
121
+ touchIdentifier?: number | null
122
+ ): ControlPosition | null => {
123
+ const touchObj =
124
+ typeof touchIdentifier === "number" ? getTouch(e, touchIdentifier) : null;
125
+
126
+ // NOTE - 올바르게 touch를 하지 않은 경우
127
+ if (typeof touchIdentifier === "number" && !touchObj) return null;
128
+
129
+ const thisNode = findDOMNode();
130
+
131
+ const offsetParent =
132
+ props?.offsetParent ||
133
+ thisNode?.offsetParent ||
134
+ thisNode?.ownerDocument?.body;
135
+
136
+ if (!offsetParent) return null;
137
+
138
+ return offsetXYFromParent(touchObj || e, offsetParent, scale);
139
+ };
140
+
141
+ const createCoreData = (x: number, y: number): DraggableData | void => {
142
+ const isStart = !isNum(draggableCoreState.lastX);
143
+ const node = findDOMNode();
144
+
145
+ if (!node) return;
146
+
147
+ if (isStart) {
148
+ return {
149
+ node,
150
+ deltaX: 0,
151
+ deltaY: 0,
152
+ lastX: x,
153
+ lastY: y,
154
+ x,
155
+ y,
156
+ };
157
+ } else {
158
+ return {
159
+ node,
160
+ deltaX: x - draggableCoreState.lastX,
161
+ deltaY: y - draggableCoreState.lastY,
162
+ lastX: draggableCoreState.lastX,
163
+ lastY: draggableCoreState.lastY,
164
+ x,
165
+ y,
166
+ };
167
+ }
168
+ };
169
+
170
+ function handleDragStart(e: MouseTouchEvent): void | false {
171
+ onMouseDown && onMouseDown(e);
172
+
173
+ // NOTE - left clicks만 가능
174
+ if (!allowAnyClick && typeof e.button === "number" && e.button !== 0) {
175
+ return false;
176
+ }
177
+
178
+ const thisNode = findDOMNode();
179
+
180
+ if (!thisNode || !thisNode.ownerDocument || !thisNode.ownerDocument.body) {
181
+ throw new Error("<DraggableCore> not mounted on DragStart!");
182
+ }
183
+
184
+ const { ownerDocument } = thisNode;
185
+
186
+ // NOTE - handle || cancel prop이 제공되었을 때 selector와 match되는지를 확인
187
+ // handle prop이 존재할 때, matchSelectorAndParentsTo 메소드를 통해 thisNode와 일치하는 element만 Drag 가능
188
+ // cancel prop이 존재할 때, matchSelectorAndParentsTo 메소드를 통해 thisNode와 일치하는 element는 Drag 불가능
189
+ if (
190
+ disabled ||
191
+ !(
192
+ ownerDocument.defaultView &&
193
+ e.target instanceof ownerDocument.defaultView.Node
194
+ ) ||
195
+ (props.handle &&
196
+ !matchSelectorAndParentsTo(e.target, props.handle, thisNode)) ||
197
+ (props.cancel &&
198
+ matchSelectorAndParentsTo(e.target, props.cancel, thisNode))
199
+ ) {
200
+ return;
201
+ }
202
+
203
+ // NOTE - ipad || iphone과 같은 mobile 기기에서 scroll 방지
204
+ if (e.type === "touchstart") e.preventDefault();
205
+
206
+ // NOTE - touch event가 발생했을 때 multi touch 상황에서 각각의 터치를 구분하고 다른 처리를 할 수 있게 하기 위함입니다.
207
+ const touchIdentifier = getTouchIdentifier(e);
208
+
209
+ if (touchIdentifier) {
210
+ setDraggableCoreState({ ...draggableCoreState, touchIdentifier });
211
+ }
212
+
213
+ const position = getControlPosition(e, touchIdentifier);
214
+
215
+ if (!position) return;
216
+
217
+ const { x, y } = position;
218
+
219
+ const coreEvent = createCoreData(x, y);
220
+
221
+ if (!coreEvent) return;
222
+
223
+ const shouldUpdate = onStart(e, coreEvent);
224
+
225
+ if (shouldUpdate === false || isMounted.current === false) return;
226
+
227
+ if (enableUserSelectHack) addUserSelectStyles(ownerDocument);
228
+
229
+ setDraggableCoreState((prev) => ({
230
+ ...prev,
231
+ dragging: true,
232
+ lastX: x,
233
+ lastY: y,
234
+ }));
235
+ }
236
+
237
+ function handleDrag(e: MouseTouchEvent): void | false {
238
+ const position = getControlPosition(e, draggableCoreState.touchIdentifier);
239
+
240
+ if (!position) return;
241
+
242
+ let { x, y } = position;
243
+
244
+ if (Array.isArray(props.grid)) {
245
+ let deltaX = x - draggableCoreState.lastX;
246
+ let deltaY = y - draggableCoreState.lastY;
247
+
248
+ [deltaX, deltaY] = snapToGrid(props.grid, deltaX, deltaY);
249
+
250
+ if (!deltaX && !deltaY) return;
251
+
252
+ x = draggableCoreState.lastX + deltaX;
253
+ y = draggableCoreState.lastY + deltaY;
254
+ }
255
+
256
+ const coreEvent = createCoreData(x, y);
257
+ const thisNode = findDOMNode();
258
+
259
+ if (!coreEvent || !thisNode) return;
260
+
261
+ const shouldUpdate = onDrag(e, coreEvent, thisNode);
262
+
263
+ if (shouldUpdate === false || isMounted.current === false) {
264
+ handleDragStop(e);
265
+
266
+ return;
267
+ }
268
+
269
+ setDraggableCoreState((prevState) => ({
270
+ ...prevState,
271
+ lastX: x,
272
+ lastY: y,
273
+ }));
274
+ }
275
+
276
+ function handleDragStop(e: MouseTouchEvent): void | false {
277
+ if (!draggableCoreState.dragging) return;
278
+
279
+ const position = getControlPosition(e, draggableCoreState.touchIdentifier);
280
+
281
+ if (!position) return;
282
+
283
+ let { x, y } = position;
284
+
285
+ if (Array.isArray(props?.grid)) {
286
+ let deltaX = x - draggableCoreState.lastX || 0;
287
+ let deltaY = y - draggableCoreState.lastY || 0;
288
+ [deltaX, deltaY] = snapToGrid(props?.grid, deltaX, deltaY);
289
+ x = draggableCoreState.lastX + deltaX;
290
+ y = draggableCoreState.lastY + deltaY;
291
+ }
292
+
293
+ const coreEvent = createCoreData(x, y);
294
+
295
+ if (!coreEvent) return;
296
+
297
+ const shouldContinue = onStop(e, coreEvent);
298
+
299
+ if (shouldContinue === false || isMounted.current === false) return false;
300
+
301
+ const thisNode = findDOMNode();
302
+
303
+ if (thisNode) {
304
+ if (enableUserSelectHack) removeUserSelectStyles(thisNode.ownerDocument);
305
+ }
306
+
307
+ setDraggableCoreState((prev) => ({
308
+ ...prev,
309
+ dragging: false,
310
+ lastX: NaN,
311
+ lastY: NaN,
312
+ }));
313
+
314
+ if (thisNode) {
315
+ removeEvent(thisNode.ownerDocument, dragEventFor.MOVE, handleDrag);
316
+ removeEvent(thisNode.ownerDocument, dragEventFor.STOP, handleDragStop);
317
+ }
318
+ }
319
+
320
+ const handleMouseDown: EventHandler<MouseTouchEvent> = (e) => {
321
+ dragEventFor = EVENTS.MOUSE;
322
+
323
+ return handleDragStart(e);
324
+ };
325
+
326
+ const handleMouseUp: EventHandler<MouseTouchEvent> = (e) => {
327
+ dragEventFor = EVENTS.MOUSE;
328
+
329
+ return handleDragStop(e);
330
+ };
331
+
332
+ // NOTE - handleMouseDown(start drag)과 동일하지만 touch device에 대응
333
+ const handleTouchStart: EventHandler<MouseTouchEvent> = (e) => {
334
+ dragEventFor = EVENTS.TOUCH;
335
+
336
+ return handleDragStart(e);
337
+ };
338
+ const handleTouchEnd: EventHandler<MouseTouchEvent> = (e) => {
339
+ dragEventFor = EVENTS.TOUCH;
340
+
341
+ return handleDragStop(e);
342
+ };
343
+
344
+ return React.cloneElement(children, {
345
+ onMouseDown: handleMouseDown,
346
+ onMouseUp: handleMouseUp,
347
+ onTouchEnd: handleTouchEnd,
348
+ ref: props.nodeRef ?? draggableCoreRef,
349
+ });
350
+ };
351
+
352
+ export default DraggableCore;
@@ -0,0 +1,12 @@
1
+ export const EVENTS = {
2
+ TOUCH: {
3
+ START: "touchstart",
4
+ MOVE: "touchmove",
5
+ STOP: "touchend",
6
+ },
7
+ MOUSE: {
8
+ START: "mousedown",
9
+ MOVE: "mousemove",
10
+ STOP: "mouseup",
11
+ },
12
+ };
@@ -0,0 +1,2 @@
1
+ export { default as Draggable } from "./Draggable";
2
+ export { default as DraggableCore } from "./DraggableCore";