@vuu-ui/grid-layout 4.0.0-alpha.1

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 (142) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +141 -0
  3. package/package.json +40 -0
  4. package/src/GridCommand.js +395 -0
  5. package/src/GridComponentSettings.js +163 -0
  6. package/src/GridController.js +359 -0
  7. package/src/GridDragCoordinator.js +293 -0
  8. package/src/GridGeometry.js +1298 -0
  9. package/src/GridLayout.css.js +119 -0
  10. package/src/GridLayout.js +120 -0
  11. package/src/GridLayoutContext.js +58 -0
  12. package/src/GridLayoutDocument.js +307 -0
  13. package/src/GridLayoutDocumentIds.js +110 -0
  14. package/src/GridLayoutItem.css.js +46 -0
  15. package/src/GridLayoutItem.js +147 -0
  16. package/src/GridLayoutLegacyCompatibility.js +66 -0
  17. package/src/GridLayoutModel.js +138 -0
  18. package/src/GridLayoutProvider.js +263 -0
  19. package/src/GridLayoutStackedItem.css.js +26 -0
  20. package/src/GridLayoutStackedtem.js +140 -0
  21. package/src/GridModel.js +1289 -0
  22. package/src/GridPlaceholder.css.js +9 -0
  23. package/src/GridPlaceholder.js +24 -0
  24. package/src/GridSnapshot.js +11 -0
  25. package/src/GridSplitter.css.js +62 -0
  26. package/src/GridSplitter.js +26 -0
  27. package/src/GridStack.js +277 -0
  28. package/src/Icon.css.js +8 -0
  29. package/src/Icon.js +26 -0
  30. package/src/IconButton.css.js +9 -0
  31. package/src/IconButton.js +28 -0
  32. package/src/TabDialog.css.js +10 -0
  33. package/src/TabDialog.js +77 -0
  34. package/src/TabMenu.css.js +8 -0
  35. package/src/TabMenu.js +78 -0
  36. package/src/componentToJson.js +41 -0
  37. package/src/drag-drop-next/DragContextNext.js +173 -0
  38. package/src/drag-drop-next/DragDropProviderNext.css.js +72 -0
  39. package/src/drag-drop-next/DragDropProviderNext.js +91 -0
  40. package/src/drag-drop-next/SpaceMan.js +256 -0
  41. package/src/drag-drop-next/TemplateDragSession.js +36 -0
  42. package/src/drag-drop-next/tabstrip-drag-drop.js +251 -0
  43. package/src/grid-dom-utils.js +57 -0
  44. package/src/grid-layout-utils.js +39 -0
  45. package/src/grid-snapshot-adapters.js +435 -0
  46. package/src/index.js +24 -0
  47. package/src/json-value.js +56 -0
  48. package/src/layoutFromJson.js +15 -0
  49. package/src/layoutToJson.js +5 -0
  50. package/src/propUtils.js +15 -0
  51. package/src/react-element-utils.js +15 -0
  52. package/src/typeOf.js +15 -0
  53. package/src/useAsDropTarget.js +229 -0
  54. package/src/useDraggable.js +46 -0
  55. package/src/useEditTabName.js +70 -0
  56. package/src/useGridChildProps.js +47 -0
  57. package/src/useGridControllerSnapshot.js +21 -0
  58. package/src/useGridLayout.js +727 -0
  59. package/src/useGridSplitterResizing.js +262 -0
  60. package/src/useNotDropTarget.js +2 -0
  61. package/types/AddTabDialog.d.ts +5 -0
  62. package/types/GridCommand.d.ts +146 -0
  63. package/types/GridComponentSettings.d.ts +71 -0
  64. package/types/GridController.d.ts +50 -0
  65. package/types/GridDragCoordinator.d.ts +80 -0
  66. package/types/GridGeometry.d.ts +331 -0
  67. package/types/GridLayout.d.ts +20 -0
  68. package/types/GridLayoutContext.d.ts +141 -0
  69. package/types/GridLayoutDocument.d.ts +61 -0
  70. package/types/GridLayoutDocumentIds.d.ts +15 -0
  71. package/types/GridLayoutItem.d.ts +28 -0
  72. package/types/GridLayoutLegacyCompatibility.d.ts +57 -0
  73. package/types/GridLayoutModel.d.ts +72 -0
  74. package/types/GridLayoutProvider.d.ts +59 -0
  75. package/types/GridLayoutStackedtem.d.ts +10 -0
  76. package/types/GridModel.d.ts +504 -0
  77. package/types/GridPlaceholder.d.ts +5 -0
  78. package/types/GridSnapshot.d.ts +49 -0
  79. package/types/GridSplitter.d.ts +10 -0
  80. package/types/GridStack.d.ts +160 -0
  81. package/types/Icon.d.ts +6 -0
  82. package/types/IconButton.d.ts +6 -0
  83. package/types/TabDialog.d.ts +9 -0
  84. package/types/TabMenu.d.ts +7 -0
  85. package/types/componentToJson.d.ts +38 -0
  86. package/types/drag-drop-next/DragContextNext.d.ts +92 -0
  87. package/types/drag-drop-next/DragDropProviderNext.d.ts +23 -0
  88. package/types/drag-drop-next/SpaceMan.d.ts +35 -0
  89. package/types/drag-drop-next/TemplateDragSession.d.ts +21 -0
  90. package/types/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  91. package/types/grid-dom-utils.d.ts +17 -0
  92. package/types/grid-layout-utils.d.ts +18 -0
  93. package/types/grid-matrix.d.ts +5 -0
  94. package/types/grid-snapshot-adapters.d.ts +22 -0
  95. package/types/index.d.ts +24 -0
  96. package/types/json-value.d.ts +23 -0
  97. package/types/layoutFromJson.d.ts +4 -0
  98. package/types/layoutToJson.d.ts +3 -0
  99. package/types/propUtils.d.ts +5 -0
  100. package/types/react-element-utils.d.ts +3 -0
  101. package/types/src/AddTabDialog.d.ts +5 -0
  102. package/types/src/GridLayout.d.ts +14 -0
  103. package/types/src/GridLayoutContext.d.ts +88 -0
  104. package/types/src/GridLayoutItem.d.ts +28 -0
  105. package/types/src/GridLayoutModel.d.ts +59 -0
  106. package/types/src/GridLayoutProvider.d.ts +37 -0
  107. package/types/src/GridLayoutStackedtem.d.ts +10 -0
  108. package/types/src/GridModel.d.ts +350 -0
  109. package/types/src/GridPlaceholder.d.ts +5 -0
  110. package/types/src/GridSplitter.d.ts +9 -0
  111. package/types/src/Icon.d.ts +6 -0
  112. package/types/src/IconButton.d.ts +6 -0
  113. package/types/src/TabMenu.d.ts +6 -0
  114. package/types/src/componentToJson.d.ts +32 -0
  115. package/types/src/drag-drop-next/DragContextNext.d.ts +73 -0
  116. package/types/src/drag-drop-next/DragDropProviderNext.d.ts +20 -0
  117. package/types/src/drag-drop-next/SpaceMan.d.ts +35 -0
  118. package/types/src/drag-drop-next/tabstrip-drag-drop.d.ts +3 -0
  119. package/types/src/grid-dom-utils.d.ts +17 -0
  120. package/types/src/grid-layout-utils.d.ts +80 -0
  121. package/types/src/grid-matrix.d.ts +5 -0
  122. package/types/src/index.d.ts +10 -0
  123. package/types/src/layoutFromJson.d.ts +3 -0
  124. package/types/src/layoutToJson.d.ts +2 -0
  125. package/types/src/propUtils.d.ts +5 -0
  126. package/types/src/react-element-utils.d.ts +3 -0
  127. package/types/src/typeOf.d.ts +6 -0
  128. package/types/src/useAsDropTarget.d.ts +7 -0
  129. package/types/src/useDraggable.d.ts +23 -0
  130. package/types/src/useGridChildProps.d.ts +13 -0
  131. package/types/src/useGridLayout.d.ts +43 -0
  132. package/types/src/useGridSplitterResizing.d.ts +12 -0
  133. package/types/src/useNotDropTarget.d.ts +1 -0
  134. package/types/typeOf.d.ts +6 -0
  135. package/types/useAsDropTarget.d.ts +7 -0
  136. package/types/useDraggable.d.ts +24 -0
  137. package/types/useEditTabName.d.ts +11 -0
  138. package/types/useGridChildProps.d.ts +13 -0
  139. package/types/useGridControllerSnapshot.d.ts +7 -0
  140. package/types/useGridLayout.d.ts +53 -0
  141. package/types/useGridSplitterResizing.d.ts +14 -0
  142. package/types/useNotDropTarget.d.ts +1 -0
