@vuu-ui/vuu-layout 0.0.27

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 (117) hide show
  1. package/README.md +1 -0
  2. package/package.json +30 -0
  3. package/src/Component.css +2 -0
  4. package/src/Component.tsx +20 -0
  5. package/src/DraggableLayout.css +18 -0
  6. package/src/DraggableLayout.tsx +29 -0
  7. package/src/__tests__/flexbox-utils.spec.js +90 -0
  8. package/src/action-buttons/action-buttons.css +12 -0
  9. package/src/action-buttons/action-buttons.tsx +30 -0
  10. package/src/action-buttons/index.ts +1 -0
  11. package/src/chest-of-drawers/Chest.css +36 -0
  12. package/src/chest-of-drawers/Chest.tsx +42 -0
  13. package/src/chest-of-drawers/Drawer.css +153 -0
  14. package/src/chest-of-drawers/Drawer.tsx +118 -0
  15. package/src/chest-of-drawers/index.ts +2 -0
  16. package/src/common-types.ts +9 -0
  17. package/src/debug.ts +16 -0
  18. package/src/dialog/Dialog.css +16 -0
  19. package/src/dialog/Dialog.tsx +59 -0
  20. package/src/dialog/index.ts +1 -0
  21. package/src/drag-drop/BoxModel.ts +546 -0
  22. package/src/drag-drop/DragState.ts +222 -0
  23. package/src/drag-drop/Draggable.ts +282 -0
  24. package/src/drag-drop/DropMenu.css +70 -0
  25. package/src/drag-drop/DropMenu.tsx +68 -0
  26. package/src/drag-drop/DropTarget.ts +392 -0
  27. package/src/drag-drop/DropTargetRenderer.css +40 -0
  28. package/src/drag-drop/DropTargetRenderer.tsx +284 -0
  29. package/src/drag-drop/dragDropTypes.ts +49 -0
  30. package/src/drag-drop/index.ts +4 -0
  31. package/src/editable-label/EditableLabel.css +28 -0
  32. package/src/editable-label/EditableLabel.tsx +99 -0
  33. package/src/editable-label/index.ts +1 -0
  34. package/src/flexbox/Flexbox.css +45 -0
  35. package/src/flexbox/Flexbox.tsx +70 -0
  36. package/src/flexbox/FlexboxLayout.jsx +26 -0
  37. package/src/flexbox/FluidGrid.css +134 -0
  38. package/src/flexbox/FluidGrid.tsx +84 -0
  39. package/src/flexbox/FluidGridLayout.tsx +10 -0
  40. package/src/flexbox/Splitter.css +140 -0
  41. package/src/flexbox/Splitter.tsx +135 -0
  42. package/src/flexbox/flexbox-utils.ts +128 -0
  43. package/src/flexbox/flexboxTypes.ts +63 -0
  44. package/src/flexbox/index.ts +4 -0
  45. package/src/flexbox/useResponsiveSizing.ts +85 -0
  46. package/src/flexbox/useSplitterResizing.ts +272 -0
  47. package/src/index.ts +20 -0
  48. package/src/layout-action.ts +21 -0
  49. package/src/layout-header/ActionButton.tsx +23 -0
  50. package/src/layout-header/Header.css +8 -0
  51. package/src/layout-header/Header.tsx +222 -0
  52. package/src/layout-header/index.ts +1 -0
  53. package/src/layout-provider/LayoutProvider.tsx +160 -0
  54. package/src/layout-provider/LayoutProviderContext.ts +17 -0
  55. package/src/layout-provider/index.ts +2 -0
  56. package/src/layout-provider/useLayoutDragDrop.ts +241 -0
  57. package/src/layout-reducer/flexUtils.ts +281 -0
  58. package/src/layout-reducer/index.ts +4 -0
  59. package/src/layout-reducer/insert-layout-element.ts +365 -0
  60. package/src/layout-reducer/layout-reducer.ts +255 -0
  61. package/src/layout-reducer/layoutTypes.ts +151 -0
  62. package/src/layout-reducer/layoutUtils.ts +302 -0
  63. package/src/layout-reducer/remove-layout-element.ts +240 -0
  64. package/src/layout-reducer/replace-layout-element.ts +118 -0
  65. package/src/layout-reducer/resize-flex-children.ts +56 -0
  66. package/src/layout-reducer/wrap-layout-element.ts +317 -0
  67. package/src/layout-view/View.css +58 -0
  68. package/src/layout-view/View.tsx +149 -0
  69. package/src/layout-view/ViewContext.ts +31 -0
  70. package/src/layout-view/index.ts +4 -0
  71. package/src/layout-view/useView.tsx +104 -0
  72. package/src/layout-view/useViewActionDispatcher.ts +133 -0
  73. package/src/layout-view/useViewResize.ts +53 -0
  74. package/src/layout-view/viewTypes.ts +37 -0
  75. package/src/palette/Palette.css +37 -0
  76. package/src/palette/Palette.tsx +140 -0
  77. package/src/palette/PaletteUitk.css +9 -0
  78. package/src/palette/PaletteUitk.tsx +79 -0
  79. package/src/palette/index.ts +2 -0
  80. package/src/placeholder/Placeholder.css +10 -0
  81. package/src/placeholder/Placeholder.tsx +39 -0
  82. package/src/placeholder/index.ts +1 -0
  83. package/src/registry/ComponentRegistry.ts +35 -0
  84. package/src/registry/index.ts +1 -0
  85. package/src/responsive/OverflowMenu.css +31 -0
  86. package/src/responsive/OverflowMenu.jsx +56 -0
  87. package/src/responsive/breakpoints.ts +48 -0
  88. package/src/responsive/index.ts +4 -0
  89. package/src/responsive/measureMinimumNodeSize.ts +23 -0
  90. package/src/responsive/overflowUtils.js +14 -0
  91. package/src/responsive/use-breakpoints.ts +100 -0
  92. package/src/responsive/useOverflowObserver.ts +606 -0
  93. package/src/responsive/useResizeObserver.ts +154 -0
  94. package/src/responsive/utils.ts +37 -0
  95. package/src/stack/Stack.css +39 -0
  96. package/src/stack/Stack.tsx +160 -0
  97. package/src/stack/StackLayout.tsx +137 -0
  98. package/src/stack/index.ts +3 -0
  99. package/src/stack/stackTypes.ts +19 -0
  100. package/src/tabs/TabPanel.css +12 -0
  101. package/src/tabs/TabPanel.tsx +17 -0
  102. package/src/tabs/index.ts +1 -0
  103. package/src/tools/config-wrapper/ConfigWrapper.jsx +53 -0
  104. package/src/tools/config-wrapper/index.js +1 -0
  105. package/src/tools/devtools-box/layout-configurator.css +112 -0
  106. package/src/tools/devtools-box/layout-configurator.jsx +369 -0
  107. package/src/tools/devtools-tree/layout-tree-viewer.css +15 -0
  108. package/src/tools/devtools-tree/layout-tree-viewer.jsx +36 -0
  109. package/src/tools/index.js +3 -0
  110. package/src/use-persistent-state.ts +115 -0
  111. package/src/utils/componentFromLayout.tsx +30 -0
  112. package/src/utils/index.ts +6 -0
  113. package/src/utils/pathUtils.ts +294 -0
  114. package/src/utils/propUtils.ts +24 -0
  115. package/src/utils/refUtils.ts +16 -0
  116. package/src/utils/styleUtils.ts +14 -0
  117. package/src/utils/typeOf.ts +22 -0
