@tsdraw/react 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +212 -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 +213 -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(
|
|
@@ -1848,47 +2010,9 @@ var DEFAULT_TOOL_LABELS = {
|
|
|
1848
2010
|
eraser: "Eraser",
|
|
1849
2011
|
hand: "Hand"
|
|
1850
2012
|
};
|
|
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
2013
|
function isToolbarAction(item) {
|
|
1862
2014
|
return item === "undo" || item === "redo";
|
|
1863
2015
|
}
|
|
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
2016
|
function Tsdraw(props) {
|
|
1893
2017
|
const [systemTheme, setSystemTheme] = useState(() => {
|
|
1894
2018
|
if (typeof window === "undefined") return "light";
|
|
@@ -1896,6 +2020,20 @@ function Tsdraw(props) {
|
|
|
1896
2020
|
});
|
|
1897
2021
|
const customTools = props.customTools ?? EMPTY_CUSTOM_TOOLS;
|
|
1898
2022
|
const toolbarPartIds = props.uiOptions?.toolbar?.parts ?? DEFAULT_TOOLBAR_PARTS;
|
|
2023
|
+
const toolbarPlacement = props.uiOptions?.toolbar?.placement;
|
|
2024
|
+
const toolbarEdgeOffset = toolbarPlacement?.edgeOffset ?? 14;
|
|
2025
|
+
const isToolbarDraggable = props.uiOptions?.toolbar?.draggable === true;
|
|
2026
|
+
const shouldSaveDraggedToolbarPosition = props.uiOptions?.toolbar?.saveDraggedPosition === true;
|
|
2027
|
+
const disabledDragPositionsArray = props.uiOptions?.toolbar?.disabledDragPositions;
|
|
2028
|
+
const disabledDragPositionsSet = useMemo(
|
|
2029
|
+
() => disabledDragPositionsArray && disabledDragPositionsArray.length > 0 ? new Set(disabledDragPositionsArray) : void 0,
|
|
2030
|
+
[disabledDragPositionsArray]
|
|
2031
|
+
);
|
|
2032
|
+
const toolbarDraggedSessionKey = useMemo(
|
|
2033
|
+
() => `tsdraw-toolbar-pos-${props.persistenceKey ?? "default"}`,
|
|
2034
|
+
[props.persistenceKey]
|
|
2035
|
+
);
|
|
2036
|
+
const [draggedToolbarPosition, setDraggedToolbarPosition] = useState(null);
|
|
1899
2037
|
const customToolMap = useMemo(
|
|
1900
2038
|
() => new Map(customTools.map((customTool) => [customTool.id, customTool])),
|
|
1901
2039
|
[customTools]
|
|
@@ -1980,9 +2118,35 @@ function Tsdraw(props) {
|
|
|
1980
2118
|
onCameraChange: props.onCameraChange,
|
|
1981
2119
|
onToolChange: props.onToolChange
|
|
1982
2120
|
});
|
|
1983
|
-
const
|
|
1984
|
-
const
|
|
2121
|
+
const toolbarPlacementAnchor = draggedToolbarPosition?.anchor ?? toolbarPlacement?.anchor ?? "bottom-center";
|
|
2122
|
+
const effectiveToolbarPlacement = draggedToolbarPosition ? { anchor: draggedToolbarPosition.anchor, edgeOffset: toolbarEdgeOffset, style: toolbarPlacement?.style } : toolbarPlacement;
|
|
2123
|
+
const toolbarPlacementStyle = resolvePlacementStyle(effectiveToolbarPlacement, "bottom-center", 14);
|
|
2124
|
+
const toolbarOrientation = resolveOrientation(toolbarPlacementAnchor);
|
|
2125
|
+
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8);
|
|
1985
2126
|
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
2127
|
+
useEffect(() => {
|
|
2128
|
+
if (!isToolbarDraggable || !shouldSaveDraggedToolbarPosition || typeof window === "undefined") return;
|
|
2129
|
+
try {
|
|
2130
|
+
const rawPosition = window.sessionStorage.getItem(toolbarDraggedSessionKey);
|
|
2131
|
+
if (!rawPosition) return;
|
|
2132
|
+
const parsedPosition = JSON.parse(rawPosition);
|
|
2133
|
+
if (typeof parsedPosition.anchor !== "string") return;
|
|
2134
|
+
setDraggedToolbarPosition({ anchor: parsedPosition.anchor });
|
|
2135
|
+
} catch {
|
|
2136
|
+
}
|
|
2137
|
+
}, [isToolbarDraggable, shouldSaveDraggedToolbarPosition, toolbarDraggedSessionKey]);
|
|
2138
|
+
useEffect(() => {
|
|
2139
|
+
if (isToolbarDraggable) return;
|
|
2140
|
+
setDraggedToolbarPosition(null);
|
|
2141
|
+
}, [isToolbarDraggable]);
|
|
2142
|
+
const handleToolbarDragEnd = useCallback((payload) => {
|
|
2143
|
+
const containerNode = containerRef.current;
|
|
2144
|
+
if (!containerNode) return;
|
|
2145
|
+
const nextPosition = calculateSnap(payload, containerNode.getBoundingClientRect(), disabledDragPositionsSet);
|
|
2146
|
+
setDraggedToolbarPosition(nextPosition);
|
|
2147
|
+
if (!shouldSaveDraggedToolbarPosition || typeof window === "undefined") return;
|
|
2148
|
+
window.sessionStorage.setItem(toolbarDraggedSessionKey, JSON.stringify(nextPosition));
|
|
2149
|
+
}, [containerRef, disabledDragPositionsSet, shouldSaveDraggedToolbarPosition, toolbarDraggedSessionKey]);
|
|
1986
2150
|
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true || props.readOnly === true;
|
|
1987
2151
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1988
2152
|
const defaultToolOverlay = /* @__PURE__ */ jsx(
|
|
@@ -2135,7 +2299,7 @@ function Tsdraw(props) {
|
|
|
2135
2299
|
position: "absolute",
|
|
2136
2300
|
zIndex: 130,
|
|
2137
2301
|
pointerEvents: "all",
|
|
2138
|
-
...resolvePlacementStyle(customElement.placement, "top-left", 8
|
|
2302
|
+
...resolvePlacementStyle(customElement.placement, "top-left", 8)
|
|
2139
2303
|
},
|
|
2140
2304
|
children: customElement.render({ currentTool, setTool, applyDrawStyle })
|
|
2141
2305
|
},
|
|
@@ -2146,9 +2310,12 @@ function Tsdraw(props) {
|
|
|
2146
2310
|
{
|
|
2147
2311
|
parts: toolbarParts,
|
|
2148
2312
|
style: toolbarPlacementStyle,
|
|
2313
|
+
orientation: toolbarOrientation,
|
|
2149
2314
|
currentTool: isPersistenceReady ? currentTool : null,
|
|
2150
2315
|
onToolChange: setTool,
|
|
2151
|
-
disabled: !isPersistenceReady
|
|
2316
|
+
disabled: !isPersistenceReady,
|
|
2317
|
+
draggable: isToolbarDraggable,
|
|
2318
|
+
onDragEnd: handleToolbarDragEnd
|
|
2152
2319
|
}
|
|
2153
2320
|
) : null
|
|
2154
2321
|
]
|