@vuu-ui/vuu-layout 2.1.19-beta.1 → 2.1.19-beta.2
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 +12 -13
- package/src/Component.js +14 -0
- package/src/LayoutContainer.css.js +24 -0
- package/src/LayoutContainer.js +26 -0
- package/src/debug.js +16 -0
- package/src/dock-layout/DockLayout.css.js +36 -0
- package/src/dock-layout/DockLayout.js +43 -0
- package/src/dock-layout/Drawer.css.js +150 -0
- package/src/dock-layout/Drawer.js +82 -0
- package/src/dock-layout/index.js +2 -0
- package/src/drag-drop/BoxModel.js +337 -0
- package/src/drag-drop/DragState.js +119 -0
- package/src/drag-drop/Draggable.js +140 -0
- package/src/drag-drop/DropTarget.js +286 -0
- package/src/drag-drop/DropTargetRenderer.js +275 -0
- package/src/drag-drop/dragDropTypes.js +0 -0
- package/src/drag-drop/index.js +3 -0
- package/src/flexbox/Flexbox.css.js +45 -0
- package/src/flexbox/Flexbox.js +45 -0
- package/src/flexbox/FlexboxLayout.js +27 -0
- package/src/flexbox/Splitter.css.js +57 -0
- package/src/flexbox/Splitter.js +112 -0
- package/src/flexbox/flexbox-utils.js +92 -0
- package/src/flexbox/flexboxTypes.js +0 -0
- package/src/flexbox/index.js +2 -0
- package/src/flexbox/useSplitterResizing.js +181 -0
- package/src/index.js +20 -0
- package/src/layout-action.js +20 -0
- package/src/layout-header/ActionButton.js +15 -0
- package/src/layout-header/Header.css.js +32 -0
- package/src/layout-header/Header.js +99 -0
- package/src/layout-header/index.js +1 -0
- package/src/layout-header/useHeader.js +76 -0
- package/src/layout-provider/LayoutProvider.js +242 -0
- package/src/layout-provider/LayoutProviderContext.js +13 -0
- package/src/layout-provider/index.js +2 -0
- package/src/layout-provider/useLayoutDragDrop.js +147 -0
- package/src/layout-reducer/flexUtils.js +186 -0
- package/src/layout-reducer/index.js +4 -0
- package/src/layout-reducer/insert-layout-element.js +179 -0
- package/src/layout-reducer/layout-reducer.js +121 -0
- package/src/layout-reducer/layoutTypes.js +34 -0
- package/src/layout-reducer/layoutUtils.js +178 -0
- package/src/layout-reducer/move-layout-element.js +18 -0
- package/src/layout-reducer/remove-layout-element.js +161 -0
- package/src/layout-reducer/replace-layout-element.js +70 -0
- package/src/layout-reducer/resize-flex-children.js +48 -0
- package/src/layout-reducer/wrap-layout-element.js +134 -0
- package/src/layout-view/View.css.js +76 -0
- package/src/layout-view/View.js +116 -0
- package/src/layout-view/useView.js +69 -0
- package/src/layout-view/useViewBroadcastChannel.js +29 -0
- package/src/layout-view/useViewResize.js +27 -0
- package/src/layout-view/viewTypes.js +0 -0
- package/src/layout-view-actions/ViewContext.js +11 -0
- package/src/layout-view-actions/useViewActionDispatcher.js +116 -0
- package/src/layout-view-actions/useViewContributions.js +31 -0
- package/src/palette/Palette.css.js +31 -0
- package/src/palette/Palette.js +85 -0
- package/src/palette/index.js +1 -0
- package/src/placeholder/LayoutStartPanel.css.js +35 -0
- package/src/placeholder/LayoutStartPanel.js +57 -0
- package/src/placeholder/Placeholder.css.js +20 -0
- package/src/placeholder/Placeholder.js +33 -0
- package/src/placeholder/index.js +1 -0
- package/src/responsive/index.js +2 -0
- package/src/responsive/measureMinimumNodeSize.js +24 -0
- package/src/responsive/useResizeObserver.js +109 -0
- package/src/responsive/utils.js +27 -0
- package/src/stack/Stack.css.js +44 -0
- package/src/stack/Stack.js +98 -0
- package/src/stack/StackLayout.js +116 -0
- package/src/stack/index.js +3 -0
- package/src/stack/stackTypes.js +0 -0
- package/src/tabs/TabPanel.css.js +17 -0
- package/src/tabs/TabPanel.js +11 -0
- package/src/tabs/index.js +1 -0
- package/src/tools/config-wrapper/ConfigWrapper.js +55 -0
- package/src/tools/config-wrapper/index.js +1 -0
- package/src/tools/devtools-box/layout-configurator.css.js +113 -0
- package/src/tools/devtools-tree/layout-tree-viewer.css.js +17 -0
- package/src/tools/index.js +3 -0
- package/src/use-persistent-state.js +43 -0
- package/src/utils/index.js +5 -0
- package/src/utils/pathUtils.js +212 -0
- package/src/utils/propUtils.js +15 -0
- package/src/utils/refUtils.js +5 -0
- package/src/utils/styleUtils.js +9 -0
- package/src/utils/typeOf.js +15 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
import { useViewDispatch } from "../layout-view-actions/ViewContext.js";
|
|
3
|
+
import { queryClosest } from "@vuu-ui/vuu-utils";
|
|
4
|
+
const useHeader = ({ onCollapse, onEditTitle, onExpand, title })=>{
|
|
5
|
+
const [editing, setEditing] = useState(false);
|
|
6
|
+
const [value, setValue] = useState(title);
|
|
7
|
+
const labelFieldRef = useRef(null);
|
|
8
|
+
const viewDispatch = useViewDispatch();
|
|
9
|
+
const handleClose = useCallback((evt)=>viewDispatch?.({
|
|
10
|
+
type: "remove"
|
|
11
|
+
}, evt), [
|
|
12
|
+
viewDispatch
|
|
13
|
+
]);
|
|
14
|
+
const focusTitle = useCallback(()=>{
|
|
15
|
+
labelFieldRef.current?.focus();
|
|
16
|
+
}, []);
|
|
17
|
+
const handleClickEdit = useCallback(()=>{
|
|
18
|
+
focusTitle();
|
|
19
|
+
setEditing((isEditing)=>!isEditing);
|
|
20
|
+
}, [
|
|
21
|
+
focusTitle
|
|
22
|
+
]);
|
|
23
|
+
const handleTitleKeyDown = (evt)=>{
|
|
24
|
+
if ("Enter" === evt.key) setEditing(true);
|
|
25
|
+
};
|
|
26
|
+
const handleMouseDown = useCallback((e)=>{
|
|
27
|
+
const button = queryClosest(e.target, ".saltButton");
|
|
28
|
+
if (null === button) viewDispatch?.({
|
|
29
|
+
type: "mousedown"
|
|
30
|
+
}, e);
|
|
31
|
+
}, [
|
|
32
|
+
viewDispatch
|
|
33
|
+
]);
|
|
34
|
+
const handleExitEditMode = (originalValue = "", finalValue = "", allowDeactivation = true, editCancelled = false)=>{
|
|
35
|
+
setEditing(false);
|
|
36
|
+
if (editCancelled) setValue(originalValue);
|
|
37
|
+
else if (finalValue !== originalValue) {
|
|
38
|
+
setValue(finalValue);
|
|
39
|
+
onEditTitle?.(finalValue);
|
|
40
|
+
}
|
|
41
|
+
if (false === allowDeactivation) labelFieldRef.current?.focus();
|
|
42
|
+
};
|
|
43
|
+
const handleToggleCollapse = useCallback((e)=>{
|
|
44
|
+
viewDispatch?.({
|
|
45
|
+
type: "collapse"
|
|
46
|
+
}, e);
|
|
47
|
+
onCollapse?.();
|
|
48
|
+
}, [
|
|
49
|
+
onCollapse,
|
|
50
|
+
viewDispatch
|
|
51
|
+
]);
|
|
52
|
+
const handleToggleExpand = useCallback((e)=>{
|
|
53
|
+
viewDispatch?.({
|
|
54
|
+
type: "expand"
|
|
55
|
+
}, e);
|
|
56
|
+
onExpand?.();
|
|
57
|
+
}, [
|
|
58
|
+
onExpand,
|
|
59
|
+
viewDispatch
|
|
60
|
+
]);
|
|
61
|
+
return {
|
|
62
|
+
editing,
|
|
63
|
+
focusTitle,
|
|
64
|
+
labelFieldRef,
|
|
65
|
+
onClickEdit: handleClickEdit,
|
|
66
|
+
onClose: handleClose,
|
|
67
|
+
onExitEditMode: handleExitEditMode,
|
|
68
|
+
onMouseDown: handleMouseDown,
|
|
69
|
+
onToggleCollapse: handleToggleCollapse,
|
|
70
|
+
onToggleExpand: handleToggleExpand,
|
|
71
|
+
onTitleKeyDown: handleTitleKeyDown,
|
|
72
|
+
setValue,
|
|
73
|
+
value
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export { useHeader };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { VuuShellLocation, usePlaceholderJSON } from "@vuu-ui/vuu-utils";
|
|
3
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { LayoutActionType, cloneElementAddLayoutProps, layoutFromJson, layoutQuery, layoutReducer, layoutToJSON } from "../layout-reducer/index.js";
|
|
5
|
+
import { findTarget, getChildProp, getProp, getProps, typeOf } from "../utils/index.js";
|
|
6
|
+
import { LayoutProviderContext } from "./LayoutProviderContext.js";
|
|
7
|
+
import { useLayoutDragDrop } from "./useLayoutDragDrop.js";
|
|
8
|
+
const isWorkspaceContainer = (props)=>props.id === VuuShellLocation.WorkspaceContainer;
|
|
9
|
+
const isMultiWorkspaceContainer = (props)=>props.id === VuuShellLocation.MultiWorkspaceContainer;
|
|
10
|
+
const shouldSave = (action)=>[
|
|
11
|
+
"add",
|
|
12
|
+
"drag-drop",
|
|
13
|
+
"remove",
|
|
14
|
+
"set-title",
|
|
15
|
+
"splitter-resize",
|
|
16
|
+
"switch-tab"
|
|
17
|
+
].includes(action.type);
|
|
18
|
+
const getLayoutChangeReason = (action)=>{
|
|
19
|
+
switch(action.type){
|
|
20
|
+
case "switch-tab":
|
|
21
|
+
if (action.id === VuuShellLocation.Workspace) return "switch-active-layout";
|
|
22
|
+
return "switch-active-tab";
|
|
23
|
+
case "save":
|
|
24
|
+
return "save-feature-props";
|
|
25
|
+
case "drag-drop":
|
|
26
|
+
return "drag-drop-operation";
|
|
27
|
+
case "add":
|
|
28
|
+
return "add-component";
|
|
29
|
+
case "remove":
|
|
30
|
+
return "remove-component";
|
|
31
|
+
case "splitter-resize":
|
|
32
|
+
return "resize-component";
|
|
33
|
+
case "set-title":
|
|
34
|
+
return "edit-feature-title";
|
|
35
|
+
default:
|
|
36
|
+
throw Error("unknown layout action");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const LayoutProviderVersion = ()=>{
|
|
40
|
+
const version = useLayoutProviderVersion();
|
|
41
|
+
return /*#__PURE__*/ jsx("div", {
|
|
42
|
+
children: `Context: ${version} `
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const pathToDropTarget = `#${VuuShellLocation.Workspace}.ACTIVE_CHILD`;
|
|
46
|
+
const LayoutProvider = (props)=>{
|
|
47
|
+
const { children, createNewChild, workspaceJSON, onLayoutChange } = props;
|
|
48
|
+
const state = useRef(void 0);
|
|
49
|
+
const childrenRef = useRef(children);
|
|
50
|
+
const [, forceRefresh] = useState(null);
|
|
51
|
+
const serializeState = useCallback((source, layoutChangeReason)=>{
|
|
52
|
+
if (onLayoutChange) {
|
|
53
|
+
const workspaceContainer = findTarget(source, isWorkspaceContainer) || state.current;
|
|
54
|
+
const isLayoutContainer = "LayoutContainer" === typeOf(workspaceContainer);
|
|
55
|
+
const target = isLayoutContainer ? getProps(workspaceContainer).children[0] : workspaceContainer;
|
|
56
|
+
const serializedModel = layoutToJSON(target);
|
|
57
|
+
onLayoutChange(serializedModel, layoutChangeReason);
|
|
58
|
+
}
|
|
59
|
+
}, [
|
|
60
|
+
onLayoutChange
|
|
61
|
+
]);
|
|
62
|
+
const dispatchLayoutAction = useCallback((action, suppressSave = false)=>{
|
|
63
|
+
const nextState = layoutReducer(state.current, action);
|
|
64
|
+
if (nextState !== state.current) {
|
|
65
|
+
state.current = nextState;
|
|
66
|
+
forceRefresh({});
|
|
67
|
+
if (!suppressSave && shouldSave(action)) serializeState(nextState, getLayoutChangeReason(action));
|
|
68
|
+
}
|
|
69
|
+
}, [
|
|
70
|
+
forceRefresh,
|
|
71
|
+
serializeState
|
|
72
|
+
]);
|
|
73
|
+
const addComponentToWorkspace = useCallback((component)=>{
|
|
74
|
+
dispatchLayoutAction({
|
|
75
|
+
type: "add",
|
|
76
|
+
path: `#${VuuShellLocation.Workspace}`,
|
|
77
|
+
component
|
|
78
|
+
});
|
|
79
|
+
}, [
|
|
80
|
+
dispatchLayoutAction
|
|
81
|
+
]);
|
|
82
|
+
const switchWorkspace = useCallback((idx)=>{
|
|
83
|
+
dispatchLayoutAction({
|
|
84
|
+
type: "switch-tab",
|
|
85
|
+
nextIdx: idx,
|
|
86
|
+
path: `#${VuuShellLocation.MultiWorkspaceContainer}`
|
|
87
|
+
});
|
|
88
|
+
}, [
|
|
89
|
+
dispatchLayoutAction
|
|
90
|
+
]);
|
|
91
|
+
const showComponentInContextPanel = useCallback((component, title, onContextPanelClose)=>{
|
|
92
|
+
dispatchLayoutAction({
|
|
93
|
+
type: "set-props",
|
|
94
|
+
path: `#${VuuShellLocation.ContextPanel}`,
|
|
95
|
+
props: {
|
|
96
|
+
expanded: true,
|
|
97
|
+
content: component,
|
|
98
|
+
onClose: onContextPanelClose,
|
|
99
|
+
title
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}, [
|
|
103
|
+
dispatchLayoutAction
|
|
104
|
+
]);
|
|
105
|
+
const layoutActionDispatcher = useCallback((action)=>{
|
|
106
|
+
switch(action.type){
|
|
107
|
+
case "drag-start":
|
|
108
|
+
prepareToDragLayout(action);
|
|
109
|
+
break;
|
|
110
|
+
case "save":
|
|
111
|
+
if (state.current) serializeState(state.current, getLayoutChangeReason(action));
|
|
112
|
+
break;
|
|
113
|
+
case "query":
|
|
114
|
+
if ("PARENT_CONTAINER" === action.query) return layoutQuery(action.query, action.path, state.current);
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
dispatchLayoutAction(action);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}, [
|
|
121
|
+
dispatchLayoutAction,
|
|
122
|
+
serializeState
|
|
123
|
+
]);
|
|
124
|
+
const prepareToDragLayout = useLayoutDragDrop(state, layoutActionDispatcher, pathToDropTarget);
|
|
125
|
+
useEffect(()=>{
|
|
126
|
+
if (workspaceJSON) if (Array.isArray(workspaceJSON)) {
|
|
127
|
+
const targetContainer = findTarget(state.current, isMultiWorkspaceContainer);
|
|
128
|
+
if (targetContainer) {
|
|
129
|
+
const { path: targetContainerPath } = targetContainer.props;
|
|
130
|
+
const target = getChildProp(targetContainer);
|
|
131
|
+
const newLayouts = workspaceJSON.map((ws, i)=>layoutFromJson(ws, `${targetContainerPath}.${i}`));
|
|
132
|
+
const action = target ? {
|
|
133
|
+
type: LayoutActionType.REPLACE,
|
|
134
|
+
target,
|
|
135
|
+
replacement: newLayouts
|
|
136
|
+
} : {
|
|
137
|
+
type: LayoutActionType.ADD,
|
|
138
|
+
path: targetContainerPath,
|
|
139
|
+
component: newLayouts
|
|
140
|
+
};
|
|
141
|
+
dispatchLayoutAction(action, true);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
const targetContainer = findTarget(state.current, isWorkspaceContainer);
|
|
145
|
+
if (targetContainer) {
|
|
146
|
+
const { path: targetContainerPath } = targetContainer.props;
|
|
147
|
+
const target = getChildProp(targetContainer);
|
|
148
|
+
const newLayout = layoutFromJson(workspaceJSON, `${targetContainerPath}.0`);
|
|
149
|
+
const action = target ? {
|
|
150
|
+
type: LayoutActionType.REPLACE,
|
|
151
|
+
target,
|
|
152
|
+
replacement: newLayout
|
|
153
|
+
} : {
|
|
154
|
+
type: LayoutActionType.ADD,
|
|
155
|
+
path: targetContainerPath,
|
|
156
|
+
component: newLayout
|
|
157
|
+
};
|
|
158
|
+
dispatchLayoutAction(action, true);
|
|
159
|
+
} else if (workspaceJSON.id === getProp(state.current, "id")) {
|
|
160
|
+
const newLayout = layoutFromJson(workspaceJSON, "0");
|
|
161
|
+
const action = {
|
|
162
|
+
type: LayoutActionType.REPLACE,
|
|
163
|
+
target: state.current,
|
|
164
|
+
replacement: newLayout
|
|
165
|
+
};
|
|
166
|
+
dispatchLayoutAction(action, true);
|
|
167
|
+
} else throw Error(`LayoutProvider unable to render new workspaceJson, no valid target container. Use a valid Shell Layout Template or include a container with the standard workspace id '${VuuShellLocation.Workspace}' `);
|
|
168
|
+
}
|
|
169
|
+
}, [
|
|
170
|
+
dispatchLayoutAction,
|
|
171
|
+
workspaceJSON
|
|
172
|
+
]);
|
|
173
|
+
if (void 0 === state.current) state.current = cloneElementAddLayoutProps(children);
|
|
174
|
+
else if (children !== childrenRef.current) {
|
|
175
|
+
state.current = cloneElementAddLayoutProps(children, state.current);
|
|
176
|
+
childrenRef.current = children;
|
|
177
|
+
}
|
|
178
|
+
return /*#__PURE__*/ jsx(LayoutProviderContext.Provider, {
|
|
179
|
+
value: {
|
|
180
|
+
addComponentToWorkspace,
|
|
181
|
+
createNewChild,
|
|
182
|
+
dispatchLayoutProvider: layoutActionDispatcher,
|
|
183
|
+
showComponentInContextPanel,
|
|
184
|
+
switchWorkspace,
|
|
185
|
+
version: 0
|
|
186
|
+
},
|
|
187
|
+
children: state.current
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const useLayoutProviderDispatch = ()=>{
|
|
191
|
+
const { dispatchLayoutProvider } = useContext(LayoutProviderContext);
|
|
192
|
+
return dispatchLayoutProvider;
|
|
193
|
+
};
|
|
194
|
+
const useLayoutOperation = ()=>{
|
|
195
|
+
const { addComponentToWorkspace, showComponentInContextPanel, switchWorkspace } = useContext(LayoutProviderContext);
|
|
196
|
+
return {
|
|
197
|
+
addComponentToWorkspace,
|
|
198
|
+
showComponentInContextPanel,
|
|
199
|
+
switchWorkspace
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
const useLayoutCreateNewChild = ()=>{
|
|
203
|
+
const layoutPlaceholderJSON = usePlaceholderJSON();
|
|
204
|
+
const { createNewChild } = useContext(LayoutProviderContext);
|
|
205
|
+
const defaultCreateNewChild = useMemo(()=>function() {
|
|
206
|
+
if (layoutPlaceholderJSON) {
|
|
207
|
+
const { props } = layoutPlaceholderJSON;
|
|
208
|
+
return layoutFromJson({
|
|
209
|
+
...layoutPlaceholderJSON,
|
|
210
|
+
props: {
|
|
211
|
+
...props,
|
|
212
|
+
resizeable: true,
|
|
213
|
+
style: {
|
|
214
|
+
...props?.style,
|
|
215
|
+
flexGrow: 1,
|
|
216
|
+
flexShrink: 0,
|
|
217
|
+
flexBasis: 0
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}, "0");
|
|
221
|
+
}
|
|
222
|
+
return layoutFromJson({
|
|
223
|
+
type: "Placeholder",
|
|
224
|
+
props: {
|
|
225
|
+
resizeable: true,
|
|
226
|
+
style: {
|
|
227
|
+
flexGrow: 1,
|
|
228
|
+
flexShrink: 0,
|
|
229
|
+
flexBasis: 0
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}, "0");
|
|
233
|
+
}, [
|
|
234
|
+
layoutPlaceholderJSON
|
|
235
|
+
]);
|
|
236
|
+
return createNewChild ?? defaultCreateNewChild;
|
|
237
|
+
};
|
|
238
|
+
const useLayoutProviderVersion = ()=>{
|
|
239
|
+
const { version } = useContext(LayoutProviderContext);
|
|
240
|
+
return version;
|
|
241
|
+
};
|
|
242
|
+
export { LayoutProvider, LayoutProviderVersion, useLayoutCreateNewChild, useLayoutOperation, useLayoutProviderDispatch, useLayoutProviderVersion };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
const unconfiguredLayoutProviderDispatch = (action)=>console.log(`dispatch ${action.type}, have you forgotten to provide a LayoutProvider ?`);
|
|
3
|
+
const unconfiguredService = (message)=>()=>console.log(`${message}, have you forgotten to provide a LayoutProvider ?`);
|
|
4
|
+
const MissingLayoutContextPanel = unconfiguredService("showComponentInContextPanel");
|
|
5
|
+
const isUnconfiguredProperty = (property)=>property === MissingLayoutContextPanel;
|
|
6
|
+
const LayoutProviderContext = createContext({
|
|
7
|
+
addComponentToWorkspace: unconfiguredService("addComponentToWorkspace"),
|
|
8
|
+
dispatchLayoutProvider: unconfiguredLayoutProviderDispatch,
|
|
9
|
+
showComponentInContextPanel: MissingLayoutContextPanel,
|
|
10
|
+
switchWorkspace: unconfiguredService("switchWorkspace"),
|
|
11
|
+
version: -1
|
|
12
|
+
});
|
|
13
|
+
export { LayoutProviderContext, isUnconfiguredProperty };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
import { Draggable } from "../drag-drop/index.js";
|
|
3
|
+
import { getIntrinsicSize } from "../layout-reducer/flexUtils.js";
|
|
4
|
+
import { followPath, resolvePath } from "../utils/index.js";
|
|
5
|
+
const NO_INSTRUCTIONS = {};
|
|
6
|
+
const NO_OFFSETS = [
|
|
7
|
+
0,
|
|
8
|
+
0
|
|
9
|
+
];
|
|
10
|
+
const getDragElement = (rect, id, dragElement)=>{
|
|
11
|
+
const wrapper = document.createElement("div");
|
|
12
|
+
wrapper.className = "vuuSimpleDraggableWrapper";
|
|
13
|
+
wrapper.classList.add("vuuSimpleDraggableWrapper", "salt-theme", "salt-density-medium");
|
|
14
|
+
wrapper.dataset.dragging = "true";
|
|
15
|
+
const div = dragElement ?? document.createElement("div");
|
|
16
|
+
div.id = id;
|
|
17
|
+
wrapper.appendChild(div);
|
|
18
|
+
document.body.appendChild(wrapper);
|
|
19
|
+
const cssText = `top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;`;
|
|
20
|
+
return [
|
|
21
|
+
wrapper,
|
|
22
|
+
cssText,
|
|
23
|
+
rect.left,
|
|
24
|
+
rect.top
|
|
25
|
+
];
|
|
26
|
+
};
|
|
27
|
+
const determineDragOffsets = (draggedElement)=>{
|
|
28
|
+
const { offsetParent } = draggedElement;
|
|
29
|
+
if (null === offsetParent) return NO_OFFSETS;
|
|
30
|
+
{
|
|
31
|
+
const { left: offsetLeft, top: offsetTop } = offsetParent.getBoundingClientRect();
|
|
32
|
+
return [
|
|
33
|
+
offsetLeft,
|
|
34
|
+
offsetTop
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const useLayoutDragDrop = (rootLayoutRef, dispatch, pathToDropTarget)=>{
|
|
39
|
+
const dragActionRef = useRef(void 0);
|
|
40
|
+
const dragOperationRef = useRef(void 0);
|
|
41
|
+
const draggableHTMLElementRef = useRef(void 0);
|
|
42
|
+
const handleDrag = useCallback((x, y)=>{
|
|
43
|
+
if (dragOperationRef.current && draggableHTMLElementRef.current) {
|
|
44
|
+
const { dragOffsets: [offsetX, offsetY], targetPosition } = dragOperationRef.current;
|
|
45
|
+
const left = "number" == typeof x ? x - offsetX : targetPosition.left;
|
|
46
|
+
const top = "number" == typeof y ? y - offsetY : targetPosition.top;
|
|
47
|
+
if (left !== targetPosition.left || top !== targetPosition.top) {
|
|
48
|
+
dragOperationRef.current.targetPosition.left = left;
|
|
49
|
+
dragOperationRef.current.targetPosition.top = top;
|
|
50
|
+
draggableHTMLElementRef.current.style.top = top + "px";
|
|
51
|
+
draggableHTMLElementRef.current.style.left = left + "px";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, []);
|
|
55
|
+
const handleDrop = useCallback((dropTarget)=>{
|
|
56
|
+
if (dragOperationRef.current) {
|
|
57
|
+
const { dragInstructions, payload: draggedReactElement, originalCSS } = dragOperationRef.current;
|
|
58
|
+
if (dropTarget) dispatch({
|
|
59
|
+
type: "drag-drop",
|
|
60
|
+
draggedReactElement,
|
|
61
|
+
dragInstructions,
|
|
62
|
+
dropTarget
|
|
63
|
+
});
|
|
64
|
+
if (draggableHTMLElementRef.current) if (!dropTarget && dragInstructions.DriftHomeIfNoDropTarget) {
|
|
65
|
+
console.log("drift back to start");
|
|
66
|
+
document.body.removeChild(draggableHTMLElementRef.current);
|
|
67
|
+
} else if (dragInstructions.RemoveDraggableOnDragEnd) document.body.removeChild(draggableHTMLElementRef.current);
|
|
68
|
+
else {
|
|
69
|
+
draggableHTMLElementRef.current.style.cssText = originalCSS;
|
|
70
|
+
delete draggableHTMLElementRef.current.dataset.dragging;
|
|
71
|
+
}
|
|
72
|
+
dragActionRef.current = void 0;
|
|
73
|
+
dragOperationRef.current = void 0;
|
|
74
|
+
draggableHTMLElementRef.current = void 0;
|
|
75
|
+
}
|
|
76
|
+
}, [
|
|
77
|
+
dispatch
|
|
78
|
+
]);
|
|
79
|
+
const handleDragStart = useCallback((evt)=>{
|
|
80
|
+
if (dragActionRef.current) {
|
|
81
|
+
const { payload: component, dragContainerPath, dragElement, dragRect, instructions = NO_INSTRUCTIONS, path } = dragActionRef.current;
|
|
82
|
+
const { current: rootLayout } = rootLayoutRef;
|
|
83
|
+
const dragPos = {
|
|
84
|
+
x: evt.clientX,
|
|
85
|
+
y: evt.clientY
|
|
86
|
+
};
|
|
87
|
+
const dragPayload = component ?? followPath(rootLayout, path, true);
|
|
88
|
+
const { id: dragPayloadId } = dragPayload.props;
|
|
89
|
+
const intrinsicSize = getIntrinsicSize(dragPayload);
|
|
90
|
+
let originalCSS = "", dragCSS = "", dragTransform = "";
|
|
91
|
+
let dragStartLeft = -1;
|
|
92
|
+
let dragStartTop = -1;
|
|
93
|
+
let dragOffsets = NO_OFFSETS;
|
|
94
|
+
let element = document.getElementById(dragPayloadId);
|
|
95
|
+
if (null === element) [element, dragCSS, dragStartLeft, dragStartTop] = getDragElement(dragRect, dragPayloadId, dragElement);
|
|
96
|
+
else {
|
|
97
|
+
dragOffsets = determineDragOffsets(element);
|
|
98
|
+
const [offsetLeft, offsetTop] = dragOffsets;
|
|
99
|
+
const { width, height, left, top } = element.getBoundingClientRect();
|
|
100
|
+
dragStartLeft = left - offsetLeft;
|
|
101
|
+
dragStartTop = top - offsetTop;
|
|
102
|
+
dragCSS = `width:${width}px;height:${height}px;left:${dragStartLeft}px;top:${dragStartTop}px;z-index: 100;background-color:#ccc;opacity: 0.6;`;
|
|
103
|
+
element.dataset.dragging = "true";
|
|
104
|
+
originalCSS = element.style.cssText;
|
|
105
|
+
}
|
|
106
|
+
dragTransform = Draggable.initDrag(rootLayoutRef.current, dragContainerPath, dragRect, dragPos, {
|
|
107
|
+
drag: handleDrag,
|
|
108
|
+
drop: handleDrop
|
|
109
|
+
}, intrinsicSize);
|
|
110
|
+
element.style.cssText = dragCSS + dragTransform;
|
|
111
|
+
draggableHTMLElementRef.current = element;
|
|
112
|
+
dragOperationRef.current = {
|
|
113
|
+
payload: dragPayload,
|
|
114
|
+
originalCSS,
|
|
115
|
+
dragRect,
|
|
116
|
+
dragOffsets,
|
|
117
|
+
dragInstructions: instructions,
|
|
118
|
+
targetPosition: {
|
|
119
|
+
left: dragStartLeft,
|
|
120
|
+
top: dragStartTop
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}, [
|
|
125
|
+
handleDrag,
|
|
126
|
+
handleDrop,
|
|
127
|
+
rootLayoutRef
|
|
128
|
+
]);
|
|
129
|
+
const prepareToDrag = useCallback((action)=>{
|
|
130
|
+
const { evt, ...options } = action;
|
|
131
|
+
try {
|
|
132
|
+
dragActionRef.current = {
|
|
133
|
+
...options,
|
|
134
|
+
dragContainerPath: resolvePath(rootLayoutRef.current, pathToDropTarget)
|
|
135
|
+
};
|
|
136
|
+
Draggable.handleMousedown(evt, handleDragStart, options.instructions);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.warn(`[useLayoutDragDrop] path ${pathToDropTarget} not resolved, the application layout does not have the required elements to support drag drop`);
|
|
139
|
+
}
|
|
140
|
+
}, [
|
|
141
|
+
handleDragStart,
|
|
142
|
+
pathToDropTarget,
|
|
143
|
+
rootLayoutRef
|
|
144
|
+
]);
|
|
145
|
+
return prepareToDrag;
|
|
146
|
+
};
|
|
147
|
+
export { useLayoutDragDrop };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { getLayoutComponent, uuid } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import react from "react";
|
|
3
|
+
import { getProps, resetPath } from "../utils/index.js";
|
|
4
|
+
const placeHolderProps = {
|
|
5
|
+
"data-placeholder": true,
|
|
6
|
+
"data-resizeable": true
|
|
7
|
+
};
|
|
8
|
+
const NO_STYLE = {};
|
|
9
|
+
const auto = "auto";
|
|
10
|
+
const defaultFlexStyle = {
|
|
11
|
+
flexBasis: 0,
|
|
12
|
+
flexGrow: 1,
|
|
13
|
+
flexShrink: 1,
|
|
14
|
+
height: auto,
|
|
15
|
+
width: auto
|
|
16
|
+
};
|
|
17
|
+
const CROSS_DIMENSION = {
|
|
18
|
+
height: "width",
|
|
19
|
+
width: "height"
|
|
20
|
+
};
|
|
21
|
+
const getFlexDimensions = (flexDirection = "row")=>{
|
|
22
|
+
if ("row" === flexDirection) return [
|
|
23
|
+
"width",
|
|
24
|
+
"height",
|
|
25
|
+
"column"
|
|
26
|
+
];
|
|
27
|
+
return [
|
|
28
|
+
"height",
|
|
29
|
+
"width",
|
|
30
|
+
"row"
|
|
31
|
+
];
|
|
32
|
+
};
|
|
33
|
+
const isPercentageSize = (value)=>"string" == typeof value && value.endsWith("%");
|
|
34
|
+
const getIntrinsicSize = (component)=>{
|
|
35
|
+
const { style: { width = auto, height = auto } = NO_STYLE } = component.props;
|
|
36
|
+
const numHeight = "number" == typeof height;
|
|
37
|
+
const numWidth = "number" == typeof width;
|
|
38
|
+
if (numHeight && numWidth) return {
|
|
39
|
+
height,
|
|
40
|
+
width
|
|
41
|
+
};
|
|
42
|
+
if (numHeight) return {
|
|
43
|
+
height
|
|
44
|
+
};
|
|
45
|
+
if (numWidth) return {
|
|
46
|
+
width
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
function getFlexStyle(component, dimension, pos) {
|
|
50
|
+
const crossDimension = CROSS_DIMENSION[dimension];
|
|
51
|
+
const { style: { [crossDimension]: intrinsicCrossSize = auto, ...intrinsicStyles } = NO_STYLE } = component.props;
|
|
52
|
+
if (pos && pos[dimension]) return {
|
|
53
|
+
...intrinsicStyles,
|
|
54
|
+
...defaultFlexStyle,
|
|
55
|
+
flexBasis: pos[dimension],
|
|
56
|
+
flexGrow: 0,
|
|
57
|
+
flexShrink: 0
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
...intrinsicStyles,
|
|
61
|
+
...defaultFlexStyle,
|
|
62
|
+
[crossDimension]: intrinsicCrossSize
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function hasUnboundedFlexStyle(component) {
|
|
66
|
+
const { style: { flex, flexGrow, flexShrink, flexBasis } = NO_STYLE } = component.props;
|
|
67
|
+
if ("number" == typeof flex) return true;
|
|
68
|
+
if (0 === flexBasis && 1 === flexGrow && 1 === flexShrink) return true;
|
|
69
|
+
if ("number" == typeof flexBasis) return false;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function getFlexOrIntrinsicStyle(component, dimension, pos) {
|
|
73
|
+
const crossDimension = CROSS_DIMENSION[dimension];
|
|
74
|
+
const { style: { [dimension]: intrinsicSize = auto, [crossDimension]: intrinsicCrossSize = auto, ...intrinsicStyles } = NO_STYLE } = component.props;
|
|
75
|
+
if (intrinsicSize !== auto) {
|
|
76
|
+
if (isPercentageSize(intrinsicSize)) return {
|
|
77
|
+
flexBasis: 0,
|
|
78
|
+
flexGrow: 1,
|
|
79
|
+
flexShrink: 1,
|
|
80
|
+
[dimension]: void 0,
|
|
81
|
+
[crossDimension]: intrinsicCrossSize
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
flexBasis: intrinsicSize,
|
|
85
|
+
flexGrow: 0,
|
|
86
|
+
flexShrink: 0,
|
|
87
|
+
[dimension]: intrinsicSize,
|
|
88
|
+
[crossDimension]: intrinsicCrossSize
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (pos && pos[dimension]) return {
|
|
92
|
+
...intrinsicStyles,
|
|
93
|
+
...defaultFlexStyle,
|
|
94
|
+
flexBasis: pos[dimension],
|
|
95
|
+
flexGrow: 0,
|
|
96
|
+
flexShrink: 0
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
...intrinsicStyles,
|
|
100
|
+
[crossDimension]: intrinsicCrossSize
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function wrapIntrinsicSizeComponentWithFlexbox(component, flexDirection, path, clientRect, dropRect) {
|
|
104
|
+
const wrappedChildren = [];
|
|
105
|
+
let pathIndex = 0;
|
|
106
|
+
let endPlaceholder;
|
|
107
|
+
if (clientRect && dropRect) {
|
|
108
|
+
let startPlaceholder;
|
|
109
|
+
const [dropLeft, dropTop, dropRight, dropBottom] = dropRect;
|
|
110
|
+
[startPlaceholder, endPlaceholder] = "column" === flexDirection ? [
|
|
111
|
+
dropTop - clientRect.top,
|
|
112
|
+
clientRect.bottom - dropBottom
|
|
113
|
+
] : [
|
|
114
|
+
dropLeft - clientRect.left,
|
|
115
|
+
clientRect.right - dropRight
|
|
116
|
+
];
|
|
117
|
+
if (startPlaceholder) wrappedChildren.push(createPlaceHolder(`${path}.${pathIndex++}`, startPlaceholder, {
|
|
118
|
+
flexGrow: 0,
|
|
119
|
+
flexShrink: 0
|
|
120
|
+
}));
|
|
121
|
+
} else endPlaceholder = true;
|
|
122
|
+
const { version = 0, style } = getProps(component);
|
|
123
|
+
wrappedChildren.push(resetPath(component, `${path}.${pathIndex++}`, {
|
|
124
|
+
version: version + 1,
|
|
125
|
+
style: {
|
|
126
|
+
...style,
|
|
127
|
+
flexBasis: "auto",
|
|
128
|
+
flexGrow: 0,
|
|
129
|
+
flexShrink: 0
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
if (endPlaceholder) wrappedChildren.push(createPlaceHolder(`${path}.${pathIndex++}`, 0, void 0, {
|
|
133
|
+
[`data-${flexDirection}-placeholder`]: true
|
|
134
|
+
}));
|
|
135
|
+
return createFlexbox(flexDirection, {
|
|
136
|
+
resizeable: false,
|
|
137
|
+
style: {
|
|
138
|
+
flexBasis: "auto"
|
|
139
|
+
}
|
|
140
|
+
}, wrappedChildren, path);
|
|
141
|
+
}
|
|
142
|
+
const getFlexValue = (flexBasis, flexFill)=>{
|
|
143
|
+
if (flexFill) return;
|
|
144
|
+
return 0 === flexBasis ? 1 : 0;
|
|
145
|
+
};
|
|
146
|
+
function createFlexbox(flexDirection, props, children, path) {
|
|
147
|
+
const id = uuid();
|
|
148
|
+
const { flexFill, style, resizeable = true } = props;
|
|
149
|
+
const { flexBasis = flexFill ? void 0 : "auto" } = style;
|
|
150
|
+
const flex = getFlexValue(flexBasis, flexFill);
|
|
151
|
+
const FlexboxLayout = getLayoutComponent("Flexbox");
|
|
152
|
+
return react.createElement(FlexboxLayout, {
|
|
153
|
+
id,
|
|
154
|
+
key: id,
|
|
155
|
+
path,
|
|
156
|
+
flexFill,
|
|
157
|
+
style: {
|
|
158
|
+
...style,
|
|
159
|
+
flexDirection,
|
|
160
|
+
flexBasis,
|
|
161
|
+
flexGrow: flex,
|
|
162
|
+
flexShrink: flex
|
|
163
|
+
},
|
|
164
|
+
resizeable
|
|
165
|
+
}, children);
|
|
166
|
+
}
|
|
167
|
+
const baseStyle = {
|
|
168
|
+
flexGrow: 1,
|
|
169
|
+
flexShrink: 1
|
|
170
|
+
};
|
|
171
|
+
function createPlaceHolder(path, size, style, props) {
|
|
172
|
+
const id = uuid();
|
|
173
|
+
return react.createElement("div", {
|
|
174
|
+
...placeHolderProps,
|
|
175
|
+
...props,
|
|
176
|
+
"data-path": path,
|
|
177
|
+
id,
|
|
178
|
+
key: id,
|
|
179
|
+
style: {
|
|
180
|
+
...baseStyle,
|
|
181
|
+
...style,
|
|
182
|
+
flexBasis: size
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export { createFlexbox, createPlaceHolder, getFlexDimensions, getFlexOrIntrinsicStyle, getFlexStyle, getIntrinsicSize, hasUnboundedFlexStyle, wrapIntrinsicSizeComponentWithFlexbox };
|