@@ -0,0 +1,222 @@
1
+ import { rect } from '../common-types';
2
+ import { Measurements, pointPositionWithinRect } from './BoxModel';
3
+ import { DragDropRect } from './dragDropTypes';
4
+
5
+ var SCALE_FACTOR = 0.4;
6
+
7
+ export type IntrinsicSizes = {
8
+ height?: number;
9
+ width?: number;
10
+ };
11
+
12
+ interface ZoneRange {
13
+ hi: number;
14
+ lo: number;
15
+ }
16
+ type DragConstraint = {
17
+ zone: {
18
+ x: ZoneRange;
19
+ y: ZoneRange;
20
+ };
21
+ pos: {
22
+ x: ZoneRange;
23
+ y: ZoneRange;
24
+ };
25
+ mouse: {
26
+ x: ZoneRange;
27
+ y: ZoneRange;
28
+ };
29
+ };
30
+
31
+ interface ExtendedZoneRange {
32
+ lo: boolean;
33
+ hi: boolean;
34
+ mousePct: number;
35
+ mousePos: number;
36
+ pos: number;
37
+ }
38
+
39
+ export class DragState {
40
+ public constraint!: DragConstraint;
41
+ public x!: ExtendedZoneRange;
42
+ public y!: ExtendedZoneRange;
43
+ public intrinsicSize: IntrinsicSizes | undefined;
44
+
45
+ constructor(
46
+ zone: DragDropRect,
47
+ mouseX: number,
48
+ mouseY: number,
49
+ measurements: DragDropRect,
50
+ intrinsicSize?: IntrinsicSizes
51
+ ) {
52
+ this.init(zone, mouseX, mouseY, measurements, intrinsicSize);
53
+ }
54
+
55
+ init(
56
+ zone: DragDropRect,
57
+ mouseX: number,
58
+ mouseY: number,
59
+ rect: DragDropRect,
60
+ intrinsicSize?: IntrinsicSizes
61
+ ) {
62
+ var { left: x, top: y } = rect;
63
+
64
+ var { pctX, pctY } = pointPositionWithinRect(mouseX, mouseY, rect);
65
+
66
+ // We are applying a scale factor of 0.4 to the draggee. This is purely a visual
67
+ // effect - the actual box size remains the original size. The 'leading' values
68
+ // represent the difference between the visual scaled down box and the actual box.
69
+
70
+ var scaleFactor = SCALE_FACTOR;
71
+
72
+ var leadX = pctX * rect.width;
73
+ var trailX = rect.width - leadX;
74
+ var leadY = pctY * rect.height;
75
+ var trailY = rect.height - leadY;
76
+
77
+ // When we assign position to rect using css. positioning units are applied to the
78
+ // unscaled shape, so we have to adjust values to take scaling into account.
79
+ var scaledWidth = rect.width * scaleFactor,
80
+ scaledHeight = rect.height * scaleFactor;
81
+
82
+ var scaleDiff = 1 - scaleFactor;
83
+ var leadXScaleDiff = leadX * scaleDiff;
84
+ var leadYScaleDiff = leadY * scaleDiff;
85
+ var trailXScaleDiff = trailX * scaleDiff;
86
+ var trailYScaleDiff = trailY * scaleDiff;
87
+
88
+ this.intrinsicSize = intrinsicSize;
89
+
90
+ this.constraint = {
91
+ zone: {
92
+ x: {
93
+ lo: zone.left,
94
+ hi: zone.right
95
+ },
96
+ y: {
97
+ lo: zone.top,
98
+ hi: zone.bottom
99
+ }
100
+ },
101
+
102
+ pos: {
103
+ x: {
104
+ lo: /* left */ zone.left - leadXScaleDiff,
105
+ hi: /* right */ zone.right - rect.width + trailXScaleDiff
106
+ },
107
+ y: {
108
+ lo: /* top */ zone.top - leadYScaleDiff,
109
+ hi: /* bottom */ zone.bottom - rect.height + trailYScaleDiff
110
+ }
111
+ },
112
+ mouse: {
113
+ x: {
114
+ lo: /* left */ zone.left + scaledWidth * pctX,
115
+ hi: /* right */ zone.right - scaledWidth * (1 - pctX)
116
+ },
117
+ y: {
118
+ lo: /* top */ zone.top + scaledHeight * pctY,
119
+ hi: /* bottom */ zone.bottom - scaledHeight * (1 - pctY)
120
+ }
121
+ }
122
+ };
123
+
124
+ // onsole.log(`DragState: constraint ${JSON.stringify(this.constraint, null, 2)}`);
125
+
126
+ this.x = {
127
+ pos: x,
128
+ lo: false,
129
+ hi: false,
130
+ mousePos: mouseX,
131
+ mousePct: pctX
132
+ };
133
+ this.y = {
134
+ pos: y,
135
+ lo: false,
136
+ hi: false,
137
+ mousePos: mouseY,
138
+ mousePct: pctY
139
+ };
140
+ }
141
+
142
+ outOfBounds() {
143
+ return this.x.lo || this.x.hi || this.y.lo || this.y.hi;
144
+ }
145
+
146
+ inBounds() {
147
+ return !this.outOfBounds();
148
+ }
149
+
150
+ dropX() {
151
+ return this.dropXY('x');
152
+ }
153
+
154
+ dropY() {
155
+ return this.dropXY('y');
156
+ }
157
+
158
+ hasIntrinsicSize(): number | undefined {
159
+ return this?.intrinsicSize?.height && this?.intrinsicSize?.width;
160
+ }
161
+
162
+ /*
163
+ * diff = mouse movement, signed int
164
+ * xy = 'x' or 'y'
165
+ */
166
+ //todo, diff can be calculated in here
167
+ update(xy: 'x' | 'y', mousePos: number) {
168
+ var state = this[xy],
169
+ mouseConstraint = this.constraint.mouse[xy],
170
+ posConstraint = this.constraint.pos[xy],
171
+ previousPos = state.pos;
172
+
173
+ var diff = mousePos - state.mousePos;
174
+
175
+ //xy==='x' && console.log(`update: state.lo=${state.lo}, mPos=${mousePos}, mC.lo=${mouseConstraint.lo}, prevPos=${previousPos}, diff=${diff} ` );
176
+
177
+ if (diff < 0) {
178
+ if (state.lo) {
179
+ /* do nothing */
180
+ } else if (mousePos < mouseConstraint.lo) {
181
+ state.lo = true;
182
+ state.pos = posConstraint.lo;
183
+ } else if (state.hi) {
184
+ if (mousePos < mouseConstraint.hi) {
185
+ state.hi = false;
186
+ state.pos += diff;
187
+ }
188
+ } else {
189
+ state.pos += diff;
190
+ }
191
+ } else if (diff > 0) {
192
+ if (state.hi) {
193
+ /* do nothing */
194
+ } else if (mousePos > mouseConstraint.hi) {
195
+ state.hi = true;
196
+ state.pos = posConstraint.hi;
197
+ } else if (state.lo) {
198
+ if (mousePos > mouseConstraint.lo) {
199
+ state.lo = false;
200
+ state.pos += diff;
201
+ }
202
+ } else {
203
+ state.pos += diff;
204
+ }
205
+ }
206
+
207
+ state.mousePos = mousePos;
208
+
209
+ return previousPos !== state.pos;
210
+ }
211
+
212
+ private dropXY(this: DragState, dir: 'x' | 'y') {
213
+ var pos = this[dir],
214
+ rect = this.constraint.zone[dir];
215
+ // why not do the rounding +/- 1 on the rect initially - this is all it is usef for
216
+ return pos.lo
217
+ ? Math.max(rect.lo, pos.mousePos)
218
+ : pos.hi
219
+ ? Math.min(pos.mousePos, Math.round(rect.hi) - 1)
220
+ : pos.mousePos;
221
+ }
222
+ }
@@ -0,0 +1,282 @@
1
+ import { ReactElement } from "react";
2
+ import { rect } from "../common-types";
3
+ import { LayoutModel } from "../layout-reducer";
4
+ import { findTarget, followPath, getProps } from "../utils";
5
+ import { BoxModel, Measurements, Position } from "./BoxModel";
6
+ import { DragDropRect } from "./dragDropTypes";
7
+ import { DragState, IntrinsicSizes } from "./DragState";
8
+ import { DropTarget, identifyDropTarget } from "./DropTarget";
9
+ import DropTargetRenderer from "./DropTargetRenderer";
10
+
11
+ export type DragStartCallback = (e: MouseEvent, x: number, y: number) => void;
12
+ export type DragMoveCallback = (
13
+ x: number | undefined,
14
+ y: number | undefined
15
+ ) => void;
16
+ export type DragEndCallback = (droppedTarget: Partial<DropTarget>) => void;
17
+ export type DragInstructions = {
18
+ DoNotRemove?: boolean;
19
+ DoNotTransform?: boolean;
20
+ dragThreshold?: number;
21
+ RemoveDraggableOnDragEnd?: boolean;
22
+ };
23
+
24
+ let _dragStartCallback: DragStartCallback | null;
25
+ let _dragMoveCallback: DragMoveCallback | null;
26
+ let _dragEndCallback: DragEndCallback | null;
27
+
28
+ let _dragStartX: number;
29
+ let _dragStartY: number;
30
+ let _dragContainer: ReactElement | null;
31
+ let _dragState: DragState;
32
+ let _dropTarget: DropTarget | null = null;
33
+ let _validDropTargetPaths: string[] | null;
34
+ let _dragInstructions: DragInstructions;
35
+ let _measurements: Measurements;
36
+ let _simpleDrag: boolean;
37
+ let _dragThreshold: number;
38
+ let _mouseDownTimer: number | null = null;
39
+
40
+ const DEFAULT_DRAG_THRESHOLD = 3;
41
+ const _dropTargetRenderer = new DropTargetRenderer();
42
+ const SCALE_FACTOR = 0.4;
43
+
44
+ function getDragContainer(
45
+ rootContainer: ReactElement,
46
+ dragContainerPath: string
47
+ ) {
48
+ if (dragContainerPath) {
49
+ return followPath(rootContainer, dragContainerPath) as ReactElement;
50
+ } else {
51
+ return findTarget(
52
+ rootContainer,
53
+ (props) => props.dropTarget
54
+ ) as ReactElement;
55
+ }
56
+ }
57
+
58
+ export const Draggable = {
59
+ handleMousedown(
60
+ e: MouseEvent,
61
+ dragStartCallback: DragStartCallback,
62
+ dragInstructions: DragInstructions = {}
63
+ ) {
64
+ _dragStartCallback = dragStartCallback;
65
+ _dragInstructions = dragInstructions;
66
+
67
+ _dragStartX = e.clientX;
68
+ _dragStartY = e.clientY;
69
+
70
+ _dragThreshold =
71
+ dragInstructions.dragThreshold === undefined
72
+ ? DEFAULT_DRAG_THRESHOLD
73
+ : dragInstructions.dragThreshold;
74
+
75
+ if (_dragThreshold === 0) {
76
+ // maybe this should be -1
77
+ _dragStartCallback(e, 0, 0);
78
+ } else {
79
+ window.addEventListener("mousemove", preDragMousemoveHandler, false);
80
+ window.addEventListener("mouseup", preDragMouseupHandler, false);
81
+
82
+ _mouseDownTimer = window.setTimeout(() => {
83
+ console.log("mousedownTimer fires");
84
+ window.removeEventListener("mousemove", preDragMousemoveHandler, false);
85
+ window.removeEventListener("mouseup", preDragMouseupHandler, false);
86
+ _dragStartCallback?.(e, 0, 0);
87
+ }, 500);
88
+ }
89
+
90
+ e.preventDefault();
91
+ },
92
+
93
+ // called from handleDragStart (_dragCallback)
94
+ initDrag(
95
+ rootContainer: ReactElement,
96
+ dragContainerPath: string,
97
+ { top, left, right, bottom }: rect,
98
+ dragPos: { x: number; y: number },
99
+ dragHandler: {
100
+ drag: DragMoveCallback;
101
+ drop: DragEndCallback;
102
+ },
103
+ intrinsicSize?: IntrinsicSizes,
104
+ dropTargets?: string[]
105
+ ) {
106
+ ({ drag: _dragMoveCallback, drop: _dragEndCallback } = dragHandler);
107
+ return initDrag(
108
+ rootContainer,
109
+ dragContainerPath,
110
+ { top, left, right, bottom } as DragDropRect,
111
+ dragPos,
112
+ intrinsicSize,
113
+ dropTargets
114
+ );
115
+ },
116
+ };
117
+
118
+ function preDragMousemoveHandler(e: MouseEvent) {
119
+ var x = true;
120
+ var y = true;
121
+
122
+ let x_diff = x ? e.clientX - _dragStartX : 0;
123
+ let y_diff = y ? e.clientY - _dragStartY : 0;
124
+ let mouseMoveDistance = Math.max(Math.abs(x_diff), Math.abs(y_diff));
125
+
126
+ // when we do finally move the draggee, we are going to 'jump' by the amount of the drag threshold, should we
127
+ // attempt to animate this ?
128
+ if (mouseMoveDistance > _dragThreshold) {
129
+ window.removeEventListener("mousemove", preDragMousemoveHandler, false);
130
+ window.removeEventListener("mouseup", preDragMouseupHandler, false);
131
+ _dragStartCallback?.(e, x_diff, y_diff);
132
+ _dragStartCallback = null;
133
+ }
134
+ }
135
+
136
+ function preDragMouseupHandler() {
137
+ if (_mouseDownTimer) {
138
+ window.clearTimeout(_mouseDownTimer);
139
+ _mouseDownTimer = null;
140
+ }
141
+ window.removeEventListener("mousemove", preDragMousemoveHandler, false);
142
+ window.removeEventListener("mouseup", preDragMouseupHandler, false);
143
+ }
144
+
145
+ function initDrag(
146
+ rootContainer: ReactElement,
147
+ dragContainerPath: string,
148
+ dragRect: DragDropRect,
149
+ dragPos: { x: number; y: number },
150
+ intrinsicSize?: IntrinsicSizes,
151
+ dropTargets?: string[]
152
+ ) {
153
+ _dragContainer = getDragContainer(rootContainer, dragContainerPath);
154
+ const { "data-path": dataPath, path = dataPath } = getProps(_dragContainer);
155
+
156
+ if (dropTargets) {
157
+ const dropPaths = dropTargets
158
+ .map((id) => findTarget(rootContainer, (props) => props.id === id))
159
+ .map((target) => (target as LayoutModel).props.path);
160
+ _validDropTargetPaths = dropPaths;
161
+ }
162
+
163
+ // var start = window.performance.now();
164
+ // translate the layout $position to drag-oriented co-ordinates, ignoring splitters
165
+ _measurements = BoxModel.measure(_dragContainer, dropTargets);
166
+ console.log({ _measurements });
167
+ // onsole.log({ measurements: _measurements });
168
+ // var end = window.performance.now();
169
+ // onsole.log(`[Draggable] measurements took ${end - start}ms`, _measurements);
170
+
171
+ const dragZone = _measurements[path];
172
+
173
+ _dragState = new DragState(
174
+ dragZone,
175
+ dragPos.x,
176
+ dragPos.y,
177
+ dragRect,
178
+ intrinsicSize
179
+ );
180
+
181
+ const pctX = Math.round(_dragState.x.mousePct * 100);
182
+ const pctY = Math.round(_dragState.y.mousePct * 100);
183
+
184
+ window.addEventListener("mousemove", dragMousemoveHandler, false);
185
+ window.addEventListener("mouseup", dragMouseupHandler, false);
186
+
187
+ _simpleDrag = false;
188
+
189
+ _dropTargetRenderer.prepare(dragRect, "tab-only");
190
+
191
+ return _dragInstructions.DoNotTransform
192
+ ? "transform:none"
193
+ : // scale factor should be applied in caller, not here
194
+ `transform:scale(${SCALE_FACTOR},${SCALE_FACTOR});transform-origin:${pctX}% ${pctY}%;`;
195
+ }
196
+
197
+ function dragMousemoveHandler(evt: MouseEvent) {
198
+ const x = evt.clientX;
199
+ const y = evt.clientY;
200
+ const dragState = _dragState;
201
+ const currentDropTarget = _dropTarget;
202
+ let dropTarget;
203
+ let newX, newY;
204
+
205
+ if (dragState.update("x", x)) {
206
+ newX = dragState.x.pos;
207
+ }
208
+
209
+ if (dragState.update("y", y)) {
210
+ newY = dragState.y.pos;
211
+ }
212
+
213
+ if (newX === undefined && newY === undefined) {
214
+ //onsole.log('both x and y are unchanged');
215
+ } else {
216
+ _dragMoveCallback?.(newX, newY);
217
+ }
218
+
219
+ if (_simpleDrag) {
220
+ return;
221
+ }
222
+
223
+ if (dragState.inBounds()) {
224
+ dropTarget = identifyDropTarget(
225
+ x,
226
+ y,
227
+ _dragContainer!,
228
+ _measurements,
229
+ dragState.hasIntrinsicSize(),
230
+ _validDropTargetPaths!
231
+ );
232
+ } else {
233
+ dropTarget = identifyDropTarget(
234
+ dragState.dropX(),
235
+ dragState.dropY(),
236
+ _dragContainer!,
237
+ _measurements
238
+ );
239
+ }
240
+
241
+ // did we have an existing droptarget which is no longer such ...
242
+ if (currentDropTarget) {
243
+ if (dropTarget == null || dropTarget.box !== currentDropTarget.box) {
244
+ _dropTarget = null;
245
+ }
246
+ }
247
+
248
+ if (dropTarget) {
249
+ _dropTargetRenderer.draw(dropTarget, dragState);
250
+ _dropTarget = dropTarget;
251
+ }
252
+ }
253
+
254
+ function dragMouseupHandler() {
255
+ onDragEnd();
256
+ }
257
+
258
+ function onDragEnd() {
259
+ if (_dropTarget) {
260
+ const dropTarget =
261
+ _dropTargetRenderer.hoverDropTarget ||
262
+ DropTarget.getActiveDropTarget(_dropTarget);
263
+
264
+ _dragEndCallback?.(dropTarget as DropTarget);
265
+
266
+ _dropTarget = null;
267
+ } else {
268
+ _dragEndCallback?.({
269
+ component: _dragContainer!,
270
+ pos: { position: Position.Absolute } as any,
271
+ });
272
+ }
273
+
274
+ _dragMoveCallback = null;
275
+ _dragEndCallback = null;
276
+
277
+ _dragContainer = null;
278
+ _dropTargetRenderer.clear();
279
+ _validDropTargetPaths = null;
280
+ window.removeEventListener("mousemove", dragMousemoveHandler, false);
281
+ window.removeEventListener("mouseup", dragMouseupHandler, false);
282
+ }
@@ -0,0 +1,70 @@
1
+ .hwDropMenu {
2
+ margin-left: -50%;
3
+ margin-bottom: -50%;
4
+ background-color: white;
5
+ border: solid 1px var(--grey40);
6
+ display: inline-flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ padding: 3px;
10
+ border-radius: 3px;
11
+ }
12
+
13
+ .hwDropMenu.left,
14
+ .hwDropMenu.right {
15
+ flex-direction: column;
16
+ }
17
+
18
+ .hwDropMenu.bottom {
19
+ transform: translate(0, -30px);
20
+ }
21
+
22
+ .hwDropMenu.right {
23
+ transform: translate(-20px, 0);
24
+ }
25
+
26
+ .hwDropMenu-item {
27
+ width: 32px;
28
+ height: 32px;
29
+ background-color: var(--grey20);
30
+ border-bottom: solid 1px var(--grey40);
31
+ cursor: pointer;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ }
36
+
37
+ .hwDropMenu-item .Icon {
38
+ transform: scale(1.25);
39
+ transform-origin: center center;
40
+ }
41
+
42
+ .hwDropMenu.left .hwDropMenu-item .hwIcon {
43
+ transform: scale(1.25) rotate(180deg);
44
+ transform-origin: center center;
45
+ }
46
+
47
+ .hwDropMenu.top .hwDropMenu-item .hwIcon {
48
+ transform: scale(1.25) rotate(270deg);
49
+ transform-origin: center center;
50
+ }
51
+
52
+ .hwDropMenu.bottom .hwDropMenu-item .hwIcon {
53
+ transform: scale(1.25) rotate(90deg);
54
+ transform-origin: center center;
55
+ }
56
+
57
+ .hwDropMenu-item .hwIcon-path {
58
+ fill: grey;
59
+ }
60
+ .hwDropMenu-item:hover {
61
+ background-color: rgba(200, 200, 200, 0.5);
62
+ }
63
+
64
+ .hwDropMenu-item:hover .hwIcon-path-2 {
65
+ fill: blue;
66
+ }
67
+
68
+ .hwDropMenu-item:last-child {
69
+ border-bottom: none;
70
+ }
@@ -0,0 +1,68 @@
1
+ import React, { HTMLAttributes } from "react";
2
+ import cx from "classnames";
3
+ import { Column2AIcon, Column2BIcon } from "@vuu-ui/ui-controls";
4
+ import "./DropMenu.css";
5
+ import { DropTarget } from "./DropTarget";
6
+
7
+ export function computeMenuPosition(
8
+ dropTarget: DropTarget,
9
+ offsetTop = 0,
10
+ offsetLeft = 0
11
+ ): [number, number, "left" | "bottom" | "right" | "top"] {
12
+ const { pos, clientRect: box } = dropTarget;
13
+ const menuOffset = 20;
14
+
15
+ return pos.position.West
16
+ ? [box.left - offsetLeft + menuOffset, pos.y - offsetTop, "left"]
17
+ : pos.position.South
18
+ ? [pos.x - offsetLeft, box.bottom - offsetTop - menuOffset, "bottom"]
19
+ : pos.position.East
20
+ ? [box.right - offsetLeft - menuOffset, pos.y - offsetTop, "right"]
21
+ : /* North | Header*/ [
22
+ pos.x - offsetLeft,
23
+ box.top - offsetTop + menuOffset,
24
+ "top",
25
+ ];
26
+ }
27
+
28
+ const getIcon = (i: number) => {
29
+ if (i === 0) {
30
+ return <Column2AIcon />;
31
+ } else {
32
+ return <Column2BIcon />;
33
+ }
34
+ };
35
+
36
+ export interface DropMenuProps extends HTMLAttributes<HTMLDivElement> {
37
+ dropTarget: DropTarget;
38
+ onHover: (target: DropTarget | null) => void;
39
+ orientation?: "left" | "top" | "right" | "bottom";
40
+ }
41
+
42
+ export const DropMenu = ({
43
+ className,
44
+ dropTarget,
45
+ onHover,
46
+ orientation,
47
+ }: DropMenuProps) => {
48
+ const dropTargets = dropTarget.toArray();
49
+ // TODO we have all the information here to draw a mini target map
50
+ // but maybe thats overkill ...
51
+
52
+ return (
53
+ <div
54
+ className={cx("hwDropMenu", className, orientation)}
55
+ onMouseLeave={() => onHover(null)}
56
+ >
57
+ {dropTargets.map((target, i) => (
58
+ <div
59
+ key={i}
60
+ className="hwDropMenu-item"
61
+ onMouseEnter={() => onHover(target)}
62
+ >
63
+ {getIcon(i)}
64
+ </div>
65
+ ))}
66
+ </div>
67
+ );
68
+ };