@tsdraw/react 0.9.1 → 0.9.3
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/dist/index.cjs +217 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +218 -46
- package/dist/index.js.map +1 -1
- package/dist/tsdraw.css +29 -8
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -44,6 +44,15 @@ interface TsdrawStylePanelCustomPart {
|
|
|
44
44
|
render: (context: TsdrawStylePanelRenderContext) => ReactNode;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
type VerticalPart = 'top' | 'bottom' | 'center';
|
|
48
|
+
type HorizontalPart = 'left' | 'right' | 'center';
|
|
49
|
+
type UiAnchor = `${VerticalPart}-${HorizontalPart}` | `${HorizontalPart}-${VerticalPart}`;
|
|
50
|
+
interface TsdrawUiPlacement {
|
|
51
|
+
anchor?: UiAnchor;
|
|
52
|
+
edgeOffset?: number;
|
|
53
|
+
style?: CSSProperties;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
interface TsdrawCursorContext {
|
|
48
57
|
currentTool: ToolId;
|
|
49
58
|
defaultCursor: string;
|
|
@@ -79,9 +88,6 @@ interface TsdrawMountApi {
|
|
|
79
88
|
}>) => void;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
type VerticalPart = 'top' | 'bottom' | 'center';
|
|
83
|
-
type HorizontalPart = 'left' | 'right' | 'center';
|
|
84
|
-
type UiAnchor = `${VerticalPart}-${HorizontalPart}` | `${HorizontalPart}-${VerticalPart}`;
|
|
85
91
|
interface TsdrawCustomTool {
|
|
86
92
|
id: ToolId;
|
|
87
93
|
label: string;
|
|
@@ -95,17 +101,14 @@ interface TsdrawCustomTool {
|
|
|
95
101
|
}
|
|
96
102
|
type TsdrawToolbarBuiltInAction = 'undo' | 'redo';
|
|
97
103
|
type ToolbarPartItem = ToolId | TsdrawToolbarBuiltInAction;
|
|
98
|
-
interface TsdrawUiPlacement {
|
|
99
|
-
anchor?: UiAnchor;
|
|
100
|
-
offsetX?: number;
|
|
101
|
-
offsetY?: number;
|
|
102
|
-
style?: CSSProperties;
|
|
103
|
-
}
|
|
104
104
|
interface TsdrawUiOptions {
|
|
105
105
|
toolbar?: {
|
|
106
106
|
hide?: boolean;
|
|
107
107
|
placement?: TsdrawUiPlacement;
|
|
108
108
|
parts?: ToolbarPartItem[][];
|
|
109
|
+
draggable?: boolean;
|
|
110
|
+
saveDraggedPosition?: boolean;
|
|
111
|
+
disabledDragPositions?: UiAnchor[];
|
|
109
112
|
};
|
|
110
113
|
stylePanel?: {
|
|
111
114
|
hide?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,15 @@ interface TsdrawStylePanelCustomPart {
|
|
|
44
44
|
render: (context: TsdrawStylePanelRenderContext) => ReactNode;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
type VerticalPart = 'top' | 'bottom' | 'center';
|
|
48
|
+
type HorizontalPart = 'left' | 'right' | 'center';
|
|
49
|
+
type UiAnchor = `${VerticalPart}-${HorizontalPart}` | `${HorizontalPart}-${VerticalPart}`;
|
|
50
|
+
interface TsdrawUiPlacement {
|
|
51
|
+
anchor?: UiAnchor;
|
|
52
|
+
edgeOffset?: number;
|
|
53
|
+
style?: CSSProperties;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
interface TsdrawCursorContext {
|
|
48
57
|
currentTool: ToolId;
|
|
49
58
|
defaultCursor: string;
|
|
@@ -79,9 +88,6 @@ interface TsdrawMountApi {
|
|
|
79
88
|
}>) => void;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
type VerticalPart = 'top' | 'bottom' | 'center';
|
|
83
|
-
type HorizontalPart = 'left' | 'right' | 'center';
|
|
84
|
-
type UiAnchor = `${VerticalPart}-${HorizontalPart}` | `${HorizontalPart}-${VerticalPart}`;
|
|
85
91
|
interface TsdrawCustomTool {
|
|
86
92
|
id: ToolId;
|
|
87
93
|
label: string;
|
|
@@ -95,17 +101,14 @@ interface TsdrawCustomTool {
|
|
|
95
101
|
}
|
|
96
102
|
type TsdrawToolbarBuiltInAction = 'undo' | 'redo';
|
|
97
103
|
type ToolbarPartItem = ToolId | TsdrawToolbarBuiltInAction;
|
|
98
|
-
interface TsdrawUiPlacement {
|
|
99
|
-
anchor?: UiAnchor;
|
|
100
|
-
offsetX?: number;
|
|
101
|
-
offsetY?: number;
|
|
102
|
-
style?: CSSProperties;
|
|
103
|
-
}
|
|
104
104
|
interface TsdrawUiOptions {
|
|
105
105
|
toolbar?: {
|
|
106
106
|
hide?: boolean;
|
|
107
107
|
placement?: TsdrawUiPlacement;
|
|
108
108
|
parts?: ToolbarPartItem[][];
|
|
109
|
+
draggable?: boolean;
|
|
110
|
+
saveDraggedPosition?: boolean;
|
|
111
|
+
disabledDragPositions?: UiAnchor[];
|
|
109
112
|
};
|
|
110
113
|
stylePanel?: {
|
|
111
114
|
hide?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
1
|
+
import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { DEFAULT_COLORS, renderCanvasBackground, getSelectionBoundsPage, buildTransformSnapshots, Editor, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor, pageToScreen, getTopShapeAtPoint, buildStartPositions, applyRotation, applyResize, applyMove, normalizeSelectionBounds, getShapesInBounds, HandDraggingState, startCameraSlide, isSelectTool, beginCameraPan, moveCameraPan } from '@tsdraw/core';
|
|
4
4
|
import { IconPointer, IconPencil, IconSquare, IconCircle, IconEraser, IconHandStop, IconArrowBackUp, IconArrowForwardUp } from '@tabler/icons-react';
|
|
@@ -96,6 +96,167 @@ function SelectionOverlay({
|
|
|
96
96
|
)
|
|
97
97
|
] });
|
|
98
98
|
}
|
|
99
|
+
function parseAnchor(anchor) {
|
|
100
|
+
const parts = anchor.split("-");
|
|
101
|
+
let vertical = "center";
|
|
102
|
+
let horizontal = "center";
|
|
103
|
+
for (const part of parts) {
|
|
104
|
+
if (part === "top" || part === "bottom") vertical = part;
|
|
105
|
+
else if (part === "left" || part === "right") horizontal = part;
|
|
106
|
+
}
|
|
107
|
+
return { vertical, horizontal };
|
|
108
|
+
}
|
|
109
|
+
function resolvePlacementStyle(placement, fallbackAnchor, fallbackEdgeOffset) {
|
|
110
|
+
const anchor = placement?.anchor ?? fallbackAnchor;
|
|
111
|
+
const edgeOffset = placement?.edgeOffset ?? fallbackEdgeOffset;
|
|
112
|
+
const { vertical, horizontal } = parseAnchor(anchor);
|
|
113
|
+
const result = {};
|
|
114
|
+
const transforms = [];
|
|
115
|
+
if (horizontal === "left") {
|
|
116
|
+
result.left = edgeOffset;
|
|
117
|
+
} else if (horizontal === "right") {
|
|
118
|
+
result.right = edgeOffset;
|
|
119
|
+
} else {
|
|
120
|
+
result.left = "50%";
|
|
121
|
+
transforms.push("translateX(-50%)");
|
|
122
|
+
}
|
|
123
|
+
if (vertical === "top") {
|
|
124
|
+
result.top = edgeOffset;
|
|
125
|
+
} else if (vertical === "bottom") {
|
|
126
|
+
result.bottom = edgeOffset;
|
|
127
|
+
} else {
|
|
128
|
+
result.top = "50%";
|
|
129
|
+
transforms.push("translateY(-50%)");
|
|
130
|
+
}
|
|
131
|
+
if (transforms.length > 0) result.transform = transforms.join(" ");
|
|
132
|
+
return placement?.style ? { ...result, ...placement.style } : result;
|
|
133
|
+
}
|
|
134
|
+
function resolveOrientation(anchor) {
|
|
135
|
+
const { vertical, horizontal } = parseAnchor(anchor);
|
|
136
|
+
if ((horizontal === "left" || horizontal === "right") && vertical === "center") return "vertical";
|
|
137
|
+
return "horizontal";
|
|
138
|
+
}
|
|
139
|
+
var SNAP_TARGETS = [
|
|
140
|
+
{ anchor: "top-center", nx: 0.5, ny: 0 },
|
|
141
|
+
{ anchor: "bottom-center", nx: 0.5, ny: 1 },
|
|
142
|
+
{ anchor: "left-center", nx: 0, ny: 0.5 },
|
|
143
|
+
{ anchor: "right-center", nx: 1, ny: 0.5 },
|
|
144
|
+
{ anchor: "top-left", nx: 0, ny: 0 },
|
|
145
|
+
{ anchor: "top-right", nx: 1, ny: 0 },
|
|
146
|
+
{ anchor: "bottom-left", nx: 0, ny: 1 },
|
|
147
|
+
{ anchor: "bottom-right", nx: 1, ny: 1 }
|
|
148
|
+
];
|
|
149
|
+
function calculateSnap(payload, containerRect, disabledPositions) {
|
|
150
|
+
const nx = Math.max(0, Math.min(1, (payload.centerX - containerRect.left) / containerRect.width));
|
|
151
|
+
const ny = Math.max(0, Math.min(1, (payload.centerY - containerRect.top) / containerRect.height));
|
|
152
|
+
let closestAnchor = "bottom-center";
|
|
153
|
+
let closestDistSq = Infinity;
|
|
154
|
+
for (const target of SNAP_TARGETS) {
|
|
155
|
+
if (disabledPositions && disabledPositions.has(target.anchor)) continue;
|
|
156
|
+
const dx = nx - target.nx;
|
|
157
|
+
const dy = ny - target.ny;
|
|
158
|
+
const distSq = dx * dx + dy * dy;
|
|
159
|
+
if (distSq < closestDistSq) {
|
|
160
|
+
closestDistSq = distSq;
|
|
161
|
+
closestAnchor = target.anchor;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { anchor: closestAnchor };
|
|
165
|
+
}
|
|
166
|
+
function BaseComponent({ children, className, style, draggable = false, onDragEnd, "aria-label": ariaLabel }) {
|
|
167
|
+
const componentRef = useRef(null);
|
|
168
|
+
const dragStateRef = useRef(null);
|
|
169
|
+
const suppressClickRef = useRef(false);
|
|
170
|
+
const pendingSnapRef = useRef(false);
|
|
171
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
if (!pendingSnapRef.current) return;
|
|
174
|
+
const componentNode = componentRef.current;
|
|
175
|
+
if (componentNode) componentNode.style.translate = "";
|
|
176
|
+
pendingSnapRef.current = false;
|
|
177
|
+
setIsDragging(false);
|
|
178
|
+
});
|
|
179
|
+
const finishDrag = (event, shouldSnap) => {
|
|
180
|
+
const componentNode = componentRef.current;
|
|
181
|
+
const dragState = dragStateRef.current;
|
|
182
|
+
if (!componentNode || !dragState || dragState.pointerId !== event.pointerId) return;
|
|
183
|
+
if (componentNode.hasPointerCapture(event.pointerId)) {
|
|
184
|
+
componentNode.releasePointerCapture(event.pointerId);
|
|
185
|
+
}
|
|
186
|
+
if (dragState.isDragging && shouldSnap && onDragEnd) {
|
|
187
|
+
const rect = componentNode.getBoundingClientRect();
|
|
188
|
+
onDragEnd({
|
|
189
|
+
left: rect.left,
|
|
190
|
+
top: rect.top,
|
|
191
|
+
width: rect.width,
|
|
192
|
+
height: rect.height,
|
|
193
|
+
centerX: rect.left + rect.width / 2,
|
|
194
|
+
centerY: rect.top + rect.height / 2
|
|
195
|
+
});
|
|
196
|
+
pendingSnapRef.current = true;
|
|
197
|
+
} else {
|
|
198
|
+
componentNode.style.translate = "";
|
|
199
|
+
setIsDragging(false);
|
|
200
|
+
}
|
|
201
|
+
suppressClickRef.current = dragState.didDrag;
|
|
202
|
+
dragStateRef.current = null;
|
|
203
|
+
};
|
|
204
|
+
const handlePointerDown = (event) => {
|
|
205
|
+
if (!draggable || event.button !== 0) return;
|
|
206
|
+
dragStateRef.current = {
|
|
207
|
+
pointerId: event.pointerId,
|
|
208
|
+
startX: event.clientX,
|
|
209
|
+
startY: event.clientY,
|
|
210
|
+
didDrag: false,
|
|
211
|
+
isDragging: false
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
const handlePointerMove = (event) => {
|
|
215
|
+
const componentNode = componentRef.current;
|
|
216
|
+
const dragState = dragStateRef.current;
|
|
217
|
+
if (!draggable || !componentNode || !dragState || dragState.pointerId !== event.pointerId) return;
|
|
218
|
+
const deltaX = event.clientX - dragState.startX;
|
|
219
|
+
const deltaY = event.clientY - dragState.startY;
|
|
220
|
+
if (!dragState.isDragging && deltaX * deltaX + deltaY * deltaY >= 25) {
|
|
221
|
+
dragState.isDragging = true;
|
|
222
|
+
dragState.didDrag = true;
|
|
223
|
+
componentNode.setPointerCapture(event.pointerId);
|
|
224
|
+
setIsDragging(true);
|
|
225
|
+
}
|
|
226
|
+
if (dragState.isDragging) {
|
|
227
|
+
componentNode.style.translate = `${deltaX}px ${deltaY}px`;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const handlePointerUp = (event) => {
|
|
231
|
+
finishDrag(event, true);
|
|
232
|
+
};
|
|
233
|
+
const handlePointerCancel = (event) => {
|
|
234
|
+
finishDrag(event, false);
|
|
235
|
+
};
|
|
236
|
+
const handleClickCapture = (event) => {
|
|
237
|
+
if (!draggable || !suppressClickRef.current) return;
|
|
238
|
+
suppressClickRef.current = false;
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
event.stopPropagation();
|
|
241
|
+
};
|
|
242
|
+
const draggableClass = draggable ? " tsdraw-component--draggable" : "";
|
|
243
|
+
const draggingClass = isDragging ? " tsdraw-component--dragging" : "";
|
|
244
|
+
return /* @__PURE__ */ jsx(
|
|
245
|
+
"div",
|
|
246
|
+
{
|
|
247
|
+
ref: componentRef,
|
|
248
|
+
className: `tsdraw-component${draggableClass}${draggingClass}${className ? ` ${className}` : ""}`,
|
|
249
|
+
style,
|
|
250
|
+
"aria-label": ariaLabel,
|
|
251
|
+
onPointerDown: handlePointerDown,
|
|
252
|
+
onPointerMove: handlePointerMove,
|
|
253
|
+
onPointerUp: handlePointerUp,
|
|
254
|
+
onPointerCancel: handlePointerCancel,
|
|
255
|
+
onClickCapture: handleClickCapture,
|
|
256
|
+
children
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
99
260
|
var STYLE_COLORS = Object.entries(DEFAULT_COLORS).filter(([key]) => key !== "white").map(([value]) => ({ value }));
|
|
100
261
|
var STYLE_DASHES = ["draw", "solid", "dashed", "dotted"];
|
|
101
262
|
var STYLE_FILLS = ["none", "blank", "semi", "solid"];
|
|
@@ -127,7 +288,7 @@ function StylePanel({
|
|
|
127
288
|
onSizeSelect
|
|
128
289
|
};
|
|
129
290
|
const customPartMap = new Map((customParts ?? []).map((customPart) => [customPart.id, customPart]));
|
|
130
|
-
return /* @__PURE__ */ jsx(
|
|
291
|
+
return /* @__PURE__ */ jsx(BaseComponent, { className: "tsdraw-style-panel", style, "aria-label": "Draw style panel", children: parts.map((part) => {
|
|
131
292
|
if (part === "colors") {
|
|
132
293
|
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-colors", children: STYLE_COLORS.map((item) => /* @__PURE__ */ jsx(
|
|
133
294
|
"button",
|
|
@@ -247,8 +408,9 @@ function getActionIcon(actionId) {
|
|
|
247
408
|
if (actionId === "undo") return /* @__PURE__ */ jsx(IconArrowBackUp, { size: 16, stroke: 1.8 });
|
|
248
409
|
return /* @__PURE__ */ jsx(IconArrowForwardUp, { size: 16, stroke: 1.8 });
|
|
249
410
|
}
|
|
250
|
-
function Toolbar({ parts, currentTool, onToolChange, disabled, style }) {
|
|
251
|
-
|
|
411
|
+
function Toolbar({ parts, currentTool, onToolChange, disabled, style, orientation = "horizontal", draggable = false, onDragEnd }) {
|
|
412
|
+
const orientationClass = orientation === "vertical" ? " tsdraw-toolbar--vertical" : "";
|
|
413
|
+
return /* @__PURE__ */ jsx(BaseComponent, { className: `tsdraw-toolbar${orientationClass}`, style, draggable, onDragEnd, children: parts.map((part, partIndex) => /* @__PURE__ */ jsxs("div", { className: "tsdraw-toolbar-part", children: [
|
|
252
414
|
part.items.map((item) => {
|
|
253
415
|
if (item.type === "action") {
|
|
254
416
|
return /* @__PURE__ */ jsx(
|
|
@@ -1611,6 +1773,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1611
1773
|
if (ignorePersistenceChanges) return;
|
|
1612
1774
|
schedulePersist();
|
|
1613
1775
|
});
|
|
1776
|
+
const cleanupRenderRequest = editor.onRequestRender(() => {
|
|
1777
|
+
render();
|
|
1778
|
+
refreshSelectionBounds(editor);
|
|
1779
|
+
});
|
|
1614
1780
|
resize();
|
|
1615
1781
|
const ro = new ResizeObserver(resize);
|
|
1616
1782
|
ro.observe(container);
|
|
@@ -1679,6 +1845,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1679
1845
|
schedulePersistRef.current = null;
|
|
1680
1846
|
cleanupEditorListener();
|
|
1681
1847
|
cleanupHistoryListener();
|
|
1848
|
+
cleanupRenderRequest();
|
|
1682
1849
|
disposeMount?.();
|
|
1683
1850
|
ro.disconnect();
|
|
1684
1851
|
canvas.removeEventListener("pointerdown", handlePointerDown);
|
|
@@ -1848,47 +2015,9 @@ var DEFAULT_TOOL_LABELS = {
|
|
|
1848
2015
|
eraser: "Eraser",
|
|
1849
2016
|
hand: "Hand"
|
|
1850
2017
|
};
|
|
1851
|
-
function parseAnchor(anchor) {
|
|
1852
|
-
const parts = anchor.split("-");
|
|
1853
|
-
let vertical = "center";
|
|
1854
|
-
let horizontal = "center";
|
|
1855
|
-
for (const part of parts) {
|
|
1856
|
-
if (part === "top" || part === "bottom") vertical = part;
|
|
1857
|
-
else if (part === "left" || part === "right") horizontal = part;
|
|
1858
|
-
}
|
|
1859
|
-
return { vertical, horizontal };
|
|
1860
|
-
}
|
|
1861
2018
|
function isToolbarAction(item) {
|
|
1862
2019
|
return item === "undo" || item === "redo";
|
|
1863
2020
|
}
|
|
1864
|
-
function resolvePlacementStyle(placement, fallbackAnchor, fallbackOffsetX, fallbackOffsetY) {
|
|
1865
|
-
const anchor = placement?.anchor ?? fallbackAnchor;
|
|
1866
|
-
const offsetX = placement?.offsetX ?? fallbackOffsetX;
|
|
1867
|
-
const offsetY = placement?.offsetY ?? fallbackOffsetY;
|
|
1868
|
-
const { vertical, horizontal } = parseAnchor(anchor);
|
|
1869
|
-
const result = {};
|
|
1870
|
-
const transforms = [];
|
|
1871
|
-
if (horizontal === "left") {
|
|
1872
|
-
result.left = offsetX;
|
|
1873
|
-
} else if (horizontal === "right") {
|
|
1874
|
-
result.right = offsetX;
|
|
1875
|
-
} else {
|
|
1876
|
-
result.left = "50%";
|
|
1877
|
-
transforms.push("translateX(-50%)");
|
|
1878
|
-
if (offsetX) transforms.push(`translateX(${offsetX}px)`);
|
|
1879
|
-
}
|
|
1880
|
-
if (vertical === "top") {
|
|
1881
|
-
result.top = offsetY;
|
|
1882
|
-
} else if (vertical === "bottom") {
|
|
1883
|
-
result.bottom = offsetY;
|
|
1884
|
-
} else {
|
|
1885
|
-
result.top = "50%";
|
|
1886
|
-
transforms.push("translateY(-50%)");
|
|
1887
|
-
if (offsetY) transforms.push(`translateY(${offsetY}px)`);
|
|
1888
|
-
}
|
|
1889
|
-
if (transforms.length > 0) result.transform = transforms.join(" ");
|
|
1890
|
-
return placement?.style ? { ...result, ...placement.style } : result;
|
|
1891
|
-
}
|
|
1892
2021
|
function Tsdraw(props) {
|
|
1893
2022
|
const [systemTheme, setSystemTheme] = useState(() => {
|
|
1894
2023
|
if (typeof window === "undefined") return "light";
|
|
@@ -1896,6 +2025,20 @@ function Tsdraw(props) {
|
|
|
1896
2025
|
});
|
|
1897
2026
|
const customTools = props.customTools ?? EMPTY_CUSTOM_TOOLS;
|
|
1898
2027
|
const toolbarPartIds = props.uiOptions?.toolbar?.parts ?? DEFAULT_TOOLBAR_PARTS;
|
|
2028
|
+
const toolbarPlacement = props.uiOptions?.toolbar?.placement;
|
|
2029
|
+
const toolbarEdgeOffset = toolbarPlacement?.edgeOffset ?? 14;
|
|
2030
|
+
const isToolbarDraggable = props.uiOptions?.toolbar?.draggable === true;
|
|
2031
|
+
const shouldSaveDraggedToolbarPosition = props.uiOptions?.toolbar?.saveDraggedPosition === true;
|
|
2032
|
+
const disabledDragPositionsArray = props.uiOptions?.toolbar?.disabledDragPositions;
|
|
2033
|
+
const disabledDragPositionsSet = useMemo(
|
|
2034
|
+
() => disabledDragPositionsArray && disabledDragPositionsArray.length > 0 ? new Set(disabledDragPositionsArray) : void 0,
|
|
2035
|
+
[disabledDragPositionsArray]
|
|
2036
|
+
);
|
|
2037
|
+
const toolbarDraggedSessionKey = useMemo(
|
|
2038
|
+
() => `tsdraw-toolbar-pos-${props.persistenceKey ?? "default"}`,
|
|
2039
|
+
[props.persistenceKey]
|
|
2040
|
+
);
|
|
2041
|
+
const [draggedToolbarPosition, setDraggedToolbarPosition] = useState(null);
|
|
1899
2042
|
const customToolMap = useMemo(
|
|
1900
2043
|
() => new Map(customTools.map((customTool) => [customTool.id, customTool])),
|
|
1901
2044
|
[customTools]
|
|
@@ -1980,9 +2123,35 @@ function Tsdraw(props) {
|
|
|
1980
2123
|
onCameraChange: props.onCameraChange,
|
|
1981
2124
|
onToolChange: props.onToolChange
|
|
1982
2125
|
});
|
|
1983
|
-
const
|
|
1984
|
-
const
|
|
2126
|
+
const toolbarPlacementAnchor = draggedToolbarPosition?.anchor ?? toolbarPlacement?.anchor ?? "bottom-center";
|
|
2127
|
+
const effectiveToolbarPlacement = draggedToolbarPosition ? { anchor: draggedToolbarPosition.anchor, edgeOffset: toolbarEdgeOffset, style: toolbarPlacement?.style } : toolbarPlacement;
|
|
2128
|
+
const toolbarPlacementStyle = resolvePlacementStyle(effectiveToolbarPlacement, "bottom-center", 14);
|
|
2129
|
+
const toolbarOrientation = resolveOrientation(toolbarPlacementAnchor);
|
|
2130
|
+
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8);
|
|
1985
2131
|
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
2132
|
+
useEffect(() => {
|
|
2133
|
+
if (!isToolbarDraggable || !shouldSaveDraggedToolbarPosition || typeof window === "undefined") return;
|
|
2134
|
+
try {
|
|
2135
|
+
const rawPosition = window.sessionStorage.getItem(toolbarDraggedSessionKey);
|
|
2136
|
+
if (!rawPosition) return;
|
|
2137
|
+
const parsedPosition = JSON.parse(rawPosition);
|
|
2138
|
+
if (typeof parsedPosition.anchor !== "string") return;
|
|
2139
|
+
setDraggedToolbarPosition({ anchor: parsedPosition.anchor });
|
|
2140
|
+
} catch {
|
|
2141
|
+
}
|
|
2142
|
+
}, [isToolbarDraggable, shouldSaveDraggedToolbarPosition, toolbarDraggedSessionKey]);
|
|
2143
|
+
useEffect(() => {
|
|
2144
|
+
if (isToolbarDraggable) return;
|
|
2145
|
+
setDraggedToolbarPosition(null);
|
|
2146
|
+
}, [isToolbarDraggable]);
|
|
2147
|
+
const handleToolbarDragEnd = useCallback((payload) => {
|
|
2148
|
+
const containerNode = containerRef.current;
|
|
2149
|
+
if (!containerNode) return;
|
|
2150
|
+
const nextPosition = calculateSnap(payload, containerNode.getBoundingClientRect(), disabledDragPositionsSet);
|
|
2151
|
+
setDraggedToolbarPosition(nextPosition);
|
|
2152
|
+
if (!shouldSaveDraggedToolbarPosition || typeof window === "undefined") return;
|
|
2153
|
+
window.sessionStorage.setItem(toolbarDraggedSessionKey, JSON.stringify(nextPosition));
|
|
2154
|
+
}, [containerRef, disabledDragPositionsSet, shouldSaveDraggedToolbarPosition, toolbarDraggedSessionKey]);
|
|
1986
2155
|
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true || props.readOnly === true;
|
|
1987
2156
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1988
2157
|
const defaultToolOverlay = /* @__PURE__ */ jsx(
|
|
@@ -2135,7 +2304,7 @@ function Tsdraw(props) {
|
|
|
2135
2304
|
position: "absolute",
|
|
2136
2305
|
zIndex: 130,
|
|
2137
2306
|
pointerEvents: "all",
|
|
2138
|
-
...resolvePlacementStyle(customElement.placement, "top-left", 8
|
|
2307
|
+
...resolvePlacementStyle(customElement.placement, "top-left", 8)
|
|
2139
2308
|
},
|
|
2140
2309
|
children: customElement.render({ currentTool, setTool, applyDrawStyle })
|
|
2141
2310
|
},
|
|
@@ -2146,9 +2315,12 @@ function Tsdraw(props) {
|
|
|
2146
2315
|
{
|
|
2147
2316
|
parts: toolbarParts,
|
|
2148
2317
|
style: toolbarPlacementStyle,
|
|
2318
|
+
orientation: toolbarOrientation,
|
|
2149
2319
|
currentTool: isPersistenceReady ? currentTool : null,
|
|
2150
2320
|
onToolChange: setTool,
|
|
2151
|
-
disabled: !isPersistenceReady
|
|
2321
|
+
disabled: !isPersistenceReady,
|
|
2322
|
+
draggable: isToolbarDraggable,
|
|
2323
|
+
onDragEnd: handleToolbarDragEnd
|
|
2152
2324
|
}
|
|
2153
2325
|
) : null
|
|
2154
2326
|
]
|