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.
- package/README.md +29 -0
- package/bin/cli.js +8 -0
- package/bitbucket-pipelines.yml +35 -0
- package/package.json +51 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/App.tsx +28 -0
- package/src/examples/ReactGridLayout/ReactGridLayoutShowcase01.tsx +80 -0
- package/src/examples/ReactGridLayout/index.ts +1 -0
- package/src/examples/ResponsiveGridLayout/ResponsiveGridLayoutShowcase01.tsx +114 -0
- package/src/examples/ResponsiveGridLayout/index.ts +1 -0
- package/src/examples/index.ts +2 -0
- package/src/examples/utils.ts +15 -0
- package/src/index.tsx +21 -0
- package/src/lib/Draggable/Draggable.tsx +303 -0
- package/src/lib/Draggable/DraggableCore.tsx +352 -0
- package/src/lib/Draggable/constants.ts +12 -0
- package/src/lib/Draggable/index.ts +2 -0
- package/src/lib/Draggable/types.ts +74 -0
- package/src/lib/Draggable/utils/domHelpers.ts +284 -0
- package/src/lib/Draggable/utils/getPrefix.ts +42 -0
- package/src/lib/Draggable/utils/positionHelpers.ts +49 -0
- package/src/lib/Draggable/utils/types.ts +41 -0
- package/src/lib/Draggable/utils/validationHelpers.ts +23 -0
- package/src/lib/GridItem/GridItem.tsx +493 -0
- package/src/lib/GridItem/index.ts +1 -0
- package/src/lib/GridItem/types.ts +121 -0
- package/src/lib/GridItem/utils/calculateUtils.ts +173 -0
- package/src/lib/GridLayoutEditor/ReactGridLayout.tsx +662 -0
- package/src/lib/GridLayoutEditor/ResponsiveGridLayout.tsx +204 -0
- package/src/lib/GridLayoutEditor/index.ts +9 -0
- package/src/lib/GridLayoutEditor/styles/styles.css +133 -0
- package/src/lib/GridLayoutEditor/types.ts +199 -0
- package/src/lib/GridLayoutEditor/utils/renderHelpers.ts +737 -0
- package/src/lib/GridLayoutEditor/utils/responsiveUtils.ts +111 -0
- package/src/lib/PreviewGLE/ReactGridLayoutPreview.tsx +46 -0
- package/src/lib/PreviewGLE/ResponsiveGridLayoutPreview.tsx +54 -0
- package/src/lib/PreviewGLE/index.ts +2 -0
- package/src/lib/Resizable/Resizable.tsx +323 -0
- package/src/lib/Resizable/ResizableBox.tsx +109 -0
- package/src/lib/Resizable/index.ts +1 -0
- package/src/lib/Resizable/styles/styles.css +76 -0
- package/src/lib/Resizable/types.ts +96 -0
- package/src/lib/Resizable/utils/cloneElement.ts +15 -0
- package/src/lib/components/WidthProvider.tsx +71 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/types.ts +19 -0
- package/src/lib/index.ts +4 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +5 -0
- package/src/utils/types.ts +3 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Breakpoint,
|
|
3
|
+
Breakpoints,
|
|
4
|
+
CompactType,
|
|
5
|
+
Layout,
|
|
6
|
+
ResponsiveLayout,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import { cloneLayout, compact, correctBounds } from "./renderHelpers";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Given a width, find the highest breakpoint that matches is valid for it (width > breakpoint).
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} breakpoints Breakpoints object (e.g. {lg: 1200, md: 960, ...})
|
|
14
|
+
* @param {Number} width Screen width.
|
|
15
|
+
*/
|
|
16
|
+
export function getBreakpointFromWidth(
|
|
17
|
+
breakpoints: Breakpoints<Breakpoint>,
|
|
18
|
+
width: number
|
|
19
|
+
): Breakpoint {
|
|
20
|
+
const sorted = sortBreakpoints(breakpoints);
|
|
21
|
+
let matching = sorted[0];
|
|
22
|
+
for (let i = 1, len = sorted.length; i < len; i++) {
|
|
23
|
+
const breakpointName = sorted[i];
|
|
24
|
+
if (width > breakpoints[breakpointName]) matching = breakpointName;
|
|
25
|
+
}
|
|
26
|
+
return matching;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Given a breakpoint, get the # of cols set for it.
|
|
31
|
+
*/
|
|
32
|
+
export function getColsFromBreakpoint(
|
|
33
|
+
breakpoint: Breakpoint,
|
|
34
|
+
cols: Breakpoints<Breakpoint>
|
|
35
|
+
): number {
|
|
36
|
+
if (!cols[breakpoint]) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"ResponsiveGridLayout: `cols` entry for breakpoint " +
|
|
39
|
+
breakpoint +
|
|
40
|
+
" is missing!"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return cols[breakpoint];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Given existing layouts and a new breakpoint, find or generate a new layout.
|
|
48
|
+
*
|
|
49
|
+
* This finds the layout above the new one and generates from it, if it exists.
|
|
50
|
+
*/
|
|
51
|
+
export function findOrGenerateResponsiveLayout(
|
|
52
|
+
layouts: ResponsiveLayout<Breakpoint>,
|
|
53
|
+
breakpoints: Breakpoints<Breakpoint>,
|
|
54
|
+
breakpoint: Breakpoint,
|
|
55
|
+
lastBreakpoint: Breakpoint,
|
|
56
|
+
cols: number,
|
|
57
|
+
compactType: CompactType,
|
|
58
|
+
overlap: boolean
|
|
59
|
+
): Layout {
|
|
60
|
+
// If it already exists, just return it.
|
|
61
|
+
if (layouts[breakpoint]) {
|
|
62
|
+
return cloneLayout(layouts[breakpoint]);
|
|
63
|
+
}
|
|
64
|
+
// Find or generate the next layout
|
|
65
|
+
let layout = layouts[lastBreakpoint];
|
|
66
|
+
const breakpointsSorted = sortBreakpoints(breakpoints);
|
|
67
|
+
|
|
68
|
+
// Above?
|
|
69
|
+
for (
|
|
70
|
+
let i = breakpointsSorted.indexOf(breakpoint);
|
|
71
|
+
i < breakpointsSorted.length;
|
|
72
|
+
i++
|
|
73
|
+
) {
|
|
74
|
+
const b = breakpointsSorted[i];
|
|
75
|
+
if (layouts[b]) {
|
|
76
|
+
layout = layouts[b];
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!layout) {
|
|
81
|
+
// below?
|
|
82
|
+
for (let i = breakpointsSorted.indexOf(breakpoint) - 1; i >= 0; i--) {
|
|
83
|
+
const b = breakpointsSorted[i];
|
|
84
|
+
if (layouts[b]) {
|
|
85
|
+
layout = layouts[b];
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
layout = cloneLayout(layout || []); // clone layout so we don't modify existing items
|
|
92
|
+
return overlap
|
|
93
|
+
? layout
|
|
94
|
+
: compact(correctBounds(layout, { cols: cols }), compactType, cols);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Given breakpoints, return an array of breakpoints sorted by width. This is usually
|
|
99
|
+
* e.g. ['xxs', 'xs', 'sm', ...]
|
|
100
|
+
*
|
|
101
|
+
* @param {Object} breakpoints Key/value pair of breakpoint names to widths.
|
|
102
|
+
* @return {Array} Sorted breakpoints.
|
|
103
|
+
*/
|
|
104
|
+
export function sortBreakpoints(
|
|
105
|
+
breakpoints: Breakpoints<Breakpoint>
|
|
106
|
+
): Array<Breakpoint> {
|
|
107
|
+
const keys: Array<string> = Object.keys(breakpoints);
|
|
108
|
+
return keys.sort(function (a, b) {
|
|
109
|
+
return breakpoints[a] - breakpoints[b];
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import ReactGridLayout from "../GridLayoutEditor/ReactGridLayout";
|
|
2
|
+
import { Layout } from "../GridLayoutEditor/types";
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
import { WidthProvider } from "../components";
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
layout: Layout;
|
|
8
|
+
props: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// const Responsive = WidthProvider(ReactGridLayout);
|
|
12
|
+
|
|
13
|
+
// NOTE - 예시용으로 확인할 수 있게 props을 받았지만 추후에 배포될 때 제거 필요
|
|
14
|
+
function ReactGridLayoutPreview({ layout, props }: Props) {
|
|
15
|
+
const defaultProps = {
|
|
16
|
+
isDraggable: false,
|
|
17
|
+
isResizable: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// const [layout, setLayout] = useState<Layout>(lay);
|
|
21
|
+
function onLayoutChange(newLayout: Layout) {
|
|
22
|
+
// setLayout(newLayout);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ReactGridLayout
|
|
27
|
+
layout={layout}
|
|
28
|
+
onLayoutChange={onLayoutChange}
|
|
29
|
+
{...defaultProps}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{layout.map((each) => (
|
|
33
|
+
<S_SampleBox key={each.i}>{each.i}</S_SampleBox>
|
|
34
|
+
))}
|
|
35
|
+
</ReactGridLayout>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const S_SampleBox = styled.div`
|
|
40
|
+
width: 100px;
|
|
41
|
+
height: 100px;
|
|
42
|
+
background-color: #fbff00;
|
|
43
|
+
border: #3c1eff 1px solid;
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export default ReactGridLayoutPreview;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
import ResponsiveGridLayout from "../GridLayoutEditor/ResponsiveGridLayout";
|
|
3
|
+
import { Layout } from "../GridLayoutEditor/types";
|
|
4
|
+
import { WidthProvider } from "../components";
|
|
5
|
+
import _ from "lodash";
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
layouts: { [key: string]: Layout };
|
|
9
|
+
props: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const Responsive = WidthProvider(ResponsiveGridLayout);
|
|
13
|
+
|
|
14
|
+
// NOTE - 예시용으로 확인할 수 있게 props을 받았지만 추후에 배포될 때 제거 필요
|
|
15
|
+
function ResponsiveGridLayoutPreview({ layouts, props }: Props) {
|
|
16
|
+
const defaultProps = {
|
|
17
|
+
isDraggable: false,
|
|
18
|
+
isResizable: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function generateDOM() {
|
|
22
|
+
return _.map(layouts.lg, function (l, i) {
|
|
23
|
+
return (
|
|
24
|
+
<S_SampleBox key={i} className={l.static ? "static" : ""}>
|
|
25
|
+
{l.static ? (
|
|
26
|
+
<span
|
|
27
|
+
className="text"
|
|
28
|
+
title="This item is static and cannot be removed or resized."
|
|
29
|
+
>
|
|
30
|
+
Static - {i}
|
|
31
|
+
</span>
|
|
32
|
+
) : (
|
|
33
|
+
<span className="text">{i}</span>
|
|
34
|
+
)}
|
|
35
|
+
</S_SampleBox>
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Responsive layouts={layouts} {...defaultProps} {...props}>
|
|
42
|
+
{generateDOM()}
|
|
43
|
+
</Responsive>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const S_SampleBox = styled.div`
|
|
48
|
+
width: 100px;
|
|
49
|
+
height: 100px;
|
|
50
|
+
background-color: #fbff00;
|
|
51
|
+
border: #3c1eff 1px solid;
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
export default ResponsiveGridLayoutPreview;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import React, { ReactElement, RefObject, SyntheticEvent, useRef } from "react";
|
|
2
|
+
import DraggableCore from "../Draggable/DraggableCore";
|
|
3
|
+
import { PropsWithChildren } from "../Draggable/types";
|
|
4
|
+
import {
|
|
5
|
+
DragCallbackData,
|
|
6
|
+
ReactRef,
|
|
7
|
+
ResizableProps,
|
|
8
|
+
ResizeHandleAxis,
|
|
9
|
+
} from "./types";
|
|
10
|
+
import { cloneElement } from "./utils/cloneElement";
|
|
11
|
+
import "./styles/styles.css";
|
|
12
|
+
|
|
13
|
+
type HandleRefsType = {
|
|
14
|
+
[key in ResizeHandleAxis]: RefObject<HTMLElement>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type Props = ResizableProps;
|
|
18
|
+
|
|
19
|
+
const Resizable = ({
|
|
20
|
+
children,
|
|
21
|
+
axis = "both",
|
|
22
|
+
handleSize = [20, 20],
|
|
23
|
+
lockAspectRatio = false,
|
|
24
|
+
minConstraints = [20, 20],
|
|
25
|
+
maxConstraints = [Infinity, Infinity],
|
|
26
|
+
resizeHandles = ["se"],
|
|
27
|
+
transformScale = 1,
|
|
28
|
+
...props
|
|
29
|
+
}: PropsWithChildren<Props>) => {
|
|
30
|
+
const handleRefs = useRef<HandleRefsType>({
|
|
31
|
+
s: useRef<HTMLElement>(null),
|
|
32
|
+
n: useRef<HTMLElement>(null),
|
|
33
|
+
e: useRef<HTMLElement>(null),
|
|
34
|
+
w: useRef<HTMLElement>(null),
|
|
35
|
+
se: useRef<HTMLElement>(null),
|
|
36
|
+
sw: useRef<HTMLElement>(null),
|
|
37
|
+
ne: useRef<HTMLElement>(null),
|
|
38
|
+
nw: useRef<HTMLElement>(null),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
let startPosition = useRef<{ top: number; left: number } | null>(null);
|
|
42
|
+
let startSize = useRef<{ width: number; height: number } | null>(null);
|
|
43
|
+
|
|
44
|
+
let lastHandleRect: DOMRect | null = null;
|
|
45
|
+
let slack: [number, number] | null = null;
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
className,
|
|
49
|
+
draggableOpts,
|
|
50
|
+
width,
|
|
51
|
+
height,
|
|
52
|
+
handle,
|
|
53
|
+
onResize,
|
|
54
|
+
onResizeStop,
|
|
55
|
+
onResizeStart,
|
|
56
|
+
...restProps
|
|
57
|
+
} = props;
|
|
58
|
+
|
|
59
|
+
const resetData = () => {
|
|
60
|
+
lastHandleRect = slack = null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// NOTE - 최소 또는 최대 제약 조건 width와 height 계산, 비율 고정 계산
|
|
64
|
+
const checkConstraints = (
|
|
65
|
+
width: number,
|
|
66
|
+
height: number
|
|
67
|
+
): [number, number] => {
|
|
68
|
+
if (!minConstraints && !maxConstraints && !lockAspectRatio) {
|
|
69
|
+
return [width, height];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (lockAspectRatio) {
|
|
73
|
+
const ratio = props.width / props.height;
|
|
74
|
+
const deltaWidth = width - props.width;
|
|
75
|
+
const deltaHeight = height - props.height;
|
|
76
|
+
|
|
77
|
+
if (Math.abs(deltaWidth) > Math.abs(deltaHeight * ratio)) {
|
|
78
|
+
height = width / ratio;
|
|
79
|
+
} else {
|
|
80
|
+
width = height * ratio;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [oldW, oldH] = [width, height];
|
|
85
|
+
|
|
86
|
+
let [slackW, slackH] = slack || [0, 0];
|
|
87
|
+
|
|
88
|
+
width += slackW;
|
|
89
|
+
height += slackH;
|
|
90
|
+
|
|
91
|
+
if (minConstraints) {
|
|
92
|
+
width = Math.max(minConstraints[0], width);
|
|
93
|
+
|
|
94
|
+
height = Math.max(minConstraints[1], height);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (maxConstraints) {
|
|
98
|
+
width = Math.min(maxConstraints[0], width);
|
|
99
|
+
height = Math.min(maxConstraints[1], height);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
slack = [slackW + (oldW - width), slackH + (oldH - height)];
|
|
103
|
+
|
|
104
|
+
return [width, height];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const checkTopLeft = (
|
|
108
|
+
deltaX: number,
|
|
109
|
+
deltaY: number,
|
|
110
|
+
top: number,
|
|
111
|
+
left: number,
|
|
112
|
+
width: number,
|
|
113
|
+
height: number,
|
|
114
|
+
handleAxis: ResizeHandleAxis
|
|
115
|
+
): [number, number] => {
|
|
116
|
+
let newTop = top;
|
|
117
|
+
let newLeft = left;
|
|
118
|
+
|
|
119
|
+
if (!startPosition.current || !startSize.current) return [newTop, newLeft];
|
|
120
|
+
|
|
121
|
+
const currentSumOfLeftAndWidth = left + width;
|
|
122
|
+
const startSumOfLeftAndWidth =
|
|
123
|
+
startPosition.current.left + startSize.current.width;
|
|
124
|
+
|
|
125
|
+
const currentSumOfTopAndHeight = top + height;
|
|
126
|
+
const startSumOfTopAndHeight =
|
|
127
|
+
startPosition.current.top + startSize.current.height;
|
|
128
|
+
|
|
129
|
+
const resizableLeft =
|
|
130
|
+
startPosition.current.left + (startSize.current.width - width);
|
|
131
|
+
const resizableTop =
|
|
132
|
+
startPosition.current.top + (startSize.current.height - height);
|
|
133
|
+
|
|
134
|
+
if (deltaX < 0) {
|
|
135
|
+
if (handleAxis === "nw" || handleAxis === "w" || handleAxis === "sw") {
|
|
136
|
+
if (currentSumOfLeftAndWidth > startSumOfLeftAndWidth) {
|
|
137
|
+
newLeft = Math.min(left, resizableLeft);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (deltaY < 0) {
|
|
143
|
+
if (handleAxis === "nw" || handleAxis === "n" || handleAxis === "ne") {
|
|
144
|
+
if (currentSumOfTopAndHeight > startSumOfTopAndHeight) {
|
|
145
|
+
newTop = Math.min(top, resizableTop);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (deltaX > 0) {
|
|
151
|
+
if (handleAxis === "nw" || handleAxis === "w" || handleAxis === "sw") {
|
|
152
|
+
if (width >= minConstraints[0]) {
|
|
153
|
+
newLeft = Math.max(left, resizableLeft);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (deltaY > 0) {
|
|
159
|
+
if (handleAxis === "nw" || handleAxis === "n" || handleAxis === "ne") {
|
|
160
|
+
if (height >= minConstraints[1]) {
|
|
161
|
+
newTop = Math.max(top, resizableTop);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [newTop, newLeft];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const resizeHandler = (
|
|
170
|
+
handlerName: "onResize" | "onResizeStart" | "onResizeStop",
|
|
171
|
+
handleAxis: ResizeHandleAxis
|
|
172
|
+
): Function => {
|
|
173
|
+
return (
|
|
174
|
+
e: SyntheticEvent<any>,
|
|
175
|
+
{ node, deltaX, deltaY }: DragCallbackData
|
|
176
|
+
) => {
|
|
177
|
+
if (handlerName === "onResizeStart") {
|
|
178
|
+
const position = { top: props.top ?? 0, left: props.left ?? 0 };
|
|
179
|
+
startPosition.current = position;
|
|
180
|
+
startSize.current = {
|
|
181
|
+
width: props.width ?? 0,
|
|
182
|
+
height: props.height ?? 0,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
resetData();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const canDragX =
|
|
189
|
+
(axis === "both" || axis === "x") &&
|
|
190
|
+
handleAxis !== "n" &&
|
|
191
|
+
handleAxis !== "s";
|
|
192
|
+
const canDragY =
|
|
193
|
+
(axis === "both" || axis === "y") &&
|
|
194
|
+
handleAxis !== "e" &&
|
|
195
|
+
handleAxis !== "w";
|
|
196
|
+
|
|
197
|
+
if (!canDragX && !canDragY) return;
|
|
198
|
+
|
|
199
|
+
const axisV = handleAxis[0];
|
|
200
|
+
const axisH = handleAxis[handleAxis.length - 1];
|
|
201
|
+
const handleRect = node.getBoundingClientRect();
|
|
202
|
+
|
|
203
|
+
// if (lastHandleRect !== null) {
|
|
204
|
+
// if (axisH === "w") {
|
|
205
|
+
// const deltaLeftSinceLast = handleRect.left - lastHandleRect.left;
|
|
206
|
+
// deltaX += deltaLeftSinceLast;
|
|
207
|
+
// }
|
|
208
|
+
// if (axisV === "n") {
|
|
209
|
+
// const deltaTopSinceLast = handleRect.top - lastHandleRect.top;
|
|
210
|
+
// deltaY += deltaTopSinceLast;
|
|
211
|
+
// }
|
|
212
|
+
// }
|
|
213
|
+
|
|
214
|
+
lastHandleRect = handleRect;
|
|
215
|
+
|
|
216
|
+
if (axisH === "w") {
|
|
217
|
+
deltaX = -deltaX;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (axisV === "n") {
|
|
221
|
+
deltaY = -deltaY;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let width = props.width + (canDragX ? deltaX / transformScale : 0);
|
|
225
|
+
let height = props.height + (canDragY ? deltaY / transformScale : 0);
|
|
226
|
+
|
|
227
|
+
let left = props?.left ?? 0;
|
|
228
|
+
let top = props?.top ?? 0;
|
|
229
|
+
|
|
230
|
+
if (axisV === "n") {
|
|
231
|
+
top = top - deltaY;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (axisH === "w") {
|
|
235
|
+
left = left - deltaX;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
[width, height] = checkConstraints(width, height);
|
|
239
|
+
|
|
240
|
+
[top, left] = checkTopLeft(
|
|
241
|
+
deltaX,
|
|
242
|
+
deltaY,
|
|
243
|
+
top,
|
|
244
|
+
left,
|
|
245
|
+
width,
|
|
246
|
+
height,
|
|
247
|
+
handleAxis
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const isDimensionsChanged =
|
|
251
|
+
width !== props.width || height !== props.height;
|
|
252
|
+
|
|
253
|
+
const cb =
|
|
254
|
+
typeof props[handlerName] === "function" ? props[handlerName] : null;
|
|
255
|
+
const isCbSkipped = handlerName === "onResize" && !isDimensionsChanged;
|
|
256
|
+
|
|
257
|
+
if (cb && !isCbSkipped) {
|
|
258
|
+
cb(e, { node, size: { width, height, top, left }, handle: handleAxis });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (handlerName === "onResizeStop") {
|
|
262
|
+
startPosition.current = null;
|
|
263
|
+
startSize.current = null;
|
|
264
|
+
|
|
265
|
+
resetData();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const renderResizeHandle = (
|
|
271
|
+
handleAxis: ResizeHandleAxis,
|
|
272
|
+
ref: ReactRef<HTMLElement> | null
|
|
273
|
+
): ReactElement<any> => {
|
|
274
|
+
if (!handle) {
|
|
275
|
+
return (
|
|
276
|
+
<span
|
|
277
|
+
className={`react-resizable-handle react-resizable-handle-${handleAxis}`}
|
|
278
|
+
ref={ref}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof handle === "function") {
|
|
284
|
+
return handle(handleAxis, ref);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const isDOMElement = typeof handle.type === "string";
|
|
288
|
+
|
|
289
|
+
const props = {
|
|
290
|
+
ref,
|
|
291
|
+
...(isDOMElement ? {} : { handleAxis }),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return React.cloneElement(handle, props);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return cloneElement(children, {
|
|
298
|
+
...restProps,
|
|
299
|
+
className: `${className ? `${className} ` : ""}react-resizable`,
|
|
300
|
+
children: [
|
|
301
|
+
// ...children.props.children,
|
|
302
|
+
React.Children.map(children.props.children, (child) => child),
|
|
303
|
+
...resizeHandles.map((handleAxis) => {
|
|
304
|
+
const ref = handleRefs?.current && handleRefs.current[handleAxis];
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<DraggableCore
|
|
308
|
+
{...draggableOpts}
|
|
309
|
+
nodeRef={ref}
|
|
310
|
+
key={`resizableHandle-${handleAxis}`}
|
|
311
|
+
onStop={resizeHandler("onResizeStop", handleAxis)}
|
|
312
|
+
onStart={resizeHandler("onResizeStart", handleAxis)}
|
|
313
|
+
onDrag={resizeHandler("onResize", handleAxis)}
|
|
314
|
+
>
|
|
315
|
+
{renderResizeHandle(handleAxis, ref)}
|
|
316
|
+
</DraggableCore>
|
|
317
|
+
);
|
|
318
|
+
}),
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export default Resizable;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { SyntheticEvent, useState } from "react";
|
|
2
|
+
import { ResizableBoxState, ResizableProps, ResizeCallbackData } from "./types";
|
|
3
|
+
import { PropsWithChildren } from "../Draggable/types";
|
|
4
|
+
import Resizable from "./Resizable";
|
|
5
|
+
|
|
6
|
+
type ResizableBoxProps = ResizableProps & {
|
|
7
|
+
style?: Object;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const ResizableBox = ({
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}: PropsWithChildren<ResizableBoxProps>) => {
|
|
14
|
+
const {
|
|
15
|
+
handle,
|
|
16
|
+
handleSize,
|
|
17
|
+
onResize,
|
|
18
|
+
onResizeStart,
|
|
19
|
+
onResizeStop,
|
|
20
|
+
draggableOpts,
|
|
21
|
+
minConstraints,
|
|
22
|
+
maxConstraints,
|
|
23
|
+
lockAspectRatio,
|
|
24
|
+
axis,
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
resizeHandles,
|
|
28
|
+
style,
|
|
29
|
+
transformScale,
|
|
30
|
+
...restProps
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const [resizableBoxState, setResizableBoxState] = useState<ResizableBoxState>(
|
|
34
|
+
{
|
|
35
|
+
width: width,
|
|
36
|
+
height: height,
|
|
37
|
+
propsWidth: width,
|
|
38
|
+
propsHeight: height,
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const handleResize = (
|
|
43
|
+
e: SyntheticEvent<any>,
|
|
44
|
+
data: ResizeCallbackData
|
|
45
|
+
): void => {
|
|
46
|
+
const { size } = data;
|
|
47
|
+
|
|
48
|
+
if (props.onResize) {
|
|
49
|
+
setResizableBoxState((prevState) => ({
|
|
50
|
+
...prevState,
|
|
51
|
+
width: size.width,
|
|
52
|
+
height: size.height,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
props.onResize(e, data);
|
|
56
|
+
} else {
|
|
57
|
+
setResizableBoxState((prevState) => ({
|
|
58
|
+
...prevState,
|
|
59
|
+
width: size.width,
|
|
60
|
+
height: size.height,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// NOTE - class형 컴포넌트에서 사용되는 getDerivedStateFromProps에 대응하기 위한 조건문입니다.
|
|
66
|
+
if (
|
|
67
|
+
width !== resizableBoxState.propsWidth ||
|
|
68
|
+
height !== resizableBoxState.propsHeight
|
|
69
|
+
) {
|
|
70
|
+
setResizableBoxState({
|
|
71
|
+
width: width,
|
|
72
|
+
height: height,
|
|
73
|
+
propsWidth: width,
|
|
74
|
+
propsHeight: height,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<Resizable
|
|
81
|
+
axis={axis}
|
|
82
|
+
draggableOpts={draggableOpts}
|
|
83
|
+
handle={handle}
|
|
84
|
+
handleSize={handleSize}
|
|
85
|
+
height={resizableBoxState.height}
|
|
86
|
+
lockAspectRatio={lockAspectRatio}
|
|
87
|
+
maxConstraints={maxConstraints}
|
|
88
|
+
minConstraints={minConstraints}
|
|
89
|
+
onResizeStart={onResizeStart}
|
|
90
|
+
onResize={handleResize}
|
|
91
|
+
onResizeStop={onResizeStop}
|
|
92
|
+
resizeHandles={resizeHandles}
|
|
93
|
+
transformScale={transformScale}
|
|
94
|
+
width={resizableBoxState.width}
|
|
95
|
+
>
|
|
96
|
+
<div
|
|
97
|
+
{...restProps}
|
|
98
|
+
style={{
|
|
99
|
+
...style,
|
|
100
|
+
width: resizableBoxState.width + "px",
|
|
101
|
+
height: resizableBoxState.height + "px",
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
</Resizable>
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default ResizableBox;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Resizable } from "./Resizable";
|