@vuu-ui/vuu-layout 0.5.13 → 0.5.15

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 (114) hide show
  1. package/package.json +10 -13
  2. package/src/Component.css +0 -0
  3. package/src/Component.tsx +20 -0
  4. package/src/DraggableLayout.css +18 -0
  5. package/src/DraggableLayout.tsx +26 -0
  6. package/src/__tests__/flexbox-utils.spec.js +90 -0
  7. package/src/chest-of-drawers/Chest.css +36 -0
  8. package/src/chest-of-drawers/Chest.tsx +42 -0
  9. package/src/chest-of-drawers/Drawer.css +159 -0
  10. package/src/chest-of-drawers/Drawer.tsx +118 -0
  11. package/src/chest-of-drawers/index.ts +2 -0
  12. package/src/common-types.ts +9 -0
  13. package/src/debug.ts +16 -0
  14. package/src/drag-drop/BoxModel.ts +551 -0
  15. package/src/drag-drop/DragState.ts +219 -0
  16. package/src/drag-drop/Draggable.ts +282 -0
  17. package/src/drag-drop/DropMenu.css +71 -0
  18. package/src/drag-drop/DropMenu.tsx +61 -0
  19. package/src/drag-drop/DropTarget.ts +393 -0
  20. package/src/drag-drop/DropTargetRenderer.css +40 -0
  21. package/src/drag-drop/DropTargetRenderer.tsx +277 -0
  22. package/src/drag-drop/dragDropTypes.ts +47 -0
  23. package/src/drag-drop/index.ts +5 -0
  24. package/src/editable-label/EditableLabel.css +28 -0
  25. package/src/editable-label/EditableLabel.tsx +99 -0
  26. package/src/editable-label/index.ts +1 -0
  27. package/src/flexbox/Flexbox.css +45 -0
  28. package/src/flexbox/Flexbox.tsx +70 -0
  29. package/src/flexbox/FlexboxLayout.tsx +28 -0
  30. package/src/flexbox/FluidGrid.css +134 -0
  31. package/src/flexbox/FluidGrid.tsx +82 -0
  32. package/src/flexbox/FluidGridLayout.tsx +9 -0
  33. package/src/flexbox/Splitter.css +140 -0
  34. package/src/flexbox/Splitter.tsx +127 -0
  35. package/src/flexbox/flexbox-utils.ts +128 -0
  36. package/src/flexbox/flexboxTypes.ts +68 -0
  37. package/src/flexbox/index.ts +5 -0
  38. package/src/flexbox/useResponsiveSizing.ts +82 -0
  39. package/src/flexbox/useSplitterResizing.ts +270 -0
  40. package/src/index.ts +19 -0
  41. package/src/layout-action.ts +21 -0
  42. package/src/layout-header/ActionButton.tsx +23 -0
  43. package/src/layout-header/Header.css +8 -0
  44. package/src/layout-header/Header.tsx +216 -0
  45. package/src/layout-header/index.ts +1 -0
  46. package/src/layout-provider/LayoutProvider.tsx +161 -0
  47. package/src/layout-provider/LayoutProviderContext.ts +17 -0
  48. package/src/layout-provider/index.ts +3 -0
  49. package/src/layout-provider/useLayoutDragDrop.ts +210 -0
  50. package/src/layout-reducer/flexUtils.ts +276 -0
  51. package/src/layout-reducer/index.ts +5 -0
  52. package/src/layout-reducer/insert-layout-element.ts +365 -0
  53. package/src/layout-reducer/layout-reducer.ts +237 -0
  54. package/src/layout-reducer/layoutTypes.ts +159 -0
  55. package/src/layout-reducer/layoutUtils.ts +288 -0
  56. package/src/layout-reducer/remove-layout-element.ts +226 -0
  57. package/src/layout-reducer/replace-layout-element.ts +113 -0
  58. package/src/layout-reducer/resize-flex-children.ts +55 -0
  59. package/src/layout-reducer/wrap-layout-element.ts +307 -0
  60. package/src/layout-view/View.css +61 -0
  61. package/src/layout-view/View.tsx +143 -0
  62. package/src/layout-view/ViewContext.ts +30 -0
  63. package/src/layout-view/index.ts +5 -0
  64. package/src/layout-view/useView.tsx +104 -0
  65. package/src/layout-view/useViewActionDispatcher.ts +123 -0
  66. package/src/layout-view/useViewResize.ts +53 -0
  67. package/src/layout-view/viewTypes.ts +35 -0
  68. package/src/palette/Palette.css +33 -0
  69. package/src/palette/Palette.tsx +140 -0
  70. package/src/palette/PaletteSalt.css +9 -0
  71. package/src/palette/PaletteSalt.tsx +79 -0
  72. package/src/palette/index.ts +3 -0
  73. package/src/placeholder/Placeholder.css +10 -0
  74. package/src/placeholder/Placeholder.tsx +38 -0
  75. package/src/placeholder/index.ts +1 -0
  76. package/src/registry/ComponentRegistry.ts +44 -0
  77. package/src/registry/index.ts +1 -0
  78. package/src/responsive/breakpoints.ts +62 -0
  79. package/src/responsive/index.ts +3 -0
  80. package/src/responsive/measureMinimumNodeSize.ts +23 -0
  81. package/src/responsive/overflowUtils.js +14 -0
  82. package/src/responsive/use-breakpoints.ts +101 -0
  83. package/src/responsive/useResizeObserver.ts +154 -0
  84. package/src/responsive/utils.ts +37 -0
  85. package/src/stack/Stack.css +39 -0
  86. package/src/stack/Stack.tsx +173 -0
  87. package/src/stack/StackLayout.tsx +119 -0
  88. package/src/stack/index.ts +4 -0
  89. package/src/stack/stackTypes.ts +22 -0
  90. package/src/tabs/TabPanel.css +12 -0
  91. package/src/tabs/TabPanel.tsx +17 -0
  92. package/src/tabs/index.ts +1 -0
  93. package/src/tools/config-wrapper/ConfigWrapper.tsx +55 -0
  94. package/src/tools/config-wrapper/index.ts +1 -0
  95. package/src/tools/devtools-box/layout-configurator.css +112 -0
  96. package/src/tools/devtools-box/layout-configurator.jsx +369 -0
  97. package/src/tools/devtools-tree/layout-tree-viewer.css +15 -0
  98. package/src/tools/devtools-tree/layout-tree-viewer.jsx +36 -0
  99. package/src/tools/index.ts +4 -0
  100. package/src/use-persistent-state.ts +112 -0
  101. package/src/utils/index.ts +5 -0
  102. package/src/utils/pathUtils.ts +283 -0
  103. package/src/utils/propUtils.ts +26 -0
  104. package/src/utils/refUtils.ts +16 -0
  105. package/src/utils/styleUtils.ts +13 -0
  106. package/src/utils/typeOf.ts +25 -0
  107. package/tsconfig-emit-types.json +11 -0
  108. package/LICENSE +0 -201
  109. package/cjs/index.js +0 -20
  110. package/cjs/index.js.map +0 -7
  111. package/esm/index.js +0 -20
  112. package/esm/index.js.map +0 -7
  113. package/index.css +0 -2
  114. package/index.css.map +0 -7
