@vuu-ui/vuu-layout 0.5.14 → 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,21 @@
1
+ export const Action = {
2
+ ADD: 'add',
3
+ BLUR: 'blur',
4
+ BLUR_SPLITTER: 'blur-splitter',
5
+ DRAG_START: 'drag-start',
6
+ DRAG_STARTED: 'drag-started',
7
+ DRAG_DROP: 'drag-drop',
8
+ FOCUS: 'focus',
9
+ FOCUS_SPLITTER: 'focus-splitter',
10
+ INITIALIZE: 'initialize',
11
+ MAXIMIZE: 'maximize',
12
+ MINIMIZE: 'minimize',
13
+ REMOVE: 'remove',
14
+ REPLACE: 'replace',
15
+ RESTORE: 'restore',
16
+ SAVE: 'save',
17
+ SET_TITLE: 'set-title',
18
+ SPLITTER_RESIZE: 'splitter-resize',
19
+ SWITCH_TAB: 'switch-tab',
20
+ TEAR_OUT: 'tear-out'
21
+ };
@@ -0,0 +1,23 @@
1
+ import classnames from 'classnames';
2
+ import { HTMLAttributes, MouseEvent } from 'react';
3
+
4
+ export interface ActionButtonProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'onClick'> {
5
+ actionId: 'maximize' | 'minimize' | 'restore' | 'tearout';
6
+ iconName?: string;
7
+ onClick: (evt: MouseEvent, actionId: 'maximize' | 'minimize' | 'restore' | 'tearout') => void;
8
+ }
9
+
10
+ const ActionButton = ({ actionId, className, iconName, onClick, ...props }: ActionButtonProps) => {
11
+ const handleClick = (evt: MouseEvent) => {
12
+ onClick(evt, actionId);
13
+ };
14
+ return (
15
+ <button
16
+ {...props}
17
+ className={classnames('ActionButton', className)}
18
+ onClick={handleClick}
19
+ title="Close View"></button>
20
+ );
21
+ };
22
+
23
+ export default ActionButton;
@@ -0,0 +1,8 @@
1
+ .vuuHeader {
2
+ --saltToolbar-background: var(--salt-container-tertiary-background);
3
+ }
4
+
5
+ .salt-density-high .vuuHeader {
6
+ --saltToolbarField-marginTop: 0;
7
+ }
8
+
@@ -0,0 +1,216 @@
1
+ import classnames from "classnames";
2
+ import React, {
3
+ HTMLAttributes,
4
+ KeyboardEvent,
5
+ MouseEvent,
6
+ ReactElement,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import { Contribution, useViewDispatch } from "../layout-view";
11
+
12
+ import {
13
+ EditableLabel,
14
+ Toolbar,
15
+ ToolbarButton,
16
+ ToolbarField,
17
+ Tooltray
18
+ } from "@heswell/salt-lab";
19
+ import { CloseIcon } from "@salt-ds/icons";
20
+
21
+ import "./Header.css";
22
+
23
+ export interface HeaderProps extends HTMLAttributes<HTMLDivElement> {
24
+ collapsed?: boolean;
25
+ contributions?: Contribution[];
26
+ expanded?: boolean;
27
+ closeable?: boolean;
28
+ onEditTitle: (value: string) => void;
29
+ orientation?: "horizontal" | "vertical";
30
+ tearOut?: boolean;
31
+ }
32
+
33
+ export const Header = ({
34
+ className: classNameProp,
35
+ contributions,
36
+ collapsed,
37
+ closeable,
38
+ onEditTitle,
39
+ orientation: orientationProp = "horizontal",
40
+ style,
41
+ title = "Untitled",
42
+ }: HeaderProps) => {
43
+ const labelFieldRef = useRef<HTMLDivElement>(null);
44
+ const [value, setValue] = useState<string>(title);
45
+ const [editing, setEditing] = useState<boolean>(false);
46
+
47
+ const viewDispatch = useViewDispatch();
48
+ const handleClose = (evt: MouseEvent) =>
49
+ viewDispatch?.({ type: "remove" }, evt);
50
+ const classBase = "vuuHeader";
51
+
52
+ const handleTitleMouseDown = () => {
53
+ labelFieldRef.current?.focus();
54
+ };
55
+
56
+ const handleButtonMouseDown = (evt: MouseEvent) => {
57
+ // do not allow drag to be initiated
58
+ evt.stopPropagation();
59
+ };
60
+
61
+ const orientation = collapsed || orientationProp;
62
+
63
+ const className = classnames(
64
+ classBase,
65
+ classNameProp,
66
+ `${classBase}-${orientation}`
67
+ );
68
+
69
+ const handleEnterEditMode = () => {
70
+ setEditing(true);
71
+ };
72
+
73
+ const handleTitleKeyDown = (evt: KeyboardEvent<HTMLDivElement>) => {
74
+ if (evt.key === "Enter") {
75
+ setEditing(true);
76
+ }
77
+ };
78
+
79
+ const handleExitEditMode = (
80
+ originalValue = "",
81
+ finalValue = "",
82
+ allowDeactivation = true,
83
+ editCancelled = false
84
+ ) => {
85
+ setEditing(false);
86
+ if (editCancelled) {
87
+ setValue(originalValue);
88
+ } else if (finalValue !== originalValue) {
89
+ setValue(finalValue);
90
+ onEditTitle?.(finalValue);
91
+ }
92
+ if (allowDeactivation === false) {
93
+ labelFieldRef.current?.focus();
94
+ }
95
+ };
96
+
97
+ const handleMouseDown = (e: MouseEvent) => {
98
+ viewDispatch?.({ type: "mousedown" }, e);
99
+ };
100
+
101
+ const toolbarItems: ReactElement[] = [];
102
+ const contributedItems: ReactElement[] = [];
103
+ const actionButtons: ReactElement[] = [];
104
+
105
+ title &&
106
+ toolbarItems.push(
107
+ <ToolbarField className="vuuHeader-title" key="title">
108
+ <EditableLabel
109
+ editing={editing}
110
+ key="title"
111
+ value={value}
112
+ onChange={setValue}
113
+ onMouseDownCapture={handleTitleMouseDown}
114
+ onEnterEditMode={handleEnterEditMode}
115
+ onExitEditMode={handleExitEditMode}
116
+ onKeyDown={handleTitleKeyDown}
117
+ ref={labelFieldRef}
118
+ tabIndex={0}
119
+ />
120
+ </ToolbarField>
121
+ );
122
+
123
+ contributions?.forEach((contribution, i) => {
124
+ contributedItems.push(React.cloneElement(contribution.content, { key: i }));
125
+ });
126
+
127
+ closeable &&
128
+ actionButtons.push(
129
+ <ToolbarButton
130
+ key="close"
131
+ onClick={handleClose}
132
+ onMouseDown={handleButtonMouseDown}
133
+ >
134
+ <CloseIcon /> Close
135
+ </ToolbarButton>
136
+ );
137
+
138
+ contributedItems.length > 0 &&
139
+ toolbarItems.push(
140
+ <Tooltray data-align-end key="contributions">
141
+ {contributedItems}
142
+ </Tooltray>
143
+ );
144
+
145
+ actionButtons.length > 0 &&
146
+ toolbarItems.push(
147
+ <Tooltray data-align-end key="actions">
148
+ {actionButtons}
149
+ </Tooltray>
150
+ );
151
+
152
+ return (
153
+ <Toolbar
154
+ className={className}
155
+ orientation={orientationProp}
156
+ style={style}
157
+ onMouseDown={handleMouseDown}
158
+ >
159
+ {toolbarItems}
160
+ {/*
161
+ {collapsed === false ? (
162
+ <ActionButton
163
+ aria-label="Minimize View"
164
+ actionId="minimize"
165
+ iconName="minimize"
166
+ onClick={handleAction}
167
+ onMouseDown={handleButtonMouseDown}
168
+ />
169
+ ) : null}
170
+ {collapsed ? (
171
+ <ActionButton
172
+ aria-label="Restore View"
173
+ actionId="restore"
174
+ iconName="double-chevron-right"
175
+ onClick={handleAction}
176
+ onMouseDown={handleButtonMouseDown}
177
+ />
178
+ ) : null}
179
+ {expanded === false ? (
180
+ <ActionButton
181
+ aria-label="Maximize View"
182
+ actionId="maximize"
183
+ iconName="maximize"
184
+ onClick={handleAction}
185
+ onMouseDown={handleButtonMouseDown}
186
+ />
187
+ ) : null}
188
+ {expanded ? (
189
+ <ActionButton
190
+ aria-label="Restore View"
191
+ actionId="restore"
192
+ iconName="restore"
193
+ onClick={handleAction}
194
+ onMouseDown={handleButtonMouseDown}
195
+ />
196
+ ) : null}
197
+ {tearOut ? (
198
+ <ActionButton
199
+ aria-label="Tear out View"
200
+ actionId="tearout"
201
+ iconName="tear-out"
202
+ onClick={handleAction}
203
+ onMouseDown={handleButtonMouseDown}
204
+ />
205
+ ) : null}
206
+ {closeable ? (
207
+ <Button
208
+ aria-label="close"
209
+ data-icon
210
+ onClick={handleClose}
211
+ onMouseDown={handleButtonMouseDown}
212
+ />
213
+ ) : null} */}
214
+ </Toolbar>
215
+ );
216
+ };
@@ -0,0 +1 @@
1
+ export * from './Header';
@@ -0,0 +1,161 @@
1
+ import {
2
+ MutableRefObject,
3
+ ReactElement,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import {
11
+ LayoutActionType,
12
+ layoutFromJson,
13
+ LayoutJSON,
14
+ layoutReducer,
15
+ LayoutReducerAction,
16
+ layoutToJSON,
17
+ processLayoutElement
18
+ } from "../layout-reducer";
19
+ import { findTarget, getChildProp, getProps, typeOf } from "../utils";
20
+ import {
21
+ LayoutProviderContext,
22
+ LayoutProviderDispatch
23
+ } from "./LayoutProviderContext";
24
+ import { useLayoutDragDrop } from "./useLayoutDragDrop";
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ const withDropTarget = (props: any) => props.dropTarget;
28
+ const shouldSave = (action: LayoutReducerAction) =>
29
+ [
30
+ "drag-drop",
31
+ "remove",
32
+ "set-title",
33
+ "splitter-resize",
34
+ "switch-tab",
35
+ ].includes(action.type);
36
+
37
+ type LayoutChangeHandler = (layout: LayoutJSON, source: string) => void;
38
+
39
+ export interface LayoutProviderProps {
40
+ children: ReactElement;
41
+ layout?: LayoutJSON;
42
+ onLayoutChange?: LayoutChangeHandler;
43
+ }
44
+
45
+ export const LayoutProviderVersion = () => {
46
+ const version = useLayoutProviderVersion();
47
+ return <div>{`Context: ${version} `}</div>;
48
+ };
49
+
50
+ export const LayoutProvider = (props: LayoutProviderProps): ReactElement => {
51
+ const { children, layout, onLayoutChange } = props;
52
+ const state = useRef<ReactElement | undefined>(undefined);
53
+ const childrenRef = useRef<ReactElement>(children);
54
+
55
+ const [, forceRefresh] = useState<unknown>(null);
56
+
57
+ const serializeState = useCallback(
58
+ (source) => {
59
+ if (onLayoutChange) {
60
+ const targetContainer =
61
+ findTarget(source, withDropTarget) || state.current;
62
+ const isDraggableLayout = typeOf(targetContainer) === "DraggableLayout";
63
+ const target = isDraggableLayout
64
+ ? getProps(targetContainer).children[0]
65
+ : targetContainer;
66
+ const serializedModel = layoutToJSON(target);
67
+ onLayoutChange(serializedModel, "drag-root");
68
+ }
69
+ },
70
+ [onLayoutChange]
71
+ );
72
+
73
+ const dispatchLayoutAction = useCallback(
74
+ (action: LayoutReducerAction, suppressSave = false) => {
75
+ const nextState = layoutReducer(state.current as ReactElement, action);
76
+ if (nextState !== state.current) {
77
+ state.current = nextState;
78
+ forceRefresh({});
79
+ if (!suppressSave && shouldSave(action)) {
80
+ serializeState(nextState);
81
+ }
82
+ }
83
+ },
84
+ [serializeState]
85
+ );
86
+
87
+ const layoutActionDispatcher: LayoutProviderDispatch = useCallback(
88
+ (action) => {
89
+ switch(action.type) {
90
+ case "drag-start": {
91
+ prepareToDragLayout(action); break
92
+ }
93
+ case "save": {
94
+ serializeState(state.current); break
95
+ }
96
+ default: {
97
+ dispatchLayoutAction(action); break
98
+ }
99
+ }
100
+ },
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ [dispatchLayoutAction, serializeState]
103
+ );
104
+
105
+ const prepareToDragLayout = useLayoutDragDrop(
106
+ state as MutableRefObject<ReactElement>,
107
+ layoutActionDispatcher
108
+ );
109
+
110
+ useEffect(() => {
111
+ if (layout) {
112
+ const targetContainer = findTarget(
113
+ state.current as never,
114
+ withDropTarget
115
+ ) as ReactElement;
116
+ const target = getChildProp(targetContainer);
117
+ const newLayout = layoutFromJson(
118
+ layout,
119
+ `${targetContainer.props.path}.0`
120
+ );
121
+ const action = target
122
+ ? {
123
+ type: LayoutActionType.REPLACE,
124
+ target,
125
+ replacement: newLayout,
126
+ }
127
+ : {
128
+ type: LayoutActionType.ADD,
129
+ path: targetContainer.props.path,
130
+ component: newLayout,
131
+ };
132
+ dispatchLayoutAction(action, true);
133
+ }
134
+ }, [dispatchLayoutAction, layout]);
135
+
136
+ if (state.current === undefined) {
137
+ state.current = processLayoutElement(children);
138
+ } else if (children !== childrenRef.current) {
139
+ state.current = processLayoutElement(children, state.current);
140
+ childrenRef.current = children;
141
+ }
142
+
143
+ return (
144
+ <LayoutProviderContext.Provider
145
+ value={{ dispatchLayoutProvider: layoutActionDispatcher, version: 0 }}
146
+ >
147
+ {state.current}
148
+ </LayoutProviderContext.Provider>
149
+ );
150
+ };
151
+
152
+ export const useLayoutProviderDispatch = () => {
153
+ const { dispatchLayoutProvider } = useContext(LayoutProviderContext);
154
+ return dispatchLayoutProvider;
155
+ };
156
+
157
+ export const useLayoutProviderVersion = () => {
158
+ console.log({ LayoutProviderContext });
159
+ const { version } = useContext(LayoutProviderContext);
160
+ return version;
161
+ };
@@ -0,0 +1,17 @@
1
+ import { createContext, Dispatch } from 'react';
2
+ import { DragStartAction, LayoutReducerAction, SaveAction } from '../layout-reducer';
3
+
4
+ const unconfiguredLayoutProviderDispatch: LayoutProviderDispatch = (action) =>
5
+ console.log(`dispatch ${action.type}, have you forgotten to provide a LayoutProvider ?`);
6
+
7
+ export type LayoutProviderDispatch = Dispatch<LayoutReducerAction | SaveAction | DragStartAction>;
8
+
9
+ export interface LayoutProviderContextProps {
10
+ dispatchLayoutProvider: LayoutProviderDispatch;
11
+ version: number;
12
+ }
13
+
14
+ export const LayoutProviderContext = createContext<LayoutProviderContextProps>({
15
+ dispatchLayoutProvider: unconfiguredLayoutProviderDispatch,
16
+ version: -1
17
+ });
@@ -0,0 +1,3 @@
1
+ export * from './LayoutProvider';
2
+ export * from './LayoutProviderContext';
3
+
@@ -0,0 +1,210 @@
1
+ import { MutableRefObject, ReactElement, useCallback, useRef } from "react";
2
+ import {
3
+ DragDropRect,
4
+ DragEndCallback,
5
+ Draggable,
6
+ DragInstructions
7
+ } from "../drag-drop";
8
+ import { DragStartAction } from "../layout-reducer";
9
+ import { getIntrinsicSize } from "../layout-reducer/flexUtils";
10
+ import { followPath } from "../utils";
11
+ import { LayoutProviderDispatch } from "./LayoutProviderContext";
12
+
13
+ const NO_INSTRUCTIONS = {} as DragInstructions;
14
+ const NO_OFFSETS: [number, number] = [0, 0];
15
+
16
+ interface CurrentDragAction extends Omit<DragStartAction, "evt" | "type"> {
17
+ dragContainerPath: string;
18
+ }
19
+
20
+ interface DragOperation {
21
+ payload: ReactElement;
22
+ originalCSS: string;
23
+ dragRect: unknown;
24
+ dragInstructions: DragInstructions;
25
+ dragOffsets: [number, number];
26
+ targetPosition: { left: number; top: number };
27
+ }
28
+
29
+ const getDragElement = (
30
+ rect: DragDropRect,
31
+ id: string,
32
+ dragElement?: HTMLElement
33
+ ): [HTMLElement, string, number, number] => {
34
+ const wrapper = document.createElement("div");
35
+ wrapper.className = "vuuSimpleDraggableWrapper";
36
+ wrapper.classList.add(
37
+ "vuuSimpleDraggableWrapper",
38
+ "salt-theme",
39
+ "salt-density-medium"
40
+ );
41
+ wrapper.dataset.dragging = "true";
42
+
43
+ const div = dragElement ?? document.createElement("div");
44
+ div.id = id;
45
+
46
+ wrapper.appendChild(div);
47
+ document.body.appendChild(wrapper);
48
+ const cssText = `top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;`;
49
+ return [wrapper, cssText, rect.left, rect.top];
50
+ };
51
+
52
+ const determineDragOffsets = (
53
+ draggedElement: HTMLElement
54
+ ): [number, number] => {
55
+ const { offsetParent } = draggedElement;
56
+ if (offsetParent === null) {
57
+ return NO_OFFSETS;
58
+ } else {
59
+ const { left: offsetLeft, top: offsetTop } =
60
+ offsetParent.getBoundingClientRect();
61
+ return [offsetLeft, offsetTop];
62
+ }
63
+ };
64
+
65
+ export const useLayoutDragDrop = (
66
+ rootLayoutRef: MutableRefObject<ReactElement>,
67
+ dispatch: LayoutProviderDispatch
68
+ ) => {
69
+ const dragActionRef = useRef<CurrentDragAction>();
70
+ const dragOperationRef = useRef<DragOperation>();
71
+ const draggableHTMLElementRef = useRef<HTMLElement>();
72
+
73
+ const handleDrag = useCallback((x, y) => {
74
+ if (dragOperationRef.current && draggableHTMLElementRef.current) {
75
+ const {
76
+ dragOffsets: [offsetX, offsetY],
77
+ targetPosition,
78
+ } = dragOperationRef.current;
79
+ const left = typeof x === "number" ? x - offsetX : targetPosition.left;
80
+ const top = typeof y === "number" ? y - offsetY : targetPosition.top;
81
+ if (left !== targetPosition.left || top !== targetPosition.top) {
82
+ dragOperationRef.current.targetPosition.left = left;
83
+ dragOperationRef.current.targetPosition.top = top;
84
+ draggableHTMLElementRef.current.style.top = top + "px";
85
+ draggableHTMLElementRef.current.style.left = left + "px";
86
+ }
87
+ }
88
+ }, []);
89
+
90
+ const handleDrop: DragEndCallback = useCallback((dropTarget) => {
91
+ if (dragOperationRef.current) {
92
+ const {
93
+ dragInstructions,
94
+ payload: draggedReactElement,
95
+ originalCSS,
96
+ } = dragOperationRef.current;
97
+ dispatch({
98
+ type: "drag-drop",
99
+ draggedReactElement,
100
+ dragInstructions,
101
+ dropTarget,
102
+ });
103
+
104
+ console.log(`[useLayoutDragDrop]`, {
105
+ dragInstructions,
106
+ });
107
+ if (draggableHTMLElementRef.current) {
108
+ if (dragInstructions.RemoveDraggableOnDragEnd) {
109
+ document.body.removeChild(draggableHTMLElementRef.current);
110
+ } else {
111
+ draggableHTMLElementRef.current.style.cssText = originalCSS;
112
+ delete draggableHTMLElementRef.current.dataset.dragging;
113
+ }
114
+ }
115
+
116
+ dragActionRef.current = undefined;
117
+ dragOperationRef.current = undefined;
118
+ draggableHTMLElementRef.current = undefined;
119
+ }
120
+ }, [dispatch]);
121
+
122
+ const handleDragStart = useCallback(
123
+ (evt: MouseEvent) => {
124
+ if (dragActionRef.current) {
125
+ const {
126
+ payload: component,
127
+ dragContainerPath,
128
+ dragElement,
129
+ dragRect,
130
+ instructions = NO_INSTRUCTIONS,
131
+ path,
132
+ } = dragActionRef.current;
133
+ const { current: rootLayout } = rootLayoutRef;
134
+ const dragPos = { x: evt.clientX, y: evt.clientY };
135
+ const dragPayload = component ?? followPath(rootLayout, path, true);
136
+ const { id: dragPayloadId } = dragPayload.props;
137
+ const intrinsicSize = getIntrinsicSize(dragPayload);
138
+ let originalCSS = "",
139
+ dragCSS = "",
140
+ dragTransform = "";
141
+
142
+ let dragStartLeft = -1;
143
+ let dragStartTop = -1;
144
+ let dragOffsets: [number, number] = NO_OFFSETS;
145
+
146
+ let element = document.getElementById(dragPayloadId);
147
+
148
+ if (element === null) {
149
+ [element, dragCSS, dragStartLeft, dragStartTop] = getDragElement(
150
+ dragRect,
151
+ dragPayloadId,
152
+ dragElement
153
+ );
154
+ } else {
155
+ dragOffsets = determineDragOffsets(element);
156
+ const [offsetLeft, offsetTop] = dragOffsets;
157
+ const { width, height, left, top } = element.getBoundingClientRect();
158
+ dragStartLeft = left - offsetLeft;
159
+ dragStartTop = top - offsetTop;
160
+ dragCSS = `width:${width}px;height:${height}px;left:${dragStartLeft}px;top:${dragStartTop}px;z-index: 100;background-color:#ccc;opacity: 0.6;`;
161
+ element.dataset.dragging = "true";
162
+ originalCSS = element.style.cssText;
163
+ }
164
+
165
+ dragTransform = Draggable.initDrag(
166
+ rootLayoutRef.current,
167
+ dragContainerPath,
168
+ dragRect,
169
+ dragPos,
170
+ {
171
+ drag: handleDrag,
172
+ drop: handleDrop,
173
+ },
174
+ intrinsicSize
175
+ // dropTargets
176
+ );
177
+
178
+ element.style.cssText = dragCSS + dragTransform;
179
+ draggableHTMLElementRef.current = element;
180
+
181
+ dragOperationRef.current = {
182
+ payload: dragPayload,
183
+ originalCSS,
184
+ dragRect,
185
+ dragOffsets,
186
+ dragInstructions: instructions,
187
+ targetPosition: { left: dragStartLeft, top: dragStartTop },
188
+ };
189
+ }
190
+ },
191
+ [handleDrag, handleDrop, rootLayoutRef]
192
+ );
193
+
194
+ const prepareToDrag = useCallback(
195
+ (action: DragStartAction) => {
196
+ const { evt, ...options } = action;
197
+ console.log(`prepare to drag`, {
198
+ options,
199
+ });
200
+ dragActionRef.current = {
201
+ ...options,
202
+ dragContainerPath: "",
203
+ };
204
+ Draggable.handleMousedown(evt, handleDragStart, options.instructions);
205
+ },
206
+ [handleDragStart]
207
+ );
208
+
209
+ return prepareToDrag;
210
+ };