@@ -0,0 +1,41 @@
1
+ import { elementImplementsJSONSerialization } from "@vuu-ui/vuu-utils";
2
+ import react from "react";
3
+ import { getProps } from "./propUtils.js";
4
+ import { typeOf } from "./typeOf.js";
5
+ function componentToJson(element) {
6
+ if (elementImplementsJSONSerialization(element)) return element.type.toJSON(element);
7
+ {
8
+ const type = typeOf(element);
9
+ const { id, children, type: _omit, ...props } = getProps(element);
10
+ const state = void 0;
11
+ return {
12
+ id,
13
+ type,
14
+ props: serializeProps(props),
15
+ state,
16
+ children: react.Children.map(children, componentChildToJson) ?? void 0
17
+ };
18
+ }
19
+ }
20
+ function componentChildToJson(child) {
21
+ if ("string" == typeof child || "number" == typeof child) return child;
22
+ return react.isValidElement(child) ? componentToJson(child) : null;
23
+ }
24
+ function serializeProps(props) {
25
+ if (props) {
26
+ const { path, ...otherProps } = props;
27
+ const result = {};
28
+ for (const [key, value] of Object.entries(otherProps))result[key] = serializeValue(value);
29
+ return result;
30
+ }
31
+ }
32
+ function serializeValue(value) {
33
+ if ("string" == typeof value || "number" == typeof value || "boolean" == typeof value) return value;
34
+ if (Array.isArray(value)) return value.map(serializeValue);
35
+ if ("object" == typeof value && null !== value) {
36
+ const result = {};
37
+ for (const [k, v] of Object.entries(value))result[k] = serializeValue(v);
38
+ return result;
39
+ }
40
+ }
41
+ export { componentToJson, serializeProps };
@@ -0,0 +1,173 @@
1
+ import { EventEmitter } from "@vuu-ui/vuu-utils";
2
+ import { isTypedComponentTemplate, sourceIsComponent, sourceIsTabbedComponent, sourceIsTemplate } from "../GridLayoutContext.js";
3
+ import { initializeDragContainer } from "./tabstrip-drag-drop.js";
4
+ class DragContext extends EventEmitter {
5
+ templateDragSession;
6
+ #dragElementWidth;
7
+ #dragLabelWidth;
8
+ #dragStateCleanups = new Set();
9
+ #dragSource;
10
+ #dragSources = new Map();
11
+ #dropped = false;
12
+ #dropPending = false;
13
+ #dropZoneCache = new Map();
14
+ #element;
15
+ #mouseX = -1;
16
+ #mouseY = -1;
17
+ constructor(templateDragSession){
18
+ super(), this.templateDragSession = templateDragSession;
19
+ }
20
+ beginDrag(e, dragSource) {
21
+ const { clientX: x, clientY: y, dataTransfer } = e;
22
+ if (dataTransfer) {
23
+ dataTransfer.effectAllowed = "move";
24
+ if (sourceIsTemplate(dragSource)) dataTransfer.setData("text/json", JSON.stringify(isTypedComponentTemplate(dragSource) ? dragSource.component : JSON.parse(dragSource.componentJson)));
25
+ else if (sourceIsComponent(dragSource)) dataTransfer.setData("text/plain", dragSource.id);
26
+ else if (sourceIsTabbedComponent(dragSource)) dataTransfer.setData("text/plain", dragSource.tab.id);
27
+ else throw Error("[DragContextNext] unsupported drag source type");
28
+ const { height, width } = dragSource.element.getBoundingClientRect();
29
+ let dragLabelWidth = width;
30
+ if (sourceIsComponent(dragSource) && dragSource.dragElement) dragLabelWidth = dragSource.dragElement.getBoundingClientRect()?.width;
31
+ this.#dragSource = dragSource;
32
+ this.#dropped = false;
33
+ this.#dropPending = false;
34
+ this.#dragElementWidth = width;
35
+ this.#dragLabelWidth = dragLabelWidth;
36
+ this.#mouseX = x;
37
+ this.#mouseY = y;
38
+ if (sourceIsTemplate(dragSource)) this.templateDragSession?.begin(dragSource, {
39
+ elementHeight: height,
40
+ elementWidth: width,
41
+ labelWidth: dragLabelWidth,
42
+ x,
43
+ y
44
+ });
45
+ }
46
+ }
47
+ endDrag() {
48
+ this.cleanupDragState();
49
+ if (this.#dragSource && sourceIsTemplate(this.#dragSource)) this.templateDragSession?.end();
50
+ this.#dropZoneCache.clear();
51
+ this.#dragSource = void 0;
52
+ this.#element = void 0;
53
+ this.#dragElementWidth = void 0;
54
+ }
55
+ beginDrop() {
56
+ this.#dropPending = true;
57
+ }
58
+ cancelDrag() {
59
+ if (sourceIsTabbedComponent(this.#dragSource)) {
60
+ const { layoutId: gridId, tabsId, label: value } = this.#dragSource;
61
+ this.emit("cancel-tab-drag", {
62
+ type: "cancel-tab-drag",
63
+ gridId,
64
+ itemId: this.#dragSource.tab.id,
65
+ tabsId,
66
+ value
67
+ });
68
+ }
69
+ this.endDrag();
70
+ }
71
+ completeDrop() {
72
+ const dragSource = this.dragSource;
73
+ this.#dropped = true;
74
+ this.#dropPending = false;
75
+ if (dragSource && sourceIsTemplate(dragSource)) this.templateDragSession?.completeDrop();
76
+ for (const cleanup of this.#dragStateCleanups)cleanup();
77
+ }
78
+ detachTab(gridId, tabsId, itemId, value) {
79
+ this.emit("detach-tab", {
80
+ type: "detach-tab",
81
+ gridId,
82
+ itemId,
83
+ tabsId,
84
+ value
85
+ });
86
+ }
87
+ drop = ({ tabsId, dropPosition })=>{
88
+ const dragSource = this.dragSource;
89
+ this.completeDrop();
90
+ if (dragSource) this.emit("drop", {
91
+ type: "drop",
92
+ dragSource,
93
+ tabsId,
94
+ dropPosition
95
+ });
96
+ else throw Error("[DragContextNext] drop, dragSource not defined");
97
+ };
98
+ registerTabsForDragDrop = (id)=>{
99
+ this.#dragSources.set(id, {
100
+ dropTargets: [
101
+ "*"
102
+ ]
103
+ });
104
+ const dragSourceElement = document.getElementById(`tabs-${id}`);
105
+ if (dragSourceElement) {
106
+ const cleanup = initializeDragContainer(dragSourceElement, this, "horizontal", id);
107
+ return ()=>{
108
+ cleanup();
109
+ this.#dragSources.delete(id);
110
+ };
111
+ }
112
+ throw Error(`[DragContextNext] registerDragSource no element found for #tabs-${id}`);
113
+ };
114
+ registerDragStateCleanup = (cleanup)=>{
115
+ this.#dragStateCleanups.add(cleanup);
116
+ return ()=>this.#dragStateCleanups.delete(cleanup);
117
+ };
118
+ cleanupDragState() {
119
+ for (const cleanup of this.#dragStateCleanups)cleanup();
120
+ }
121
+ get draggedElement() {
122
+ const element = this.#element;
123
+ if (element) return element;
124
+ throw Error("dragged element is unavailable because beginDrag was not called");
125
+ }
126
+ get dragElementWidth() {
127
+ return this.#dragElementWidth ?? this.templateDragSession?.metrics?.elementWidth ?? 100;
128
+ }
129
+ get dragLabelWidth() {
130
+ return this.#dragLabelWidth ?? this.templateDragSession?.metrics?.labelWidth ?? this.dragElementWidth;
131
+ }
132
+ get dragSource() {
133
+ return this.#dragSource ?? this.templateDragSession?.source;
134
+ }
135
+ get internalDragSources() {
136
+ return this.#dragSources;
137
+ }
138
+ set dragSources(dragSources) {
139
+ this.buildDragSources(dragSources);
140
+ }
141
+ get dropped() {
142
+ const dragSource = this.dragSource;
143
+ return dragSource && sourceIsTemplate(dragSource) ? this.templateDragSession?.dropped ?? this.#dropped : this.#dropped;
144
+ }
145
+ get dropPending() {
146
+ return this.#dropPending;
147
+ }
148
+ get x() {
149
+ return void 0 === this.#dragSource && this.templateDragSession?.source ? this.templateDragSession.metrics?.x ?? this.#mouseX : this.#mouseX;
150
+ }
151
+ set x(value) {
152
+ this.#mouseX = value;
153
+ if (void 0 === this.#dragSource && this.templateDragSession?.source) this.templateDragSession.x = value;
154
+ }
155
+ get y() {
156
+ return void 0 === this.#dragSource && this.templateDragSession?.source ? this.templateDragSession.metrics?.y ?? this.#mouseY : this.#mouseY;
157
+ }
158
+ set y(value) {
159
+ this.#mouseY = value;
160
+ if (void 0 === this.#dragSource && this.templateDragSession?.source) this.templateDragSession.y = value;
161
+ }
162
+ get ownsDrag() {
163
+ return void 0 !== this.#dragSource;
164
+ }
165
+ buildDragSources(dragSources) {
166
+ const sources = this.#dragSources;
167
+ for (const [sourceId, { dropTargets, orientation = "horizontal" }] of Object.entries(dragSources))sources.set(sourceId, {
168
+ dropTargets,
169
+ orientation
170
+ });
171
+ }
172
+ }
173
+ export { DragContext };
@@ -0,0 +1,72 @@
1
+ const css = `
2
+ .vuuDragContainer {
3
+ --orientation-transition-property: width;
4
+ --transition-property: var(--orientation-transition-property);
5
+ align-items: stretch;
6
+ row-gap: 0;
7
+ background: #fff !important;
8
+
9
+ &.vuuDragContainer-dragging {
10
+ & .saltTabNextTrigger {
11
+ pointer-events: none;
12
+ }
13
+ }
14
+
15
+ &.vuuDragContainer-vertical {
16
+ --orientation-transition-property: height;
17
+
18
+ & .SpaceMan {
19
+ width: auto;
20
+ height: 0;
21
+ }
22
+ }
23
+
24
+ &:has(.vuuDraggableItem-settling) {
25
+ overflow: visible;
26
+ }
27
+
28
+ & .vuuDraggableItem:not([aria-selected="true"]) {
29
+ background: inherit;
30
+ }
31
+
32
+ & .vuuTabDropTarget-before {
33
+ box-shadow: inset 3px 0 var(--salt-status-info-foreground);
34
+ }
35
+
36
+ & .vuuTabDropTarget-after {
37
+ box-shadow: inset -3px 0 var(--salt-status-info-foreground);
38
+ }
39
+
40
+ & .vuuDraggableItem-hidden {
41
+ z-index: -1;
42
+ position: absolute;
43
+ }
44
+
45
+ & .vuuDraggableItem-settling {
46
+ z-index: 1;
47
+ position: absolute;
48
+ }
49
+
50
+ & .vuuDraggableItem-animating {
51
+ transition: transform .2s ease-in-out;
52
+ }
53
+
54
+ & .SpaceMan {
55
+ min-height: 100%;
56
+ transition-property: var(--transition-property);
57
+ background-color: #d9d9d9;
58
+ flex: none;
59
+ align-self: stretch;
60
+ width: 0;
61
+ transition-duration: .1s;
62
+ transition-timing-function: ease-in;
63
+ }
64
+
65
+ &.saltTabListNext {
66
+ gap: 0;
67
+ }
68
+ }
69
+
70
+
71
+ `;
72
+ export default css;
@@ -0,0 +1,91 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useMemo, useRef } from "react";
3
+ import { DragContext } from "./DragContextNext.js";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import DragDropProviderNext from "./DragDropProviderNext.css";
7
+ import { useTemplateDragSession } from "./TemplateDragSession.js";
8
+ const DragDropContext = /*#__PURE__*/ createContext(void 0);
9
+ const NOOP = ()=>void 0;
10
+ const DragDropProviderNext_DragDropProviderNext = ({ children, onCancelDrag = NOOP, onCancelTabDrag, onDetachTab, onDrop })=>{
11
+ const targetWindow = useWindow();
12
+ const templateDragSession = useTemplateDragSession();
13
+ useComponentCssInjection({
14
+ testId: "vuu-drag-drop-provider",
15
+ css: DragDropProviderNext,
16
+ window: targetWindow
17
+ });
18
+ const dragContext = useMemo(()=>new DragContext(templateDragSession), [
19
+ templateDragSession
20
+ ]);
21
+ const handlersRef = useRef({
22
+ onCancelDrag,
23
+ onCancelTabDrag,
24
+ onDetachTab,
25
+ onDrop
26
+ });
27
+ handlersRef.current = {
28
+ onCancelDrag,
29
+ onCancelTabDrag,
30
+ onDetachTab,
31
+ onDrop
32
+ };
33
+ useEffect(()=>{
34
+ if (!targetWindow) return;
35
+ const cancelActiveDrag = ()=>{
36
+ if (dragContext.dropPending) return;
37
+ handlersRef.current.onCancelDrag();
38
+ if (dragContext.ownsDrag) dragContext.cancelDrag();
39
+ else dragContext.cleanupDragState();
40
+ };
41
+ const handleKeyDown = (event)=>{
42
+ if ("Escape" === event.key) cancelActiveDrag();
43
+ };
44
+ const handlePointerCancel = ()=>{
45
+ if (!dragContext.ownsDrag) cancelActiveDrag();
46
+ };
47
+ const handleMouseUp = ()=>{
48
+ if (!dragContext.dragSource) return;
49
+ if (dragContext.dropped) dragContext.endDrag();
50
+ else cancelActiveDrag();
51
+ };
52
+ const handleCancelTabDrag = (event)=>handlersRef.current.onCancelTabDrag(event);
53
+ const handleDetachTab = (event)=>handlersRef.current.onDetachTab(event);
54
+ const handleDrop = (event)=>handlersRef.current.onDrop(event);
55
+ dragContext.on("cancel-tab-drag", handleCancelTabDrag);
56
+ dragContext.on("detach-tab", handleDetachTab);
57
+ dragContext.on("drop", handleDrop);
58
+ targetWindow.addEventListener("keydown", handleKeyDown);
59
+ targetWindow.addEventListener("dragend", cancelActiveDrag);
60
+ targetWindow.addEventListener("mouseup", handleMouseUp);
61
+ targetWindow.addEventListener("pointercancel", handlePointerCancel);
62
+ const cleanupCallbacks = [];
63
+ return ()=>{
64
+ dragContext.removeListener("cancel-tab-drag", handleCancelTabDrag);
65
+ dragContext.removeListener("detach-tab", handleDetachTab);
66
+ dragContext.removeListener("drop", handleDrop);
67
+ targetWindow.removeEventListener("keydown", handleKeyDown);
68
+ targetWindow.removeEventListener("dragend", cancelActiveDrag);
69
+ targetWindow.removeEventListener("mouseup", handleMouseUp);
70
+ targetWindow.removeEventListener("pointercancel", handlePointerCancel);
71
+ cancelActiveDrag();
72
+ cleanupCallbacks.forEach((cleanup)=>{
73
+ cleanup();
74
+ });
75
+ };
76
+ }, [
77
+ dragContext,
78
+ targetWindow
79
+ ]);
80
+ return /*#__PURE__*/ jsx(DragDropContext.Provider, {
81
+ value: dragContext,
82
+ children: children
83
+ });
84
+ };
85
+ const useDragContext = ()=>{
86
+ const dragContext = useContext(DragDropContext);
87
+ if (!dragContext) throw Error("[useDragContext] no DragDropProviderNext found");
88
+ return dragContext;
89
+ };
90
+ const useOptionalDragContext = ()=>useContext(DragDropContext);
91
+ export { DragDropProviderNext_DragDropProviderNext as DragDropProviderNext, useDragContext, useOptionalDragContext };
@@ -0,0 +1,256 @@
1
+ import { asInteger, isValidNumber } from "@vuu-ui/vuu-utils";
2
+ class SpaceMan {
3
+ id;
4
+ #dragContext;
5
+ #dragItem;
6
+ #dragContainer = null;
7
+ #dragStartTimer;
8
+ #dragSize = 0;
9
+ #dragOperation = "none";
10
+ #fromIndex;
11
+ #mouseOffset = {
12
+ x: 0,
13
+ y: 0
14
+ };
15
+ #orientation;
16
+ #sizeProperty;
17
+ #spacer1 = this.createDragSpacer();
18
+ #spacer2 = this.createDragSpacer();
19
+ #state = "initial";
20
+ #toDirection;
21
+ #toIndex = void 0;
22
+ #withinDragContainer = false;
23
+ constructor(dragContext, id, orientation = "horizontal"){
24
+ this.id = id;
25
+ this.#dragContext = dragContext;
26
+ this.#orientation = orientation;
27
+ this.#sizeProperty = "horizontal" === orientation ? "width" : "height";
28
+ }
29
+ get mouseOffset() {
30
+ return this.#mouseOffset;
31
+ }
32
+ set mouseOffset(offset) {
33
+ this.#mouseOffset = offset;
34
+ }
35
+ get dropPosition() {
36
+ const dropTargetSpacer = this.#dragContainer?.querySelector('[data-drop-target="true"]');
37
+ if (dropTargetSpacer) {
38
+ let siblingElement = dropTargetSpacer.nextElementSibling;
39
+ if (siblingElement) return {
40
+ position: "before",
41
+ target: siblingElement.dataset.gridLayoutItemId ?? siblingElement.dataset.label ?? siblingElement.id
42
+ };
43
+ siblingElement = dropTargetSpacer.previousElementSibling;
44
+ if (siblingElement === this.#dragItem) siblingElement = siblingElement.previousElementSibling;
45
+ if (siblingElement) return {
46
+ position: "after",
47
+ target: siblingElement.dataset.gridLayoutItemId ?? siblingElement.dataset.label ?? siblingElement.id
48
+ };
49
+ }
50
+ }
51
+ get positionRelativeToTargetTab() {
52
+ return "before";
53
+ }
54
+ setDragContainer = (el)=>{
55
+ this.#dragContainer = el;
56
+ };
57
+ enterDragContainer() {
58
+ this.#withinDragContainer = true;
59
+ }
60
+ leaveDragContainer() {
61
+ this.#withinDragContainer = false;
62
+ this.#state = "away";
63
+ this.setSpacerSizes(0, 0);
64
+ setTimeout(()=>{
65
+ this.clearSpacers();
66
+ }, 200);
67
+ }
68
+ dragStart(index) {
69
+ const item = this.#dragContainer?.querySelector(`[data-index="${index}"]`);
70
+ if (item) {
71
+ this.clearDragStartTimer();
72
+ const propertyName = this.#sizeProperty;
73
+ const { [propertyName]: size } = item.getBoundingClientRect();
74
+ this.#dragSize = size;
75
+ this.#dragOperation = "local";
76
+ this.#withinDragContainer = true;
77
+ this.#dragStartTimer = setTimeout(()=>{
78
+ this.#dragStartTimer = void 0;
79
+ this.#dragContainer?.classList.add("vuuDragContainer-dragging");
80
+ this.#spacer1.style[propertyName] = `${size}px`;
81
+ this.#spacer1.dataset.dropTarget = "true";
82
+ item.after(this.#spacer1);
83
+ this.#dragItem = item;
84
+ item.classList.add("vuuDraggableItem-hidden");
85
+ if (!this.#withinDragContainer) this.setSpacerSizes(0, 0, 30);
86
+ }, 60);
87
+ this.#state = "1spacer";
88
+ this.#fromIndex = index;
89
+ } else throw Error(`[SpaceMan] spaceOut no item at index[${index}]`);
90
+ }
91
+ dragEnter(index, direction) {
92
+ const propertyName = this.#sizeProperty;
93
+ if (index === this.#toIndex && direction === this.#toDirection) return;
94
+ if (false === this.#withinDragContainer) {
95
+ this.#toIndex = index > asInteger(this.#fromIndex, Number.MAX_SAFE_INTEGER) ? index - 1 : index;
96
+ this.enterDragContainer();
97
+ this.insertSpacer(index, this.#dragContext.dragLabelWidth, direction);
98
+ if ("none" === this.#dragOperation) this.#dragOperation = "remote";
99
+ } else {
100
+ this.#toIndex = index;
101
+ this.#toDirection = direction;
102
+ const item = this.#dragContainer?.querySelector(`[data-index="${index}"]`);
103
+ if (item) {
104
+ if ("none" === this.#dragOperation) {
105
+ this.#dragOperation = "remote";
106
+ this.insertSpacer(index, 100);
107
+ } else if ("fwd" === direction) {
108
+ if ("1spacer" === this.#state) {
109
+ item.after(this.#spacer2);
110
+ this.#state = "2spacer";
111
+ this.setSpacerSizes(0, this.#dragSize);
112
+ } else if ("2spacer" === this.#state) if ("0px" === this.#spacer1.style[propertyName]) {
113
+ item.after(this.#spacer1);
114
+ this.setSpacerSizes(this.#dragSize, 0);
115
+ } else {
116
+ item.after(this.#spacer2);
117
+ this.setSpacerSizes(0, this.#dragSize);
118
+ }
119
+ } else if ("1spacer" === this.#state) {
120
+ item.before(this.#spacer2);
121
+ this.#state = "2spacer";
122
+ this.setSpacerSizes(0, this.#dragSize);
123
+ } else if ("2spacer" === this.#state) if ("0px" === this.#spacer1.style[propertyName]) {
124
+ item.before(this.#spacer1);
125
+ this.setSpacerSizes(this.#dragSize, 0);
126
+ } else {
127
+ item.before(this.#spacer2);
128
+ this.setSpacerSizes(0, this.#dragSize);
129
+ }
130
+ } else throw Error(`[SpaceMan] dragEnter no item at index[${index}]`);
131
+ }
132
+ }
133
+ insertSpacer(index, size, direction = "bwd") {
134
+ if ("initial" === this.#state || "away" === this.#state) {
135
+ const item = this.#dragContainer?.querySelector(`[data-index="${index}"]`);
136
+ if (item) {
137
+ this.#dragSize = size;
138
+ if ("fwd" === direction) item.after(this.#spacer1);
139
+ else item.before(this.#spacer1);
140
+ this.#state = "1spacer";
141
+ this.setSpacerSizes(size);
142
+ } else throw Error(`[SpaceMan] inject no item at index[${index}]`);
143
+ } else throw Error(`can only inject drag content when state is "initial" or "away", found "${this.#state}"`);
144
+ }
145
+ clearSpacers() {
146
+ const propertyName = this.#sizeProperty;
147
+ this.#spacer1.remove();
148
+ this.#spacer2.remove();
149
+ this.#spacer1.style[propertyName] = "0px";
150
+ this.#spacer2.style[propertyName] = "0px";
151
+ }
152
+ clearDragStartTimer() {
153
+ if (void 0 !== this.#dragStartTimer) {
154
+ clearTimeout(this.#dragStartTimer);
155
+ this.#dragStartTimer = void 0;
156
+ }
157
+ }
158
+ cleanup() {
159
+ this.clearDragStartTimer();
160
+ this.clearSpacers();
161
+ this.#dragContainer?.classList.remove("vuuDragContainer-dragging");
162
+ if (this.#dragItem) {
163
+ this.#dragItem.classList.remove("vuuDraggableItem-hidden");
164
+ this.#dragItem.classList.remove("vuuDraggableItem-settling");
165
+ this.#dragItem.classList.remove("vuuDraggableItem-animating");
166
+ this.#dragItem.style.left = "";
167
+ this.#dragItem.style.top = "";
168
+ this.#dragItem.style.transform = "";
169
+ this.#dragItem.style.width = "";
170
+ this.#dragItem = void 0;
171
+ }
172
+ this.#dragOperation = "none";
173
+ this.#fromIndex = void 0;
174
+ this.#state = "initial";
175
+ this.#toDirection = void 0;
176
+ this.#toIndex = void 0;
177
+ this.#withinDragContainer = false;
178
+ }
179
+ drop(x, y) {
180
+ return new Promise((resolve)=>{
181
+ if (this.#dragItem) {
182
+ const dragItem = this.#dragItem;
183
+ let settled = false;
184
+ const settleComplete = ()=>{
185
+ if (settled) return;
186
+ settled = true;
187
+ dragItem.removeEventListener("transitionend", settleComplete);
188
+ this.cleanup();
189
+ resolve();
190
+ };
191
+ setTimeout(settleComplete, 250);
192
+ dragItem?.classList.replace("vuuDraggableItem-hidden", "vuuDraggableItem-settling");
193
+ const { left: containerLeft, top: containerTop } = this.getPositionOfDragContainer();
194
+ const { left, top, width } = this.getPositionOfDropTarget();
195
+ if ("vertical" === this.#orientation) dragItem.style.width = `${width}px`;
196
+ dragItem.style.left = `${left - containerLeft}px`;
197
+ dragItem.style.top = `${top - containerTop}px`;
198
+ const offsetLeft = x - this.#mouseOffset.x - left - 1;
199
+ const offsetTop = y - this.#mouseOffset.y - top - 1;
200
+ dragItem.style.transform = `translate(${offsetLeft}px, ${offsetTop}px)`;
201
+ this.#dragContainer?.classList.remove("vuuDragContainer-dragging");
202
+ requestAnimationFrame(()=>{
203
+ dragItem?.classList.add("vuuDraggableItem-animating");
204
+ dragItem.addEventListener("transitionend", settleComplete);
205
+ dragItem.style.transform = "translate(0px, 0px)";
206
+ });
207
+ } else {
208
+ this.cleanup();
209
+ resolve();
210
+ }
211
+ });
212
+ }
213
+ getPositionOfDragContainer() {
214
+ if (this.#dragContainer) {
215
+ const { left, top } = this.#dragContainer.getBoundingClientRect();
216
+ return {
217
+ left,
218
+ top
219
+ };
220
+ }
221
+ throw Error("[SpaceMan] getPositionOfDragContainer, no drag container");
222
+ }
223
+ getPositionOfDropTarget(index = 0) {
224
+ const spacer = 0 === index ? this.#spacer1 : this.#spacer2;
225
+ const { [this.#sizeProperty]: size, left, top, width } = spacer.getBoundingClientRect();
226
+ if (0 !== size) return {
227
+ left,
228
+ top,
229
+ width
230
+ };
231
+ if (0 === index) return this.getPositionOfDropTarget(1);
232
+ throw Error("no spacer with expected width");
233
+ }
234
+ setSpacerSizes(size1, size2, timeout = 0) {
235
+ const propertyName = this.#sizeProperty;
236
+ const applySizes = ()=>{
237
+ if (null === this.#spacer1.parentNode) ;
238
+ else {
239
+ this.#spacer1.style[propertyName] = `${size1}px`;
240
+ this.#spacer1.dataset.dropTarget = size1 > 0 ? "true" : "false";
241
+ if (isValidNumber(size2)) {
242
+ this.#spacer2.style[propertyName] = `${size2}px`;
243
+ this.#spacer2.dataset.dropTarget = size2 > 0 ? "true" : "false";
244
+ }
245
+ }
246
+ };
247
+ if (0 === timeout) applySizes();
248
+ else setTimeout(applySizes, timeout);
249
+ }
250
+ createDragSpacer() {
251
+ const spacer = document.createElement("div");
252
+ spacer.className = "SpaceMan";
253
+ return spacer;
254
+ }
255
+ }
256
+ export { SpaceMan };
@@ -0,0 +1,36 @@
1
+ import { createContext, useContext } from "react";
2
+ class TemplateDragSession {
3
+ #dropped = false;
4
+ #metrics;
5
+ #source;
6
+ begin(source, metrics) {
7
+ this.#dropped = false;
8
+ this.#metrics = metrics;
9
+ this.#source = source;
10
+ }
11
+ completeDrop() {
12
+ this.#dropped = true;
13
+ }
14
+ end() {
15
+ this.#metrics = void 0;
16
+ this.#source = void 0;
17
+ }
18
+ get dropped() {
19
+ return this.#dropped;
20
+ }
21
+ get metrics() {
22
+ return this.#metrics;
23
+ }
24
+ get source() {
25
+ return this.#source;
26
+ }
27
+ set x(value) {
28
+ if (this.#metrics) this.#metrics.x = value;
29
+ }
30
+ set y(value) {
31
+ if (this.#metrics) this.#metrics.y = value;
32
+ }
33
+ }
34
+ const TemplateDragSessionContext = createContext(void 0);
35
+ const useTemplateDragSession = ()=>useContext(TemplateDragSessionContext);
36
+ export { TemplateDragSession, TemplateDragSessionContext, useTemplateDragSession };