@@ -0,0 +1,226 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import React, { ReactElement } from 'react';
3
+ import { createPlaceHolder } from './flexUtils';
4
+ import { swapChild } from './replace-layout-element';
5
+
6
+ import {
7
+ followPath,
8
+ followPathToParent,
9
+ getProp,
10
+ getProps,
11
+ nextStep,
12
+ resetPath,
13
+ typeOf
14
+ } from '../utils';
15
+ import { RemoveAction } from './layoutTypes';
16
+
17
+ export function removeChild(layoutRoot: ReactElement, { path }: RemoveAction) {
18
+ const target = followPath(layoutRoot, path!) as ReactElement;
19
+ let targetParent = followPathToParent(layoutRoot, path!);
20
+ if (targetParent === null) {
21
+ return layoutRoot;
22
+ }
23
+ const { children } = getProps(targetParent);
24
+ if (children.length > 1 && allOtherChildrenArePlaceholders(children, path)) {
25
+ const {
26
+ style: { flexBasis, display, flexDirection, ...style }
27
+ } = getProps(targetParent);
28
+ let containerPath = getProp(targetParent, 'path');
29
+ let newLayout = swapChild(
30
+ layoutRoot,
31
+ targetParent,
32
+ createPlaceHolder(containerPath, flexBasis, style)
33
+ );
34
+ while ((targetParent = followPathToParent(newLayout, containerPath))) {
35
+ if (getProp(targetParent, 'path') === '0') {
36
+ break;
37
+ }
38
+ const { children } = getProps(targetParent);
39
+ if (allOtherChildrenArePlaceholders(children)) {
40
+ containerPath = getProp(targetParent, 'path');
41
+ const {
42
+ style: { flexBasis, display, flexDirection, ...style }
43
+ } = getProps(targetParent);
44
+ newLayout = swapChild(
45
+ layoutRoot,
46
+ targetParent,
47
+ createPlaceHolder(containerPath, flexBasis, style)
48
+ );
49
+ } else if (hasAdjacentPlaceholders(children)) {
50
+ newLayout = collapsePlaceholders(layoutRoot, targetParent as ReactElement);
51
+ } else {
52
+ break;
53
+ }
54
+ }
55
+ return newLayout;
56
+ }
57
+ return _removeChild(layoutRoot, target);
58
+ }
59
+
60
+ function _removeChild(container: ReactElement, child: ReactElement): ReactElement {
61
+ const props = getProps(container)
62
+ const { children: componentChildren, path, preserve } = props
63
+ let { active } = props
64
+ const { idx, finalStep } = nextStep(path, getProp(child, 'path'));
65
+ const type = typeOf(container) as string;
66
+ let children = componentChildren.slice() as ReactElement[];
67
+
68
+ if (finalStep) {
69
+ children.splice(idx, 1);
70
+ if (active !== undefined && active >= idx) {
71
+ active = Math.max(0, active - 1);
72
+ }
73
+
74
+ if (children.length === 1 && !preserve && path !== '0' && type.match(/Flexbox|Stack/)) {
75
+ return unwrap(container, children[0]);
76
+ }
77
+
78
+ if (!children.some(isFlexible) && children.some(canBeMadeFlexible)) {
79
+ children = makeFlexible(children);
80
+ }
81
+ } else {
82
+ children[idx] = _removeChild(children[idx], child) as ReactElement;
83
+ }
84
+
85
+ children = children.map((child, i) => resetPath(child, `${path}.${i}`));
86
+ return React.cloneElement(container, { active }, children);
87
+ }
88
+
89
+ function unwrap(container: ReactElement, child: ReactElement) {
90
+ const type = typeOf(container);
91
+ const {
92
+ path,
93
+ style: { flexBasis, flexGrow, flexShrink, width, height }
94
+ } = getProps(container);
95
+
96
+ let unwrappedChild = resetPath(child, path);
97
+ if (path === '0') {
98
+ unwrappedChild = React.cloneElement(unwrappedChild, {
99
+ style: {
100
+ ...child.props.style,
101
+ width,
102
+ height
103
+ }
104
+ });
105
+ } else if (type === 'Flexbox') {
106
+ const dim = container.props.style.flexDirection === 'column' ? 'height' : 'width';
107
+ const {
108
+ style: { [dim]: size, ...style }
109
+ } = unwrappedChild.props;
110
+ unwrappedChild = React.cloneElement(unwrappedChild, {
111
+ flexFill: undefined,
112
+ style: {
113
+ ...style,
114
+ flexGrow,
115
+ flexShrink,
116
+ flexBasis,
117
+ width,
118
+ height
119
+ }
120
+ });
121
+ }
122
+ return unwrappedChild;
123
+ }
124
+
125
+ const isFlexible = (element: ReactElement) => {
126
+ return element.props.style.flexGrow > 0;
127
+ }
128
+
129
+ const canBeMadeFlexible = (element: ReactElement) => {
130
+ const { width, height, flexGrow } = element.props.style;
131
+ return flexGrow === 0 && typeof width !== 'number' && typeof height !== 'number';
132
+ }
133
+
134
+ const makeFlexible = (children: ReactElement[]) => {
135
+ return children.map((child) =>
136
+ canBeMadeFlexible(child)
137
+ ? React.cloneElement(child, {
138
+ style: {
139
+ ...child.props.style,
140
+ flexGrow: 1
141
+ }
142
+ })
143
+ : child
144
+ );
145
+ }
146
+
147
+ const hasAdjacentPlaceholders = (children: ReactElement[]) => {
148
+ if (children && children.length > 0) {
149
+ let wasPlaceholder = getProp(children[0], 'placeholder');
150
+ let isPlaceholder = false;
151
+ for (let i = 1; i < children.length; i++) {
152
+ isPlaceholder = getProp(children[i], 'placeholder');
153
+ if (wasPlaceholder && isPlaceholder) {
154
+ return true;
155
+ }
156
+ wasPlaceholder = isPlaceholder;
157
+ }
158
+ }
159
+ };
160
+
161
+ const collapsePlaceholders = (container: ReactElement, target: ReactElement) => {
162
+ const { children: componentChildren, path } = getProps(container);
163
+ const { idx, finalStep } = nextStep(path, getProp(target, 'path'));
164
+ let children = componentChildren.slice() as ReactElement[];
165
+ if (finalStep) {
166
+ children[idx] = _collapsePlaceHolders(target);
167
+ } else {
168
+ children[idx] = collapsePlaceholders(children[idx], target) as ReactElement;
169
+ }
170
+
171
+ children = children.map((child, i) => resetPath(child, `${path}.${i}`));
172
+ return React.cloneElement(container, undefined, children);
173
+ };
174
+
175
+ const _collapsePlaceHolders = (container: ReactElement) => {
176
+ const { children } = getProps(container);
177
+ const newChildren = [];
178
+ const placeholders: ReactElement[] = [];
179
+
180
+ for (let i = 0; i < children.length; i++) {
181
+ if (getProp(children[i], 'placeholder')) {
182
+ placeholders.push(children[i]);
183
+ } else {
184
+ if (placeholders.length === 1) {
185
+ newChildren.push(placeholders.pop());
186
+ } else if (placeholders.length > 0) {
187
+ newChildren.push(mergePlaceholders(placeholders));
188
+ placeholders.length = 0;
189
+ }
190
+ newChildren.push(children[i]);
191
+ }
192
+ }
193
+
194
+ if (placeholders.length === 1) {
195
+ newChildren.push(placeholders.pop());
196
+ } else if (placeholders.length > 0) {
197
+ newChildren.push(mergePlaceholders(placeholders));
198
+ }
199
+
200
+ const containerPath = getProp(container, 'path');
201
+ return React.cloneElement(
202
+ container,
203
+ undefined,
204
+ newChildren.map((child, i) => resetPath(child, `${containerPath}.${i}`))
205
+ );
206
+ };
207
+
208
+ const mergePlaceholders = ([placeholder, ...placeholders]: ReactElement[]) => {
209
+ const targetStyle = getProp(placeholder, 'style');
210
+ let { flexBasis, flexGrow, flexShrink } = targetStyle;
211
+ for (const {
212
+ props: { style }
213
+ } of placeholders) {
214
+ flexBasis += style.flexBasis;
215
+ flexGrow = Math.max(flexGrow, style.flexGrow);
216
+ flexShrink = Math.max(flexShrink, style.flexShrink);
217
+ }
218
+ return React.cloneElement(placeholder, {
219
+ style: { ...targetStyle, flexBasis, flexGrow, flexShrink }
220
+ });
221
+ };
222
+
223
+ const allOtherChildrenArePlaceholders = (children: ReactElement[], path?: string) =>
224
+ children.every(
225
+ (child) => getProp(child, 'placeholder') || (path && getProp(child, 'path') === path)
226
+ );
@@ -0,0 +1,113 @@
1
+ import React, { ReactElement } from 'react';
2
+ import { Action } from '../layout-action';
3
+ import { getProp, getProps, nextStep } from '../utils';
4
+ import { ReplaceAction } from './layoutTypes';
5
+ import { applyLayoutProps, LayoutProps } from './layoutUtils';
6
+
7
+ export function replaceChild(model: ReactElement, { target, replacement }: ReplaceAction) {
8
+ return _replaceChild(model, target, replacement);
9
+ }
10
+
11
+ export function _replaceChild(
12
+ model: ReactElement,
13
+ child: ReactElement,
14
+ replacement: ReactElement<LayoutProps>
15
+ ) {
16
+ const path = getProp(child, 'path');
17
+ const resizeable = getProp(child, 'resizeable');
18
+ const { style } = getProps(child);
19
+ const newChild =
20
+ applyLayoutProps(
21
+ React.cloneElement(replacement, {
22
+ resizeable,
23
+ style: {
24
+ ...style,
25
+ ...replacement.props.style
26
+ }
27
+ }),
28
+ path
29
+ );
30
+
31
+ return swapChild(model, child, newChild);
32
+ }
33
+
34
+ export function swapChild(
35
+ model: ReactElement,
36
+ child: ReactElement,
37
+ replacement: ReactElement,
38
+ op?: 'maximize' | 'minimize' | 'restore'
39
+ ): ReactElement {
40
+ if (model === child) {
41
+ return replacement;
42
+ }
43
+
44
+ const { idx, finalStep } = nextStep(getProp(model, 'path'), getProp(child, 'path'));
45
+ const children = model.props.children.slice();
46
+
47
+ if (finalStep) {
48
+ if (!op) {
49
+ children[idx] = replacement;
50
+ } else if (op === Action.MINIMIZE) {
51
+ children[idx] = minimize(model, children[idx]);
52
+ } else if (op === Action.RESTORE) {
53
+ children[idx] = restore(children[idx]);
54
+ }
55
+ } else {
56
+ children[idx] = swapChild(children[idx], child, replacement, op);
57
+ }
58
+ return React.cloneElement(model, undefined, children);
59
+ }
60
+
61
+ function minimize(parent: ReactElement, child: ReactElement) {
62
+ const { style: parentStyle } = getProps(parent);
63
+ const { style: childStyle } = getProps(child);
64
+
65
+ const { width, height, flexBasis, flexShrink, flexGrow, ...rest } = childStyle;
66
+
67
+ const restoreStyle = {
68
+ width,
69
+ height,
70
+ flexBasis,
71
+ flexShrink,
72
+ flexGrow
73
+ };
74
+
75
+ const style = {
76
+ ...rest,
77
+ flexBasis: 0,
78
+ flexGrow: 0,
79
+ flexShrink: 0
80
+ };
81
+ const collapsed =
82
+ parentStyle.flexDirection === 'row'
83
+ ? 'vertical'
84
+ : parentStyle.flexDirection === 'column'
85
+ ? 'horizontal'
86
+ : false;
87
+
88
+ if (collapsed) {
89
+ return React.cloneElement(child, {
90
+ collapsed,
91
+ restoreStyle,
92
+ style
93
+ });
94
+ }
95
+ return child;
96
+ }
97
+
98
+ function restore(child: ReactElement) {
99
+ const { style: childStyle, restoreStyle } = getProps(child);
100
+
101
+ const { flexBasis, flexShrink, flexGrow, ...rest } = childStyle;
102
+
103
+ const style = {
104
+ ...rest,
105
+ ...restoreStyle
106
+ };
107
+
108
+ return React.cloneElement(child, {
109
+ collapsed: false,
110
+ style,
111
+ restoreStyle: undefined
112
+ });
113
+ }
@@ -0,0 +1,55 @@
1
+ import React, { CSSProperties, ReactElement } from 'react';
2
+ import { dimension } from '../common-types';
3
+ import { followPath, getProps } from '../utils';
4
+ import { SplitterResizeAction } from './layoutTypes';
5
+ import { swapChild } from './replace-layout-element';
6
+
7
+ export function resizeFlexChildren(
8
+ layoutRoot: ReactElement,
9
+ { path, sizes }: SplitterResizeAction
10
+ ) {
11
+ const target = followPath(layoutRoot, path, true);
12
+ const { children, style } = getProps(target);
13
+
14
+ const dimension = style.flexDirection === 'column' ? 'height' : 'width';
15
+ const replacementChildren = applySizesToChildren(children, sizes, dimension);
16
+
17
+ const replacement = React.cloneElement(target, undefined, replacementChildren);
18
+
19
+ return swapChild(layoutRoot, target, replacement);
20
+ }
21
+
22
+ function applySizesToChildren(
23
+ children: ReactElement[],
24
+ sizes: { currentSize: number; flexBasis: number }[],
25
+ dimension: dimension
26
+ ) {
27
+ return children.map((child, i) => {
28
+ const {
29
+ style: { [dimension]: size, flexBasis: actualFlexBasis }
30
+ } = getProps(child);
31
+ const meta = sizes[i];
32
+ const { currentSize, flexBasis } = meta;
33
+ const hasCurrentSize = currentSize !== undefined;
34
+ const newSize = hasCurrentSize ? meta.currentSize : flexBasis;
35
+
36
+ if (newSize === undefined || size === newSize || actualFlexBasis === newSize) {
37
+ return child;
38
+ }
39
+ return React.cloneElement(child, {
40
+ style: applySizeToChild(child.props.style, dimension, newSize)
41
+ });
42
+ });
43
+ }
44
+
45
+ function applySizeToChild(style: CSSProperties, dimension: dimension, newSize: number) {
46
+ const hasSize = typeof style[dimension] === 'number';
47
+ const { flexShrink = 1, flexGrow = 1 } = style;
48
+ return {
49
+ ...style,
50
+ [dimension]: hasSize ? newSize : 'auto',
51
+ flexBasis: hasSize ? 'auto' : newSize,
52
+ flexShrink,
53
+ flexGrow
54
+ };
55
+ }
@@ -0,0 +1,307 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { uuid } from "@vuu-ui/vuu-utils";
3
+ import React, { ReactElement } from "react";
4
+ import { rectTuple } from "../common-types";
5
+ import { DropPos } from "../drag-drop/dragDropTypes";
6
+ import { DropTarget } from "../drag-drop/DropTarget";
7
+ import { ComponentRegistry } from "../registry/ComponentRegistry";
8
+ import { getProp, getProps, nextStep, resetPath, typeOf } from "../utils";
9
+ import {
10
+ createFlexbox,
11
+ createPlaceHolder,
12
+ flexDirection,
13
+ getFlexStyle,
14
+ getIntrinsicSize,
15
+ wrapIntrinsicSizeComponentWithFlexbox,
16
+ } from "./flexUtils";
17
+ import { LayoutModel } from "./layoutTypes";
18
+ import { applyLayoutProps, LayoutProps } from "./layoutUtils";
19
+
20
+ export interface LayoutSpec {
21
+ type: "Stack" | "Flexbox";
22
+ flexDirection: "column" | "row";
23
+ showTabs?: boolean;
24
+ }
25
+
26
+ const isHtmlElement = (component: LayoutModel) => {
27
+ const [firstLetter] = typeOf(component) as string;
28
+ return firstLetter === firstLetter.toLowerCase();
29
+ };
30
+
31
+ export function wrap(
32
+ container: ReactElement,
33
+ existingComponent: any,
34
+ newComponent: any,
35
+ pos: DropPos,
36
+ clientRect?: DropTarget["clientRect"],
37
+ dropRect?: DropTarget["dropRect"]
38
+ ): ReactElement {
39
+ const { children: containerChildren, path: containerPath } =
40
+ getProps(container);
41
+
42
+ const existingComponentPath = getProp(existingComponent, "path");
43
+ const { idx, finalStep } = nextStep(containerPath, existingComponentPath);
44
+ const children = finalStep
45
+ ? updateChildren(
46
+ container,
47
+ containerChildren,
48
+ existingComponent,
49
+ newComponent,
50
+ pos,
51
+ clientRect,
52
+ dropRect
53
+ )
54
+ : containerChildren.map((child: ReactElement, index: number) =>
55
+ index === idx
56
+ ? wrap(
57
+ child,
58
+ existingComponent,
59
+ newComponent,
60
+ pos,
61
+ clientRect,
62
+ dropRect
63
+ )
64
+ : child
65
+ );
66
+
67
+ return React.cloneElement(container, undefined, children);
68
+ }
69
+
70
+ function updateChildren(
71
+ container: LayoutModel,
72
+ containerChildren: ReactElement[],
73
+ existingComponent: ReactElement,
74
+ newComponent: ReactElement,
75
+ pos: DropPos,
76
+ clientRect?: DropTarget["clientRect"],
77
+ dropRect?: rectTuple
78
+ ) {
79
+ const intrinsicSize = getIntrinsicSize(newComponent);
80
+
81
+ if (intrinsicSize?.width && intrinsicSize?.height) {
82
+ if (clientRect === undefined || dropRect === undefined) {
83
+ throw Error(
84
+ "wrap-layout-element, updateChildren clientRect and dropRect must both be available"
85
+ );
86
+ }
87
+ return wrapIntrinsicSizedComponent(
88
+ containerChildren,
89
+ existingComponent,
90
+ newComponent,
91
+ pos,
92
+ clientRect,
93
+ dropRect
94
+ );
95
+ }
96
+ return wrapFlexComponent(
97
+ container,
98
+ containerChildren,
99
+ existingComponent,
100
+ newComponent,
101
+ pos
102
+ );
103
+ }
104
+
105
+ function wrapFlexComponent(
106
+ container: LayoutModel,
107
+ containerChildren: ReactElement[],
108
+ existingComponent: ReactElement,
109
+ newComponent: ReactElement,
110
+ pos: DropPos
111
+ ) {
112
+ const { version = 0 } = getProps(newComponent);
113
+ const existingComponentPath = getProp(existingComponent, "path");
114
+ const {
115
+ type,
116
+ flexDirection,
117
+ showTabs: showTabsProp,
118
+ } = getLayoutSpecForWrapper(pos);
119
+ const [style, existingComponentStyle, newComponentStyle] =
120
+ getWrappedFlexStyles(
121
+ type,
122
+ existingComponent,
123
+ newComponent,
124
+ flexDirection,
125
+ pos
126
+ );
127
+ const targetFirst = isTargetFirst(pos);
128
+ const active = targetFirst ? 1 : 0;
129
+
130
+ const newComponentProps = {
131
+ resizeable: true,
132
+ style: newComponentStyle,
133
+ version: version + 1,
134
+ };
135
+ const resizeProp = isHtmlElement(existingComponent)
136
+ ? "data-resizeable"
137
+ : "resizeable";
138
+ const existingComponentProps = {
139
+ [resizeProp]: true,
140
+ style: existingComponentStyle,
141
+ };
142
+
143
+ const showTabs = type === "Stack" ? { showTabs: showTabsProp } : undefined;
144
+ const splitterSize =
145
+ type === "Flexbox"
146
+ ? {
147
+ splitterSize:
148
+ (typeOf(container) === "Flexbox" && container.props.splitterSize) ??
149
+ undefined,
150
+ }
151
+ : undefined;
152
+
153
+ const id = uuid();
154
+ const wrapper = React.createElement(
155
+ ComponentRegistry[type],
156
+ {
157
+ active,
158
+ id,
159
+ key: id,
160
+ path: getProp(existingComponent, "path"),
161
+ flexFill: getProp(existingComponent, "flexFill"),
162
+ ...splitterSize,
163
+ ...showTabs,
164
+ style,
165
+ resizeable: getProp(existingComponent, "resizeable"),
166
+ } as LayoutProps,
167
+ targetFirst
168
+ ? [
169
+ resetPath(
170
+ existingComponent,
171
+ `${existingComponentPath}.0`,
172
+ existingComponentProps
173
+ ),
174
+ applyLayoutProps(
175
+ React.cloneElement(newComponent, newComponentProps),
176
+ `${existingComponentPath}.1`
177
+ ),
178
+ ]
179
+ : [
180
+ applyLayoutProps(
181
+ React.cloneElement(newComponent, newComponentProps),
182
+ `${existingComponentPath}.0`
183
+ ),
184
+ resetPath(
185
+ existingComponent,
186
+ `${existingComponentPath}.1`,
187
+ existingComponentProps
188
+ ),
189
+ ]
190
+ );
191
+ return containerChildren.map((child: ReactElement) =>
192
+ child === existingComponent ? wrapper : child
193
+ );
194
+ }
195
+
196
+ function wrapIntrinsicSizedComponent(
197
+ containerChildren: ReactElement[],
198
+ existingComponent: ReactElement,
199
+ newComponent: ReactElement,
200
+ pos: DropPos,
201
+ clientRect: DropTarget["clientRect"],
202
+ dropRect: rectTuple
203
+ ) {
204
+ const { flexDirection } = getLayoutSpecForWrapper(pos);
205
+ const contraDirection = flexDirection === "column" ? "row" : "column";
206
+ const targetFirst = isTargetFirst(pos);
207
+
208
+ const [dropLeft, dropTop, dropRight, dropBottom] = dropRect;
209
+ const [startPlaceholder, endPlaceholder] =
210
+ flexDirection === "column"
211
+ ? [dropTop - clientRect.top, clientRect.bottom - dropBottom]
212
+ : [dropLeft - clientRect.left, clientRect.right - dropRight];
213
+ const pathRoot = getProp(existingComponent, "path");
214
+ let pathIndex = 0;
215
+
216
+ const resizeProp = isHtmlElement(existingComponent)
217
+ ? "data-resizeable"
218
+ : "resizeable";
219
+
220
+ const wrappedChildren = [];
221
+ if (startPlaceholder) {
222
+ wrappedChildren.push(
223
+ targetFirst
224
+ ? resetPath(existingComponent, `${pathRoot}.${pathIndex++}`, {
225
+ [resizeProp]: true,
226
+ style: { flexBasis: startPlaceholder, flexGrow: 1, flexShrink: 1 },
227
+ })
228
+ : createPlaceHolder(`${pathRoot}.${pathIndex++}`, startPlaceholder, {
229
+ flexGrow: 0,
230
+ flexShrink: 0,
231
+ })
232
+ );
233
+ }
234
+ wrappedChildren.push(
235
+ wrapIntrinsicSizeComponentWithFlexbox(
236
+ newComponent,
237
+ contraDirection,
238
+ `${pathRoot}.${pathIndex++}`,
239
+ clientRect,
240
+ dropRect
241
+ )
242
+ );
243
+ if (endPlaceholder) {
244
+ wrappedChildren.push(
245
+ targetFirst
246
+ ? createPlaceHolder(`${pathRoot}.${pathIndex++}`, 0)
247
+ : resetPath(existingComponent, `${pathRoot}.${pathIndex++}`, {
248
+ [resizeProp]: true,
249
+ style: { flexBasis: 0, flexGrow: 1, flexShrink: 1 },
250
+ })
251
+ );
252
+ }
253
+
254
+ const wrapper = createFlexbox(
255
+ flexDirection,
256
+ existingComponent.props,
257
+ wrappedChildren,
258
+ pathRoot
259
+ );
260
+ return containerChildren.map((child) =>
261
+ child === existingComponent ? wrapper : child
262
+ );
263
+ }
264
+
265
+ function getWrappedFlexStyles(
266
+ type: string,
267
+ existingComponent: ReactElement,
268
+ newComponent: ReactElement,
269
+ flexDirection: flexDirection,
270
+ pos: DropPos
271
+ ) {
272
+ const style = {
273
+ ...existingComponent.props.style,
274
+ flexDirection,
275
+ };
276
+
277
+ const dimension =
278
+ type === "Flexbox" && flexDirection === "column" ? "height" : "width";
279
+ const newComponentStyle = getFlexStyle(newComponent, dimension, pos);
280
+ const existingComponentStyle = getFlexStyle(existingComponent, dimension);
281
+
282
+ return [style, existingComponentStyle, newComponentStyle];
283
+ }
284
+
285
+ const isTargetFirst = (pos: DropPos) =>
286
+ pos.position.SouthOrEast
287
+ ? true
288
+ : pos?.tab?.positionRelativeToTab === "before"
289
+ ? false
290
+ : pos.position.Header
291
+ ? true
292
+ : false;
293
+
294
+ function getLayoutSpecForWrapper(pos: DropPos): LayoutSpec {
295
+ if (pos.position.Header) {
296
+ return {
297
+ type: "Stack",
298
+ flexDirection: "column",
299
+ showTabs: true,
300
+ };
301
+ } else {
302
+ return {
303
+ type: "Flexbox",
304
+ flexDirection: pos.position.EastOrWest ? "row" : "column",
305
+ };
306
+ }
307
+ }