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,737 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
import { CompactType, Layout, LayoutItem, Position } from "../types";
|
|
4
|
+
|
|
5
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
6
|
+
const DEBUG = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Return the bottom coordinate of the layout.
|
|
10
|
+
*
|
|
11
|
+
* @param {Array} layout Layout array.
|
|
12
|
+
* @return {Number} Bottom coordinate.
|
|
13
|
+
*/
|
|
14
|
+
export function bottom(layout: Layout): number {
|
|
15
|
+
let max = 0,
|
|
16
|
+
bottomY;
|
|
17
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
18
|
+
bottomY = layout[i].y + layout[i].h;
|
|
19
|
+
if (bottomY > max) max = bottomY;
|
|
20
|
+
}
|
|
21
|
+
return max;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function cloneLayout(layout: Layout): Layout {
|
|
25
|
+
const newLayout = Array(layout.length);
|
|
26
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
27
|
+
newLayout[i] = cloneLayoutItem(layout[i]);
|
|
28
|
+
}
|
|
29
|
+
return newLayout;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function modifyLayout(layout: Layout, layoutItem: LayoutItem): Layout {
|
|
33
|
+
const newLayout = Array(layout.length);
|
|
34
|
+
|
|
35
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
36
|
+
if (layoutItem.i === layout[i].i) {
|
|
37
|
+
newLayout[i] = layoutItem;
|
|
38
|
+
} else {
|
|
39
|
+
newLayout[i] = layout[i];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return newLayout;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Function to be called to modify a layout item.
|
|
47
|
+
// Does defensive clones to ensure the layout is not modified.
|
|
48
|
+
export function withLayoutItem(
|
|
49
|
+
layout: Layout,
|
|
50
|
+
itemKey: string,
|
|
51
|
+
cb: (l: LayoutItem) => LayoutItem
|
|
52
|
+
): [Layout, LayoutItem | null] {
|
|
53
|
+
let item = getLayoutItem(layout, itemKey);
|
|
54
|
+
|
|
55
|
+
if (!item) return [layout, null];
|
|
56
|
+
|
|
57
|
+
item = cb(cloneLayoutItem(item)); // defensive clone then modify
|
|
58
|
+
|
|
59
|
+
// FIXME could do this faster if we already knew the index
|
|
60
|
+
layout = modifyLayout(layout, item);
|
|
61
|
+
|
|
62
|
+
return [layout, item];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fast path to cloning, since this is monomorphic
|
|
66
|
+
export function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {
|
|
67
|
+
return {
|
|
68
|
+
w: layoutItem.w,
|
|
69
|
+
h: layoutItem.h,
|
|
70
|
+
x: layoutItem.x,
|
|
71
|
+
y: layoutItem.y,
|
|
72
|
+
z: layoutItem.z,
|
|
73
|
+
i: layoutItem.i,
|
|
74
|
+
minW: layoutItem.minW,
|
|
75
|
+
maxW: layoutItem.maxW,
|
|
76
|
+
minH: layoutItem.minH,
|
|
77
|
+
maxH: layoutItem.maxH,
|
|
78
|
+
moved: Boolean(layoutItem.moved),
|
|
79
|
+
static: Boolean(layoutItem.static),
|
|
80
|
+
isDraggable: layoutItem.isDraggable,
|
|
81
|
+
isResizable: layoutItem.isResizable,
|
|
82
|
+
resizeHandles: layoutItem.resizeHandles,
|
|
83
|
+
isBounded: layoutItem.isBounded,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Comparing React `children` is a bit difficult. This is a good way to compare them.
|
|
89
|
+
* This will catch differences in keys, order, and length.
|
|
90
|
+
*/
|
|
91
|
+
// NOTE - 쓰이는 곳이 없음
|
|
92
|
+
// export function childrenEqual(a: ReactChildren, b: ReactChildren): boolean {
|
|
93
|
+
// return isEqual(
|
|
94
|
+
// React.Children.map(a, (c) => c?.["key"]),
|
|
95
|
+
// React.Children.map(b, (c) => c?.["key"])
|
|
96
|
+
// );
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
export function fastPositionEqual(a: Position, b: Position): boolean {
|
|
100
|
+
return (
|
|
101
|
+
a.left === b.left &&
|
|
102
|
+
a.top === b.top &&
|
|
103
|
+
a.width === b.width &&
|
|
104
|
+
a.height === b.height
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Given two layoutItems, check if they collide.
|
|
110
|
+
*/
|
|
111
|
+
export function collides(l1: LayoutItem, l2: LayoutItem): boolean {
|
|
112
|
+
if (l1.i === l2.i) return false; // same element
|
|
113
|
+
if (l1.x + l1.w <= l2.x) return false; // l1 is left of l2
|
|
114
|
+
if (l1.x >= l2.x + l2.w) return false; // l1 is right of l2
|
|
115
|
+
if (l1.y + l1.h <= l2.y) return false; // l1 is above l2
|
|
116
|
+
if (l1.y >= l2.y + l2.h) return false; // l1 is below l2
|
|
117
|
+
|
|
118
|
+
return true; // boxes overlap
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Given a layout, compact it. This involves going down each y coordinate and removing gaps
|
|
123
|
+
* between items.
|
|
124
|
+
*
|
|
125
|
+
* Does not modify layout items (clones). Creates a new layout array.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array} layout Layout.
|
|
128
|
+
* @param {Boolean} verticalCompact Whether or not to compact the layout
|
|
129
|
+
* vertically.
|
|
130
|
+
* @param {Boolean} allowOverlap When `true`, allows overlapping grid items.
|
|
131
|
+
* @return {Array} Compacted Layout.
|
|
132
|
+
*/
|
|
133
|
+
export function compact(
|
|
134
|
+
layout: Layout,
|
|
135
|
+
compactType: CompactType,
|
|
136
|
+
cols: number,
|
|
137
|
+
allowOverlap?: boolean
|
|
138
|
+
): Layout {
|
|
139
|
+
const compareWith = getStatics(layout);
|
|
140
|
+
const sorted = sortLayoutItems(layout, compactType);
|
|
141
|
+
const out = Array(layout.length);
|
|
142
|
+
|
|
143
|
+
for (let i = 0, len = sorted.length; i < len; i++) {
|
|
144
|
+
let l = cloneLayoutItem(sorted[i]);
|
|
145
|
+
|
|
146
|
+
// Don't move static elements
|
|
147
|
+
if (!l.static) {
|
|
148
|
+
l = compactItem(compareWith, l, compactType, cols, sorted, allowOverlap);
|
|
149
|
+
|
|
150
|
+
// Add to comparison array. We only collide with items before this one.
|
|
151
|
+
// Statics are already in this array.
|
|
152
|
+
compareWith.push(l);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Add to output array to make sure they still come out in the right order.
|
|
156
|
+
out[layout.indexOf(sorted[i])] = l;
|
|
157
|
+
|
|
158
|
+
// Clear moved flag, if it exists.
|
|
159
|
+
l.moved = false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const heightWidth = { x: "w", y: "h" };
|
|
166
|
+
/**
|
|
167
|
+
* Before moving item down, it will check if the movement will cause collisions and move those items down before.
|
|
168
|
+
*/
|
|
169
|
+
function resolveCompactionCollision(
|
|
170
|
+
layout: Layout,
|
|
171
|
+
item: LayoutItem,
|
|
172
|
+
moveToCoord: number,
|
|
173
|
+
axis: "x" | "y"
|
|
174
|
+
) {
|
|
175
|
+
const sizeProp = heightWidth[axis] as keyof typeof heightWidth;
|
|
176
|
+
item[axis] += 1;
|
|
177
|
+
const itemIndex = layout
|
|
178
|
+
.map((layoutItem) => {
|
|
179
|
+
return layoutItem.i;
|
|
180
|
+
})
|
|
181
|
+
.indexOf(item.i);
|
|
182
|
+
|
|
183
|
+
for (let i = itemIndex + 1; i < layout.length; i++) {
|
|
184
|
+
const otherItem = layout[i];
|
|
185
|
+
// Ignore static items
|
|
186
|
+
if (otherItem.static) continue;
|
|
187
|
+
|
|
188
|
+
if (otherItem.y > item.y + item.h) break;
|
|
189
|
+
|
|
190
|
+
if (collides(item, otherItem)) {
|
|
191
|
+
resolveCompactionCollision(
|
|
192
|
+
layout,
|
|
193
|
+
otherItem,
|
|
194
|
+
moveToCoord + item[sizeProp],
|
|
195
|
+
axis
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
item[axis] = moveToCoord;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Compact an item in the layout.
|
|
205
|
+
*
|
|
206
|
+
* Modifies item.
|
|
207
|
+
*
|
|
208
|
+
*/
|
|
209
|
+
export function compactItem(
|
|
210
|
+
compareWith: Layout,
|
|
211
|
+
l: LayoutItem,
|
|
212
|
+
compactType: CompactType,
|
|
213
|
+
cols: number,
|
|
214
|
+
fullLayout: Layout,
|
|
215
|
+
allowOverlap?: boolean
|
|
216
|
+
): LayoutItem {
|
|
217
|
+
const compactV = compactType === "vertical";
|
|
218
|
+
const compactH = compactType === "horizontal";
|
|
219
|
+
|
|
220
|
+
if (compactV) {
|
|
221
|
+
l.y = Math.min(bottom(compareWith), l.y);
|
|
222
|
+
|
|
223
|
+
while (l.y > 0 && !getFirstCollision(compareWith, l)) {
|
|
224
|
+
l.y--;
|
|
225
|
+
}
|
|
226
|
+
} else if (compactH) {
|
|
227
|
+
while (l.x > 0 && !getFirstCollision(compareWith, l)) {
|
|
228
|
+
l.x--;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let collides;
|
|
233
|
+
|
|
234
|
+
while (
|
|
235
|
+
(collides = getFirstCollision(compareWith, l)) &&
|
|
236
|
+
!(compactType === null && allowOverlap)
|
|
237
|
+
) {
|
|
238
|
+
if (compactH) {
|
|
239
|
+
resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
|
|
240
|
+
} else {
|
|
241
|
+
resolveCompactionCollision(fullLayout, l, collides.y + collides.h, "y");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (compactH && l.x + l.w > cols) {
|
|
245
|
+
l.x = cols - l.w;
|
|
246
|
+
l.y++;
|
|
247
|
+
|
|
248
|
+
while (l.x > 0 && !getFirstCollision(compareWith, l)) {
|
|
249
|
+
l.x--;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
l.y = Math.max(l.y, 0);
|
|
255
|
+
l.x = Math.max(l.x, 0);
|
|
256
|
+
|
|
257
|
+
return l;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Given a layout, make sure all elements fit within its bounds.
|
|
262
|
+
*
|
|
263
|
+
* Modifies layout items.
|
|
264
|
+
*
|
|
265
|
+
* @param {Array} layout Layout array.
|
|
266
|
+
* @param {Number} bounds Number of columns.
|
|
267
|
+
*/
|
|
268
|
+
export function correctBounds(
|
|
269
|
+
layout: Layout,
|
|
270
|
+
bounds: { cols: number }
|
|
271
|
+
): Layout {
|
|
272
|
+
const collidesWith = getStatics(layout);
|
|
273
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
274
|
+
const l = layout[i];
|
|
275
|
+
|
|
276
|
+
if (l.x + l.w > bounds.cols) l.x = bounds.cols - l.w;
|
|
277
|
+
|
|
278
|
+
if (l.x < 0) {
|
|
279
|
+
l.x = 0;
|
|
280
|
+
l.w = bounds.cols;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (l.z == null) {
|
|
284
|
+
l.z = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!l.static) collidesWith.push(l);
|
|
288
|
+
else {
|
|
289
|
+
while (getFirstCollision(collidesWith, l)) {
|
|
290
|
+
l.y++;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return layout;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get a layout item by ID. Used so we can override later on if necessary.
|
|
299
|
+
*
|
|
300
|
+
* @param {Array} layout Layout array.
|
|
301
|
+
* @param {String} id ID
|
|
302
|
+
* @return {LayoutItem} Item at ID.
|
|
303
|
+
*/
|
|
304
|
+
export function getLayoutItem(
|
|
305
|
+
layout: Layout,
|
|
306
|
+
id: string
|
|
307
|
+
): LayoutItem | undefined {
|
|
308
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
309
|
+
if (layout[i].i === id) return layout[i];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Returns the first item this layout collides with.
|
|
315
|
+
* It doesn't appear to matter which order we approach this from, although
|
|
316
|
+
* perhaps that is the wrong thing to do.
|
|
317
|
+
*
|
|
318
|
+
* @param {Object} layoutItem Layout item.
|
|
319
|
+
* @return {Object|undefined} A colliding layout item, or undefined.
|
|
320
|
+
*/
|
|
321
|
+
export function getFirstCollision(
|
|
322
|
+
layout: Layout,
|
|
323
|
+
layoutItem: LayoutItem
|
|
324
|
+
): LayoutItem | undefined {
|
|
325
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
326
|
+
if (collides(layout[i], layoutItem)) return layout[i];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function getAllCollisions(
|
|
331
|
+
layout: Layout,
|
|
332
|
+
layoutItem: LayoutItem
|
|
333
|
+
): Array<LayoutItem> {
|
|
334
|
+
return layout.filter((l) => collides(l, layoutItem));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get all static elements.
|
|
339
|
+
* @param {Array} layout Array of layout objects.
|
|
340
|
+
* @return {Array} Array of static layout items..
|
|
341
|
+
*/
|
|
342
|
+
export function getStatics(layout: Layout): Array<LayoutItem> {
|
|
343
|
+
return layout.filter((l) => l.static);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Move an element. Responsible for doing cascading movements of other elements.
|
|
348
|
+
*
|
|
349
|
+
* Modifies layout items.
|
|
350
|
+
*
|
|
351
|
+
* @param {Array} layout Full layout to modify.
|
|
352
|
+
* @param {LayoutItem} l element to move.
|
|
353
|
+
* @param {Number} [x] X position in grid units.
|
|
354
|
+
* @param {Number} [y] Y position in grid units.
|
|
355
|
+
*/
|
|
356
|
+
export function moveElement(properties: {
|
|
357
|
+
layout: Layout;
|
|
358
|
+
l: LayoutItem;
|
|
359
|
+
x?: number;
|
|
360
|
+
y?: number;
|
|
361
|
+
z?: number;
|
|
362
|
+
isUserAction?: boolean;
|
|
363
|
+
preventCollision?: boolean;
|
|
364
|
+
compactType: CompactType;
|
|
365
|
+
cols: number;
|
|
366
|
+
allowOverlap?: boolean;
|
|
367
|
+
}): Layout {
|
|
368
|
+
const {
|
|
369
|
+
l,
|
|
370
|
+
x,
|
|
371
|
+
y,
|
|
372
|
+
z,
|
|
373
|
+
isUserAction,
|
|
374
|
+
preventCollision,
|
|
375
|
+
cols,
|
|
376
|
+
compactType,
|
|
377
|
+
allowOverlap,
|
|
378
|
+
} = properties;
|
|
379
|
+
let { layout } = properties;
|
|
380
|
+
if (l.static && l.isDraggable !== true) return layout;
|
|
381
|
+
|
|
382
|
+
if (l.y === y && l.x === x && l.z === z) return layout;
|
|
383
|
+
|
|
384
|
+
log(
|
|
385
|
+
`Moving element ${l.i} to [${String(x)},${String(y)}] from [${l.x},${l.y}]`
|
|
386
|
+
);
|
|
387
|
+
const oldX = l.x;
|
|
388
|
+
const oldY = l.y;
|
|
389
|
+
const oldZ = l.z;
|
|
390
|
+
|
|
391
|
+
// This is quite a bit faster than extending the object
|
|
392
|
+
if (typeof x === "number") l.x = x;
|
|
393
|
+
if (typeof y === "number") l.y = y;
|
|
394
|
+
if (typeof z === "number") l.z = y;
|
|
395
|
+
|
|
396
|
+
l.moved = true;
|
|
397
|
+
|
|
398
|
+
// If this collides with anything, move it.
|
|
399
|
+
// When doing this comparison, we have to sort the items we compare with
|
|
400
|
+
// to ensure, in the case of multiple collisions, that we're getting the
|
|
401
|
+
// nearest collision.
|
|
402
|
+
let sorted = sortLayoutItems(layout, compactType);
|
|
403
|
+
const movingUp =
|
|
404
|
+
compactType === "vertical" && typeof y === "number"
|
|
405
|
+
? oldY >= y
|
|
406
|
+
: compactType === "horizontal" && typeof x === "number"
|
|
407
|
+
? oldX >= x
|
|
408
|
+
: false;
|
|
409
|
+
// $FlowIgnore acceptable modification of read-only array as it was recently cloned
|
|
410
|
+
if (movingUp) sorted = sorted.reverse();
|
|
411
|
+
const collisions = getAllCollisions(sorted, l);
|
|
412
|
+
const hasCollisions = collisions.length > 0;
|
|
413
|
+
|
|
414
|
+
// We may have collisions. We can short-circuit if we've turned off collisions or
|
|
415
|
+
// allowed overlap.
|
|
416
|
+
if (hasCollisions && allowOverlap) {
|
|
417
|
+
// Easy, we don't need to resolve collisions. But we can make sure the newly movedo item appears on top!
|
|
418
|
+
const maxZ = Math.max(
|
|
419
|
+
...collisions.map((x) => x.z ?? Number.MIN_SAFE_INTEGER)
|
|
420
|
+
);
|
|
421
|
+
l.z = maxZ + 1;
|
|
422
|
+
return cloneLayout(layout);
|
|
423
|
+
} else if (hasCollisions && preventCollision) {
|
|
424
|
+
// If we are preventing collision but not allowing overlap, we need to
|
|
425
|
+
// revert the position of this element so it goes to where it came from, rather
|
|
426
|
+
// than the user's desired location.
|
|
427
|
+
log(`Collision prevented on ${l.i}, reverting.`);
|
|
428
|
+
l.x = oldX;
|
|
429
|
+
l.y = oldY;
|
|
430
|
+
l.z = oldZ;
|
|
431
|
+
l.moved = false;
|
|
432
|
+
return layout; // did not change so don't clone
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Move each item that collides away from this element.
|
|
436
|
+
for (let i = 0, len = collisions.length; i < len; i++) {
|
|
437
|
+
const collision = collisions[i];
|
|
438
|
+
log(
|
|
439
|
+
`Resolving collision between ${l.i} at [${l.x},${l.y}] and ${collision.i} at [${collision.x},${collision.y}]`
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// Short circuit so we can't infinite loop
|
|
443
|
+
if (collision.moved) continue;
|
|
444
|
+
|
|
445
|
+
// Don't move static items - we have to move *this* element away
|
|
446
|
+
if (collision.static) {
|
|
447
|
+
layout = moveElementAwayFromCollision({
|
|
448
|
+
layout,
|
|
449
|
+
collidesWith: collision,
|
|
450
|
+
itemToMove: l,
|
|
451
|
+
isUserAction,
|
|
452
|
+
compactType,
|
|
453
|
+
cols,
|
|
454
|
+
});
|
|
455
|
+
} else {
|
|
456
|
+
layout = moveElementAwayFromCollision({
|
|
457
|
+
layout,
|
|
458
|
+
collidesWith: l,
|
|
459
|
+
itemToMove: collision,
|
|
460
|
+
isUserAction,
|
|
461
|
+
compactType,
|
|
462
|
+
cols,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return layout;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* This is where the magic needs to happen - given a collision, move an element away from the collision.
|
|
472
|
+
* We attempt to move it up if there's room, otherwise it goes below.
|
|
473
|
+
*
|
|
474
|
+
* @param {Array} layout Full layout to modify.
|
|
475
|
+
* @param {LayoutItem} collidesWith Layout item we're colliding with.
|
|
476
|
+
* @param {LayoutItem} itemToMove Layout item we're moving.
|
|
477
|
+
*/
|
|
478
|
+
export function moveElementAwayFromCollision(properties: {
|
|
479
|
+
layout: Layout;
|
|
480
|
+
collidesWith: LayoutItem;
|
|
481
|
+
itemToMove: LayoutItem;
|
|
482
|
+
isUserAction?: boolean;
|
|
483
|
+
compactType: CompactType;
|
|
484
|
+
cols: number;
|
|
485
|
+
}): Layout {
|
|
486
|
+
const { collidesWith, cols, compactType, itemToMove, layout } = properties;
|
|
487
|
+
let { isUserAction } = properties;
|
|
488
|
+
const compactH = compactType === "horizontal";
|
|
489
|
+
const compactV = compactType !== "horizontal";
|
|
490
|
+
const preventCollision = collidesWith.static; // we're already colliding (not for static items)
|
|
491
|
+
|
|
492
|
+
if (isUserAction) {
|
|
493
|
+
isUserAction = false;
|
|
494
|
+
|
|
495
|
+
const fakeItem: LayoutItem = {
|
|
496
|
+
x: compactH ? Math.max(collidesWith.x - itemToMove.w, 0) : itemToMove.x,
|
|
497
|
+
y: compactV ? Math.max(collidesWith.y - itemToMove.h, 0) : itemToMove.y,
|
|
498
|
+
z: itemToMove.z,
|
|
499
|
+
w: itemToMove.w,
|
|
500
|
+
h: itemToMove.h,
|
|
501
|
+
i: "-1",
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
if (!getFirstCollision(layout, fakeItem)) {
|
|
505
|
+
log(
|
|
506
|
+
`Doing reverse collision on ${itemToMove.i} up to [${fakeItem.x},${fakeItem.y}].`
|
|
507
|
+
);
|
|
508
|
+
return moveElement({
|
|
509
|
+
layout,
|
|
510
|
+
l: itemToMove,
|
|
511
|
+
x: compactH ? fakeItem.x : undefined,
|
|
512
|
+
y: compactV ? fakeItem.y : undefined,
|
|
513
|
+
isUserAction,
|
|
514
|
+
preventCollision,
|
|
515
|
+
compactType,
|
|
516
|
+
cols,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return moveElement({
|
|
522
|
+
layout,
|
|
523
|
+
l: itemToMove,
|
|
524
|
+
x: compactH ? itemToMove.x + 1 : undefined,
|
|
525
|
+
y: compactV ? itemToMove.y + 1 : undefined,
|
|
526
|
+
isUserAction,
|
|
527
|
+
preventCollision,
|
|
528
|
+
compactType,
|
|
529
|
+
cols,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Helper to convert a number to a percentage string.
|
|
535
|
+
*
|
|
536
|
+
* @param {Number} num Any number
|
|
537
|
+
* @return {String} That number as a percentage.
|
|
538
|
+
*/
|
|
539
|
+
export function perc(num: number): string {
|
|
540
|
+
return num * 100 + "%";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function setTransform({
|
|
544
|
+
top,
|
|
545
|
+
left,
|
|
546
|
+
z,
|
|
547
|
+
width,
|
|
548
|
+
height,
|
|
549
|
+
}: Position): Record<string, string | number> {
|
|
550
|
+
const translate = `translate(${left}px,${top}px)`;
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
transform: translate,
|
|
554
|
+
WebkitTransform: translate,
|
|
555
|
+
MozTransform: translate,
|
|
556
|
+
msTransform: translate,
|
|
557
|
+
OTransform: translate,
|
|
558
|
+
width: `${width}px`,
|
|
559
|
+
height: `${height}px`,
|
|
560
|
+
zIndex: z || 0,
|
|
561
|
+
position: "absolute",
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export function setTopLeft({
|
|
566
|
+
top,
|
|
567
|
+
left,
|
|
568
|
+
z,
|
|
569
|
+
width,
|
|
570
|
+
height,
|
|
571
|
+
}: Position): Record<string, string | number> {
|
|
572
|
+
return {
|
|
573
|
+
top: `${top}px`,
|
|
574
|
+
left: `${left}px`,
|
|
575
|
+
width: `${width}px`,
|
|
576
|
+
height: `${height}px`,
|
|
577
|
+
zIndex: z || 0,
|
|
578
|
+
position: "absolute",
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Get layout items sorted from top left to right and down.
|
|
584
|
+
*
|
|
585
|
+
* @return {Array} Array of layout objects.
|
|
586
|
+
* @return {Array} Layout, sorted static items first.
|
|
587
|
+
*/
|
|
588
|
+
export function sortLayoutItems(
|
|
589
|
+
layout: Layout,
|
|
590
|
+
compactType: CompactType
|
|
591
|
+
): Layout {
|
|
592
|
+
if (compactType === "horizontal") return sortLayoutItemsByColRow(layout);
|
|
593
|
+
if (compactType === "vertical") return sortLayoutItemsByRowCol(layout);
|
|
594
|
+
else return layout;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Sort layout items by row ascending and column ascending.
|
|
599
|
+
*
|
|
600
|
+
* Does not modify Layout.
|
|
601
|
+
*/
|
|
602
|
+
export function sortLayoutItemsByRowCol(layout: Layout): Layout {
|
|
603
|
+
return layout.slice(0).sort(function (a, b) {
|
|
604
|
+
if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
|
|
605
|
+
return 1;
|
|
606
|
+
} else if (a.y === b.y && a.x === b.x) {
|
|
607
|
+
// Without this, we can get different sort results in IE vs. Chrome/FF
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
return -1;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Sort layout items by column ascending then row ascending.
|
|
616
|
+
*
|
|
617
|
+
* Does not modify Layout.
|
|
618
|
+
*/
|
|
619
|
+
export function sortLayoutItemsByColRow(layout: Layout): Layout {
|
|
620
|
+
return layout.slice(0).sort(function (a, b) {
|
|
621
|
+
if (a.x > b.x || (a.x === b.x && a.y > b.y)) {
|
|
622
|
+
return 1;
|
|
623
|
+
}
|
|
624
|
+
return -1;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Generate a layout using the initialLayout and children as a template.
|
|
630
|
+
* Missing entries will be added, extraneous ones will be truncated.
|
|
631
|
+
*
|
|
632
|
+
* Does not modify initialLayout.
|
|
633
|
+
*
|
|
634
|
+
* @param {Array} initialLayout Layout passed in through props.
|
|
635
|
+
* @param {String} breakpoint Current responsive breakpoint.
|
|
636
|
+
* @param {?String} compact Compaction option.
|
|
637
|
+
* @return {Array} Working layout.
|
|
638
|
+
*/
|
|
639
|
+
export function synchronizeLayoutWithChildren(
|
|
640
|
+
initialLayout: Layout,
|
|
641
|
+
children: ReactElement | ReactElement[],
|
|
642
|
+
cols: number,
|
|
643
|
+
compactType: CompactType,
|
|
644
|
+
allowOverlap?: boolean
|
|
645
|
+
): Layout {
|
|
646
|
+
initialLayout = initialLayout || [];
|
|
647
|
+
|
|
648
|
+
const layout: LayoutItem[] = [];
|
|
649
|
+
|
|
650
|
+
children &&
|
|
651
|
+
React.Children.forEach(
|
|
652
|
+
Array.isArray(children) ? children : [children],
|
|
653
|
+
(child: ReactElement<any>) => {
|
|
654
|
+
if (child?.key == null) return;
|
|
655
|
+
|
|
656
|
+
const exists = getLayoutItem(initialLayout, String(child.key));
|
|
657
|
+
|
|
658
|
+
if (exists) {
|
|
659
|
+
layout.push(cloneLayoutItem(exists));
|
|
660
|
+
} else {
|
|
661
|
+
if (!isProduction && child.props._grid) {
|
|
662
|
+
console.warn(
|
|
663
|
+
"`_grid` properties on children have been deprecated as of React 15.2. " +
|
|
664
|
+
"Please use `data-grid` or add your properties directly to the `layout`."
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const g = child.props["data-grid"] || child.props._grid;
|
|
669
|
+
|
|
670
|
+
if (g) {
|
|
671
|
+
if (!isProduction) {
|
|
672
|
+
validateLayout([g], "GridLayout.children");
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
layout.push(cloneLayoutItem({ ...g, i: child.key }));
|
|
676
|
+
} else {
|
|
677
|
+
layout.push(
|
|
678
|
+
cloneLayoutItem({
|
|
679
|
+
w: 1,
|
|
680
|
+
h: 1,
|
|
681
|
+
x: 0,
|
|
682
|
+
y: bottom(layout),
|
|
683
|
+
z: 0,
|
|
684
|
+
i: String(child.key),
|
|
685
|
+
})
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const correctedLayout = correctBounds(layout, { cols: cols });
|
|
693
|
+
|
|
694
|
+
return allowOverlap
|
|
695
|
+
? correctedLayout
|
|
696
|
+
: compact(correctedLayout, compactType, cols);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Validate a layout. Throws errors.
|
|
701
|
+
*
|
|
702
|
+
* @param {Array} layout Array of layout items.
|
|
703
|
+
* @param {String} [contextName] Context name for errors.
|
|
704
|
+
* @throw {Error} Validation error.
|
|
705
|
+
*/
|
|
706
|
+
export function validateLayout(layout: Layout, contextName = "Layout"): void {
|
|
707
|
+
const subProps = ["x", "y", "w", "h"] as const;
|
|
708
|
+
|
|
709
|
+
if (!Array.isArray(layout)) {
|
|
710
|
+
throw new Error(contextName + " must be an array!");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
for (let i = 0, len = layout.length; i < len; i++) {
|
|
714
|
+
const item = layout[i];
|
|
715
|
+
|
|
716
|
+
for (let j = 0; j < subProps.length; j++) {
|
|
717
|
+
if (typeof item[subProps[j]] !== "number") {
|
|
718
|
+
throw new Error(
|
|
719
|
+
"GridLayout: " +
|
|
720
|
+
contextName +
|
|
721
|
+
"[" +
|
|
722
|
+
i +
|
|
723
|
+
"]." +
|
|
724
|
+
subProps[j] +
|
|
725
|
+
" must be a number!"
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function log(...args: any) {
|
|
733
|
+
if (!DEBUG) return;
|
|
734
|
+
console.log(...args);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export const noop: (args: any) => any = (_args: any) => undefined;
|