@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.
- package/package.json +10 -13
- package/src/Component.css +0 -0
- package/src/Component.tsx +20 -0
- package/src/DraggableLayout.css +18 -0
- package/src/DraggableLayout.tsx +26 -0
- package/src/__tests__/flexbox-utils.spec.js +90 -0
- package/src/chest-of-drawers/Chest.css +36 -0
- package/src/chest-of-drawers/Chest.tsx +42 -0
- package/src/chest-of-drawers/Drawer.css +159 -0
- package/src/chest-of-drawers/Drawer.tsx +118 -0
- package/src/chest-of-drawers/index.ts +2 -0
- package/src/common-types.ts +9 -0
- package/src/debug.ts +16 -0
- package/src/drag-drop/BoxModel.ts +551 -0
- package/src/drag-drop/DragState.ts +219 -0
- package/src/drag-drop/Draggable.ts +282 -0
- package/src/drag-drop/DropMenu.css +71 -0
- package/src/drag-drop/DropMenu.tsx +61 -0
- package/src/drag-drop/DropTarget.ts +393 -0
- package/src/drag-drop/DropTargetRenderer.css +40 -0
- package/src/drag-drop/DropTargetRenderer.tsx +277 -0
- package/src/drag-drop/dragDropTypes.ts +47 -0
- package/src/drag-drop/index.ts +5 -0
- package/src/editable-label/EditableLabel.css +28 -0
- package/src/editable-label/EditableLabel.tsx +99 -0
- package/src/editable-label/index.ts +1 -0
- package/src/flexbox/Flexbox.css +45 -0
- package/src/flexbox/Flexbox.tsx +70 -0
- package/src/flexbox/FlexboxLayout.tsx +28 -0
- package/src/flexbox/FluidGrid.css +134 -0
- package/src/flexbox/FluidGrid.tsx +82 -0
- package/src/flexbox/FluidGridLayout.tsx +9 -0
- package/src/flexbox/Splitter.css +140 -0
- package/src/flexbox/Splitter.tsx +127 -0
- package/src/flexbox/flexbox-utils.ts +128 -0
- package/src/flexbox/flexboxTypes.ts +68 -0
- package/src/flexbox/index.ts +5 -0
- package/src/flexbox/useResponsiveSizing.ts +82 -0
- package/src/flexbox/useSplitterResizing.ts +270 -0
- package/src/index.ts +19 -0
- package/src/layout-action.ts +21 -0
- package/src/layout-header/ActionButton.tsx +23 -0
- package/src/layout-header/Header.css +8 -0
- package/src/layout-header/Header.tsx +216 -0
- package/src/layout-header/index.ts +1 -0
- package/src/layout-provider/LayoutProvider.tsx +161 -0
- package/src/layout-provider/LayoutProviderContext.ts +17 -0
- package/src/layout-provider/index.ts +3 -0
- package/src/layout-provider/useLayoutDragDrop.ts +210 -0
- package/src/layout-reducer/flexUtils.ts +276 -0
- package/src/layout-reducer/index.ts +5 -0
- package/src/layout-reducer/insert-layout-element.ts +365 -0
- package/src/layout-reducer/layout-reducer.ts +237 -0
- package/src/layout-reducer/layoutTypes.ts +159 -0
- package/src/layout-reducer/layoutUtils.ts +288 -0
- package/src/layout-reducer/remove-layout-element.ts +226 -0
- package/src/layout-reducer/replace-layout-element.ts +113 -0
- package/src/layout-reducer/resize-flex-children.ts +55 -0
- package/src/layout-reducer/wrap-layout-element.ts +307 -0
- package/src/layout-view/View.css +61 -0
- package/src/layout-view/View.tsx +143 -0
- package/src/layout-view/ViewContext.ts +30 -0
- package/src/layout-view/index.ts +5 -0
- package/src/layout-view/useView.tsx +104 -0
- package/src/layout-view/useViewActionDispatcher.ts +123 -0
- package/src/layout-view/useViewResize.ts +53 -0
- package/src/layout-view/viewTypes.ts +35 -0
- package/src/palette/Palette.css +33 -0
- package/src/palette/Palette.tsx +140 -0
- package/src/palette/PaletteSalt.css +9 -0
- package/src/palette/PaletteSalt.tsx +79 -0
- package/src/palette/index.ts +3 -0
- package/src/placeholder/Placeholder.css +10 -0
- package/src/placeholder/Placeholder.tsx +38 -0
- package/src/placeholder/index.ts +1 -0
- package/src/registry/ComponentRegistry.ts +44 -0
- package/src/registry/index.ts +1 -0
- package/src/responsive/breakpoints.ts +62 -0
- package/src/responsive/index.ts +3 -0
- package/src/responsive/measureMinimumNodeSize.ts +23 -0
- package/src/responsive/overflowUtils.js +14 -0
- package/src/responsive/use-breakpoints.ts +101 -0
- package/src/responsive/useResizeObserver.ts +154 -0
- package/src/responsive/utils.ts +37 -0
- package/src/stack/Stack.css +39 -0
- package/src/stack/Stack.tsx +173 -0
- package/src/stack/StackLayout.tsx +119 -0
- package/src/stack/index.ts +4 -0
- package/src/stack/stackTypes.ts +22 -0
- package/src/tabs/TabPanel.css +12 -0
- package/src/tabs/TabPanel.tsx +17 -0
- package/src/tabs/index.ts +1 -0
- package/src/tools/config-wrapper/ConfigWrapper.tsx +55 -0
- package/src/tools/config-wrapper/index.ts +1 -0
- package/src/tools/devtools-box/layout-configurator.css +112 -0
- package/src/tools/devtools-box/layout-configurator.jsx +369 -0
- package/src/tools/devtools-tree/layout-tree-viewer.css +15 -0
- package/src/tools/devtools-tree/layout-tree-viewer.jsx +36 -0
- package/src/tools/index.ts +4 -0
- package/src/use-persistent-state.ts +112 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/pathUtils.ts +283 -0
- package/src/utils/propUtils.ts +26 -0
- package/src/utils/refUtils.ts +16 -0
- package/src/utils/styleUtils.ts +13 -0
- package/src/utils/typeOf.ts +25 -0
- package/tsconfig-emit-types.json +11 -0
- package/LICENSE +0 -201
- package/cjs/index.js +0 -20
- package/cjs/index.js.map +0 -7
- package/esm/index.js +0 -20
- package/esm/index.js.map +0 -7
- package/index.css +0 -2
- 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
|
+
}
|