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,662 @@
1
+ import * as React from "react";
2
+ import { useState, useRef, useEffect } from "react";
3
+
4
+ import type { ReactElement } from "react";
5
+ import { PropsWithChildren } from "../../utils/types";
6
+ import {
7
+ DragOverEvent,
8
+ Layout,
9
+ LayoutItem,
10
+ PositionParams,
11
+ ReactGridLayoutProps,
12
+ } from "./types";
13
+ import {
14
+ DroppingPosition,
15
+ GridDragEvent,
16
+ GridResizeEvent,
17
+ } from "../GridItem/types";
18
+ import classNames from "classnames";
19
+ import {
20
+ bottom,
21
+ cloneLayoutItem,
22
+ compact,
23
+ getAllCollisions,
24
+ getLayoutItem,
25
+ moveElement,
26
+ noop,
27
+ synchronizeLayoutWithChildren,
28
+ withLayoutItem,
29
+ } from "./utils/renderHelpers";
30
+ import {
31
+ calcGridColWidth,
32
+ calcXY,
33
+ resolveRowHeight,
34
+ } from "../GridItem/utils/calculateUtils";
35
+ import GridItem from "../GridItem/GridItem";
36
+ import { isEqual } from "lodash";
37
+
38
+ import "./styles/styles.css";
39
+
40
+ const layoutClassName = "react-grid-layout";
41
+
42
+ const ReactGridLayout = ({
43
+ children,
44
+ ...props
45
+ }: PropsWithChildren<ReactGridLayoutProps>) => {
46
+ const {
47
+ autoSize = true,
48
+ cols = 12,
49
+ className = "",
50
+ style = {},
51
+ draggableHandle = "",
52
+ draggableCancel = "",
53
+ containerPadding = undefined,
54
+ rowHeight = 150,
55
+ maxRows = Infinity,
56
+ margin = [10, 10],
57
+ isBounded = false,
58
+ isDraggable = true,
59
+ isResizable = true,
60
+ allowOverlap = false,
61
+ isDroppable = false,
62
+ useCSSTransforms = true,
63
+ transformScale = 1,
64
+ compactType = "vertical",
65
+ preventCollision = false,
66
+ droppingItem = {
67
+ i: "__dropping-elem__",
68
+ h: 1,
69
+ w: 1,
70
+ } as any, // TODO fix
71
+ resizeHandles = ["se"],
72
+ width = 0,
73
+ resizeHandle,
74
+ isHiddenVisibility = true,
75
+ innerRef,
76
+ } = props;
77
+
78
+ const [activeDrag, setActiveDrag] = useState<LayoutItem>();
79
+ const [oldDragItem, setOldDragItem] = useState<LayoutItem>();
80
+ const [oldLayout, setOldLayout] = useState<Layout>();
81
+ const [oldResizeItem, setOldResizeItem] = useState<LayoutItem>();
82
+ const [droppingDOMNode, setDroppingDOMNode] = useState<ReactElement>();
83
+ const [droppingPosition, setDroppingPosition] = useState<DroppingPosition>();
84
+ const [isMounted, setIsMounted] = useState<boolean>(false);
85
+
86
+ const [layout, setLayout] = useState<Layout>(() =>
87
+ synchronizeLayoutWithChildren(
88
+ props.layout || [],
89
+ children,
90
+ cols,
91
+ compactType,
92
+ allowOverlap
93
+ )
94
+ );
95
+
96
+ const dragEnterCounter = useRef(0);
97
+
98
+ useEffect(() => {
99
+ setIsMounted(true);
100
+ onLayoutMaybeChanged(layout, layout);
101
+ }, []);
102
+
103
+ useEffect(() => {
104
+ if (props.layout && !Array.isArray(props.layout)) {
105
+ console.error("Expecting layout to be an Array but got: ", props.layout);
106
+ }
107
+
108
+ setLayout(
109
+ synchronizeLayoutWithChildren(
110
+ props.layout || [],
111
+ children,
112
+ cols,
113
+ compactType,
114
+ allowOverlap
115
+ )
116
+ );
117
+ }, [props.layout]);
118
+
119
+ useEffect(() => {
120
+ const newLayout = synchronizeLayoutWithChildren(
121
+ props.layout || layout,
122
+ children,
123
+ props.cols || cols,
124
+ props.compactType || "vertical",
125
+ props.allowOverlap
126
+ );
127
+
128
+ setLayout(newLayout);
129
+ }, [children]);
130
+
131
+ /**
132
+ * Calculates a pixel value for the container.
133
+ * @return {String} Container height in pixels.
134
+ */
135
+ const containerHeight = (): string | undefined => {
136
+ if (!autoSize) return;
137
+
138
+ const nbRow = bottom(layout);
139
+ const containerPaddingY = containerPadding
140
+ ? containerPadding[1]
141
+ : margin[1];
142
+
143
+ return (
144
+ nbRow *
145
+ resolveRowHeight(rowHeight, calcGridColWidth(getPositionParams())) +
146
+ (nbRow - 1) * margin[1] +
147
+ containerPaddingY * 2 +
148
+ "px"
149
+ );
150
+ };
151
+
152
+ /**
153
+ * When dragging starts
154
+ * @param {String} i Id of the child
155
+ * @param {Number} x X position of the move
156
+ * @param {Number} y Y position of the move
157
+ * @param {Event} e The mousedown event
158
+ * @param {Element} node The current dragging DOM element
159
+ */
160
+ const onDragStartHandler = (
161
+ i: string,
162
+ x: number,
163
+ y: number,
164
+ { e, node }: GridDragEvent
165
+ ) => {
166
+ const l = getLayoutItem(layout, i);
167
+
168
+ if (!l) return;
169
+
170
+ setOldDragItem(cloneLayoutItem(l));
171
+ setOldLayout(layout);
172
+
173
+ props.onDragStart &&
174
+ props.onDragStart({
175
+ layout,
176
+ prev: l,
177
+ item: l,
178
+ placeholder: undefined,
179
+ e,
180
+ node,
181
+ });
182
+ };
183
+
184
+ /**
185
+ * Each drag movement create a new dragelement and move the element to the dragged location
186
+ * @param {String} i Id of the child
187
+ * @param {Number} x X position of the move
188
+ * @param {Number} y Y position of the move
189
+ * @param {Event} e The mousedown event
190
+ * @param {Element} node The current dragging DOM element
191
+ */
192
+ const onDragHandler = (
193
+ i: string,
194
+ x: number,
195
+ y: number,
196
+ { e, node }: GridDragEvent
197
+ ) => {
198
+ const l = getLayoutItem(layout, i);
199
+ if (!l) return;
200
+
201
+ const placeholder = {
202
+ w: l.w,
203
+ h: l.h,
204
+ x: l.x,
205
+ y: l.y,
206
+ z: l.z,
207
+ placeholder: true,
208
+ i,
209
+ };
210
+
211
+ const isUserAction = true;
212
+ let newLayout = moveElement({
213
+ layout,
214
+ l,
215
+ x,
216
+ y,
217
+ isUserAction,
218
+ preventCollision,
219
+ compactType,
220
+ cols,
221
+ allowOverlap,
222
+ });
223
+
224
+ props.onDrag &&
225
+ props.onDrag({
226
+ layout: newLayout,
227
+ prev: oldDragItem,
228
+ item: l,
229
+ placeholder,
230
+ e,
231
+ node,
232
+ });
233
+
234
+ setLayout(allowOverlap ? newLayout : compact(newLayout, compactType, cols));
235
+ setActiveDrag(placeholder);
236
+ };
237
+
238
+ /**
239
+ * When dragging stops, figure out which position the element is closest to and update its x and y.
240
+ * @param {String} i Index of the child.
241
+ * @param {Number} x X position of the move
242
+ * @param {Number} y Y position of the move
243
+ * @param {Event} e The mousedown event
244
+ * @param {Element} node The current dragging DOM element
245
+ */
246
+ const onDragStopHandler = (
247
+ i: string,
248
+ x: number,
249
+ y: number,
250
+ { e, node }: GridDragEvent
251
+ ) => {
252
+ if (!activeDrag) return;
253
+
254
+ const l = getLayoutItem(layout, i);
255
+ if (!l) return;
256
+
257
+ const isUserAction = true;
258
+ const movedLayout = moveElement({
259
+ layout,
260
+ l,
261
+ x,
262
+ y,
263
+ isUserAction,
264
+ preventCollision,
265
+ compactType,
266
+ cols,
267
+ allowOverlap,
268
+ });
269
+
270
+ props.onDragStop &&
271
+ props.onDragStop({
272
+ layout: movedLayout,
273
+ prev: oldDragItem,
274
+ item: l,
275
+ placeholder: undefined,
276
+ e,
277
+ node,
278
+ });
279
+
280
+ const newLayout = allowOverlap
281
+ ? movedLayout
282
+ : compact(movedLayout, compactType, cols);
283
+ setActiveDrag(undefined);
284
+ setLayout(newLayout);
285
+ setOldDragItem(undefined);
286
+ setOldLayout(undefined);
287
+
288
+ props.onLayoutChange && props.onLayoutChange(newLayout);
289
+ };
290
+
291
+ const onLayoutMaybeChanged = (newLayout: Layout, oldLayout?: Layout) => {
292
+ if (!oldLayout) {
293
+ oldLayout = layout;
294
+ }
295
+ if (!isEqual(oldLayout, newLayout)) {
296
+ props.onLayoutChange && props.onLayoutChange(newLayout);
297
+ }
298
+ };
299
+
300
+ const onResizeStartHandler = (
301
+ i: string,
302
+ x: number,
303
+ y: number,
304
+ { e, node }: GridResizeEvent
305
+ ) => {
306
+ const l = getLayoutItem(layout, i);
307
+ if (!l) return;
308
+
309
+ setOldResizeItem(cloneLayoutItem(l));
310
+ setOldLayout(layout);
311
+ props.onResizeStart &&
312
+ props.onResizeStart({
313
+ layout,
314
+ prev: l,
315
+ item: l,
316
+ e,
317
+ node,
318
+ });
319
+ };
320
+
321
+ const onResizeHandler = (
322
+ i: string,
323
+ w: number,
324
+ h: number,
325
+ { e, node }: GridResizeEvent,
326
+ x: number,
327
+ y: number
328
+ ) => {
329
+ const [newLayout, l] = withLayoutItem(layout, i, (l) => {
330
+ let hasCollisions;
331
+ if (preventCollision && !allowOverlap) {
332
+ const collisions = getAllCollisions(layout, {
333
+ ...l,
334
+ w: x,
335
+ h: y,
336
+ }).filter((layoutItem) => layoutItem.i !== l.i);
337
+ hasCollisions = collisions.length > 0;
338
+
339
+ if (hasCollisions) {
340
+ let leastX = Infinity,
341
+ leastY = Infinity;
342
+ collisions.forEach((layoutItem) => {
343
+ if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x);
344
+ if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y);
345
+ });
346
+
347
+ if (Number.isFinite(leastX)) l.w = leastX - l.x;
348
+ if (Number.isFinite(leastY)) l.h = leastY - l.y;
349
+ }
350
+ }
351
+
352
+ if (!hasCollisions) {
353
+ // NOTE - 여기서 x, y를 추가적으로 세팅해줘야 nw, w, n, sw 방향에서 placeholder가 반대로 resize되지 않고 해당 element와 똑같이 resize
354
+ l.w = w;
355
+ l.h = h;
356
+ l.x = x;
357
+ l.y = y;
358
+ }
359
+
360
+ return l;
361
+ });
362
+
363
+ if (!l) return;
364
+
365
+ const placeholder = {
366
+ w: l.w,
367
+ h: l.h,
368
+ x: l.x,
369
+ y: l.y,
370
+ z: l.z,
371
+ static: true,
372
+ i: i,
373
+ };
374
+
375
+ props.onResize &&
376
+ props.onResize({
377
+ layout: newLayout,
378
+ prev: oldResizeItem,
379
+ item: l,
380
+ placeholder,
381
+ e,
382
+ node,
383
+ });
384
+
385
+ setLayout(allowOverlap ? newLayout : compact(newLayout, compactType, cols));
386
+ setActiveDrag(placeholder);
387
+ };
388
+
389
+ const onResizeStopHandler = (
390
+ i: string,
391
+ x: number,
392
+ y: number,
393
+ { e, node }: GridResizeEvent
394
+ ) => {
395
+ const l = getLayoutItem(layout, i);
396
+ props.onResizeStop &&
397
+ props.onResizeStop({ layout, prev: oldResizeItem, item: l, e, node });
398
+
399
+ const newLayout = allowOverlap
400
+ ? layout
401
+ : compact(layout, compactType, cols);
402
+
403
+ setActiveDrag(undefined);
404
+ setLayout(newLayout);
405
+ setOldResizeItem(undefined);
406
+ setOldLayout(undefined);
407
+
408
+ onLayoutMaybeChanged(newLayout, oldLayout);
409
+ };
410
+
411
+ /**
412
+ * Create a placeholder object.
413
+ * @return {Element} Placeholder div.
414
+ */
415
+ const placeholder = () => {
416
+ if (!activeDrag) return;
417
+
418
+ // {...activeDrag} is pretty slow, actually
419
+
420
+ const l = getLayoutItem(props.layout, String(activeDrag.i));
421
+
422
+ if (!l) {
423
+ return null;
424
+ }
425
+
426
+ // NOTE: toChildren changes key
427
+ // LINK: https://legacy.reactjs.org/docs/react-api.html
428
+ const items = React.Children.toArray(children);
429
+
430
+ const item = items.find((child: any) => {
431
+ const originalKey = String(child.key).split(".$");
432
+
433
+ return originalKey[1] === l.i;
434
+ }) as React.ReactElement;
435
+
436
+ if (!item) {
437
+ return null;
438
+ }
439
+
440
+ return (
441
+ <GridItem
442
+ w={activeDrag.w}
443
+ h={activeDrag.h}
444
+ x={activeDrag.x}
445
+ y={activeDrag.y}
446
+ z={activeDrag.z || 0}
447
+ i={activeDrag.i}
448
+ className={"placeholder"}
449
+ containerWidth={width}
450
+ cols={cols}
451
+ margin={margin}
452
+ containerPadding={containerPadding || margin}
453
+ maxRows={maxRows}
454
+ rowHeight={rowHeight}
455
+ isDraggable={false}
456
+ isResizable={false}
457
+ isBounded={false}
458
+ useCSSTransforms={useCSSTransforms}
459
+ transformScale={transformScale}
460
+ >
461
+ {item && item}
462
+ </GridItem>
463
+ );
464
+ };
465
+
466
+ /**
467
+ * Given a grid item, set its style attributes & surround in a <Draggable>.
468
+ * @param {Element} child React element.
469
+ * @return {Element} Element wrapped in draggable and properly placed.
470
+ */
471
+ const processGridItem = (
472
+ child: ReactElement<any>,
473
+ isDroppingItem?: boolean
474
+ ): ReactElement<any> | undefined => {
475
+ if (!child || !child.key) return;
476
+ const l = getLayoutItem(layout, String(child.key));
477
+ if (!l) return;
478
+
479
+ const draggable =
480
+ typeof l.isDraggable === "boolean"
481
+ ? l.isDraggable
482
+ : !l.static && isDraggable;
483
+ const resizable =
484
+ typeof l.isResizable === "boolean"
485
+ ? l.isResizable
486
+ : !l.static && isResizable;
487
+ const resizeHandlesOptions = l.resizeHandles || resizeHandles;
488
+
489
+ const bounded = draggable && isBounded && l.isBounded !== false;
490
+
491
+ return (
492
+ <GridItem
493
+ containerWidth={width}
494
+ cols={cols}
495
+ margin={margin}
496
+ containerPadding={containerPadding || margin}
497
+ maxRows={maxRows}
498
+ rowHeight={rowHeight}
499
+ cancel={draggableCancel}
500
+ handle={draggableHandle}
501
+ onDragStop={onDragStopHandler}
502
+ onDragStart={onDragStartHandler}
503
+ onDrag={onDragHandler}
504
+ onResizeStart={onResizeStartHandler}
505
+ onResize={onResizeHandler}
506
+ onResizeStop={onResizeStopHandler}
507
+ isDraggable={draggable}
508
+ isResizable={resizable}
509
+ isBounded={bounded}
510
+ useCSSTransforms={useCSSTransforms && isMounted}
511
+ usePercentages={!isMounted}
512
+ transformScale={transformScale}
513
+ w={l.w}
514
+ h={l.h}
515
+ x={l.x}
516
+ y={l.y}
517
+ z={l.z || 0}
518
+ i={l.i}
519
+ minH={l.minH}
520
+ minW={l.minW}
521
+ maxH={l.maxH}
522
+ maxW={l.maxW}
523
+ static={l.static}
524
+ droppingPosition={isDroppingItem ? droppingPosition : undefined}
525
+ resizeHandles={resizeHandlesOptions}
526
+ resizeHandle={resizeHandle}
527
+ isHiddenVisibility={isHiddenVisibility}
528
+ >
529
+ {child}
530
+ </GridItem>
531
+ );
532
+ };
533
+
534
+ const getPositionParams = (): PositionParams => {
535
+ return {
536
+ cols,
537
+ margin,
538
+ maxRows,
539
+ rowHeight,
540
+ containerWidth: width,
541
+ containerPadding: containerPadding || margin,
542
+ };
543
+ };
544
+
545
+ const onDragOverHandler: React.DragEventHandler<HTMLDivElement> = (e) => {
546
+ e.preventDefault();
547
+ e.stopPropagation();
548
+
549
+ const onDragOverResult = props.onDropDragOver?.(e as any as DragOverEvent); // TODO fix
550
+ if (onDragOverResult === false) {
551
+ if (droppingDOMNode) {
552
+ removeDroppingPlaceholder();
553
+ }
554
+ return false;
555
+ }
556
+ const finalDroppingItem = { ...droppingItem, ...onDragOverResult };
557
+
558
+ const { layerX, layerY } = e.nativeEvent as any; // TODO fix
559
+ const droppingPosition = {
560
+ left: layerX / transformScale,
561
+ top: layerY / transformScale,
562
+ e,
563
+ };
564
+
565
+ if (!droppingDOMNode) {
566
+ const calculatedPosition = calcXY(
567
+ getPositionParams(),
568
+ layerY,
569
+ layerX,
570
+ finalDroppingItem.w,
571
+ finalDroppingItem.h
572
+ );
573
+
574
+ setDroppingDOMNode(<div key={finalDroppingItem.i} />);
575
+ setDroppingPosition(droppingPosition);
576
+ setLayout([
577
+ ...layout,
578
+ {
579
+ ...finalDroppingItem,
580
+ x: calculatedPosition.x,
581
+ y: calculatedPosition.y,
582
+ static: false,
583
+ isDraggable: true,
584
+ },
585
+ ]);
586
+ } else if (droppingPosition) {
587
+ const { left, top } = droppingPosition;
588
+ const shouldUpdatePosition = left != layerX || top != layerY;
589
+ if (shouldUpdatePosition) {
590
+ setDroppingPosition(droppingPosition);
591
+ }
592
+ }
593
+ };
594
+
595
+ const removeDroppingPlaceholder: () => void = () => {
596
+ const newLayout = compact(
597
+ layout.filter((l) => l.i !== droppingItem.i),
598
+ compactType,
599
+ cols,
600
+ allowOverlap
601
+ );
602
+
603
+ setLayout(newLayout);
604
+ setDroppingDOMNode(undefined);
605
+ setActiveDrag(undefined);
606
+ setDroppingPosition(undefined);
607
+ };
608
+
609
+ const onDragLeaveHandler: React.DragEventHandler<HTMLDivElement> = (e) => {
610
+ e.preventDefault();
611
+ e.stopPropagation();
612
+ dragEnterCounter.current = dragEnterCounter.current - 1;
613
+
614
+ if (dragEnterCounter.current === 0) {
615
+ removeDroppingPlaceholder();
616
+ }
617
+ };
618
+
619
+ const onDragEnterHandler: React.DragEventHandler<HTMLDivElement> = (e) => {
620
+ e.preventDefault();
621
+ e.stopPropagation();
622
+ dragEnterCounter.current += 1;
623
+ };
624
+
625
+ const onDropHandler: React.DragEventHandler<HTMLDivElement> = (e) => {
626
+ e.preventDefault();
627
+ e.stopPropagation();
628
+ const item = layout.find((l) => l.i === droppingItem.i);
629
+
630
+ dragEnterCounter.current = 0;
631
+
632
+ removeDroppingPlaceholder();
633
+
634
+ if (item) {
635
+ props.onDrop && props.onDrop(layout, item, e);
636
+ }
637
+ };
638
+
639
+ const mergedClassName = classNames(layoutClassName, className);
640
+ const mergedStyle = {
641
+ height: containerHeight(),
642
+ ...style,
643
+ };
644
+
645
+ return (
646
+ <div
647
+ ref={innerRef}
648
+ className={mergedClassName}
649
+ style={mergedStyle}
650
+ onDrop={isDroppable ? onDropHandler : noop}
651
+ onDragLeave={isDroppable ? onDragLeaveHandler : noop}
652
+ onDragEnter={isDroppable ? onDragEnterHandler : noop}
653
+ onDragOver={isDroppable ? onDragOverHandler : noop}
654
+ >
655
+ {React.Children.map(children, (child) => processGridItem(child))}
656
+ {/* TODO: 복수개의 placeholder 지원예정 */}
657
+ {placeholder()}
658
+ </div>
659
+ );
660
+ };
661
+
662
+ export default ReactGridLayout;