@maayan-albert/moab-sdk 1.0.5 → 1.0.6

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.
@@ -1,15 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var react = require('react');
4
+ var React = require('react');
5
5
  var lucideReact = require('lucide-react');
6
- var html2canvas = require('html2canvas');
6
+ var htmlToImage = require('html-to-image');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
- var html2canvas__default = /*#__PURE__*/_interopDefault(html2canvas);
10
+ var React__default = /*#__PURE__*/_interopDefault(React);
11
11
 
12
- // src/components/Button.tsx
13
12
  var Button = ({
14
13
  children,
15
14
  variant = "primary",
@@ -92,25 +91,77 @@ var Input = ({
92
91
  ] });
93
92
  };
94
93
  var Input_default = Input;
95
- var DrawingTool = ({
96
- width,
97
- height,
98
- className = "",
99
- ...props
100
- }) => {
101
- const canvasRef = react.useRef(null);
102
- const toolbarRef = react.useRef(null);
103
- const [isDrawing, setIsDrawing] = react.useState(false);
104
- const brushColor = "#EF4444";
94
+
95
+ // src/components/DrawingTool.module.scss
96
+ var css = '@keyframes moab__toolbarEnter___kVde9 {\n from {\n opacity: 0;\n transform: scale(0.5) rotate(90deg);\n }\n to {\n opacity: 1;\n transform: scale(1) rotate(0deg);\n }\n}\n@keyframes moab__scaleIn___bOiIm {\n from {\n opacity: 0;\n transform: scale(0.85);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n@keyframes moab__scaleOut___wrtHU {\n from {\n opacity: 1;\n transform: scale(1);\n }\n to {\n opacity: 0;\n transform: scale(0.85);\n }\n}\n.moab__toolbar___GXd-P {\n position: fixed;\n bottom: 1.25rem;\n right: 1.25rem;\n z-index: 100000;\n font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n pointer-events: none;\n transition: left 0s, top 0s, right 0s, bottom 0s;\n}\n\n.moab__toolbarContainer___sQuuF {\n user-select: none;\n margin-left: auto;\n align-self: flex-end;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #1a1a1a;\n color: #fff;\n border: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);\n pointer-events: auto;\n cursor: grab;\n transition: width 0.4s cubic-bezier(0.19, 1, 0.22, 1), transform 0.4s cubic-bezier(0.19, 1, 0.22, 1);\n}\n.moab__toolbarContainer___sQuuF.moab__dragging___c6CuY {\n transition: width 0.4s cubic-bezier(0.19, 1, 0.22, 1);\n cursor: grabbing;\n}\n.moab__toolbarContainer___sQuuF.moab__entrance___kTkKT {\n animation: moab__toolbarEnter___kVde9 0.5s cubic-bezier(0.34, 1.2, 0.64, 1) forwards;\n}\n.moab__toolbarContainer___sQuuF.moab__collapsed___xYm1N {\n width: 44px;\n height: 44px;\n border-radius: 22px;\n padding: 0;\n cursor: pointer;\n}\n.moab__toolbarContainer___sQuuF.moab__collapsed___xYm1N svg {\n margin-top: -1px;\n}\n.moab__toolbarContainer___sQuuF.moab__collapsed___xYm1N:hover {\n background: #2a2a2a;\n}\n.moab__toolbarContainer___sQuuF.moab__collapsed___xYm1N:active {\n transform: scale(0.95);\n}\n.moab__toolbarContainer___sQuuF.moab__expanded___Gshmq {\n width: calc-size(auto, size);\n height: 44px;\n border-radius: 1.5rem;\n padding: 0.375rem;\n}\n@supports not (width: calc-size(auto, size)) {\n .moab__toolbarContainer___sQuuF.moab__expanded___Gshmq {\n width: auto;\n }\n}\n\n.moab__toggleContent___KfJ0y {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: opacity 0.1s cubic-bezier(0.19, 1, 0.22, 1);\n}\n.moab__toggleContent___KfJ0y.moab__visible___1M9br {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n.moab__toggleContent___KfJ0y.moab__hidden___Zdoqy {\n opacity: 0;\n pointer-events: none;\n}\n\n.moab__controlsContent___KGEHS {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n transition: filter 0.8s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.8s cubic-bezier(0.19, 1, 0.22, 1), transform 0.6s cubic-bezier(0.19, 1, 0.22, 1);\n}\n.moab__controlsContent___KGEHS.moab__visible___1M9br {\n opacity: 1;\n filter: blur(0px);\n transform: scale(1);\n visibility: visible;\n pointer-events: auto;\n}\n.moab__controlsContent___KGEHS.moab__hidden___Zdoqy {\n opacity: 0;\n filter: blur(10px);\n transform: scale(0.4);\n pointer-events: none;\n}\n\n.moab__buttonWrapper___ZDZWf {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.moab__controlButton___AYQcE {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n margin: 0;\n border: none;\n background: transparent;\n color: rgba(255, 255, 255, 0.5);\n border-radius: 999px;\n cursor: pointer;\n transition: background 0.2s cubic-bezier(0.25, 1, 0.5, 1), color 0.2s cubic-bezier(0.25, 1, 0.5, 1), transform 0.15s cubic-bezier(0.25, 1, 0.5, 1);\n}\n.moab__controlButton___AYQcE:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n color: rgba(255, 255, 255, 0.9);\n}\n.moab__controlButton___AYQcE[data-active=true]:hover:not(:disabled) {\n background: rgba(60, 130, 247, 0.35);\n color: #3c82f7;\n}\n.moab__controlButton___AYQcE:active:not(:disabled) {\n transform: scale(0.95);\n}\n.moab__controlButton___AYQcE:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n}\n.moab__controlButton___AYQcE[data-active=true] {\n color: #3c82f7;\n background: rgba(60, 130, 247, 0.25);\n}\n.moab__controlButton___AYQcE[data-danger]:hover:not(:disabled) {\n background: rgba(255, 59, 48, 0.15);\n color: #ff3b30;\n}\n\n.moab__buttonTooltip___ZUL58 {\n position: absolute;\n bottom: calc(100% + 0.5rem);\n left: 50%;\n transform: translateX(-50%);\n padding: 0.375rem 0.625rem;\n background: #383838;\n color: rgba(255, 255, 255, 0.7);\n font-size: 0.6875rem;\n font-weight: 400;\n line-height: 1.2;\n border-radius: 0.5rem;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n transition: opacity 0.15s ease, visibility 0.15s ease, transform 0.15s ease;\n z-index: 100001;\n box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.28);\n}\n.moab__buttonTooltip___ZUL58::after {\n content: "";\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n width: 0;\n height: 0;\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n border-top: 4px solid #383838;\n}\n\n.moab__buttonWrapper___ZDZWf:hover .moab__buttonTooltip___ZUL58 {\n opacity: 1;\n visibility: visible;\n}\n\n.moab__divider___o4mDB {\n width: 1px;\n height: 24px;\n background: rgba(255, 255, 255, 0.15);\n margin: 0 0.125rem;\n}\n\n.moab__light___s-xQ4.moab__toolbarContainer___sQuuF {\n background: #fff;\n color: rgba(0, 0, 0, 0.85);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04);\n}\n.moab__light___s-xQ4.moab__toolbarContainer___sQuuF.moab__collapsed___xYm1N:hover {\n background: #f5f5f5;\n}\n.moab__light___s-xQ4.moab__controlButton___AYQcE {\n color: rgba(0, 0, 0, 0.5);\n}\n.moab__light___s-xQ4.moab__controlButton___AYQcE:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.06);\n color: rgba(0, 0, 0, 0.85);\n}\n.moab__light___s-xQ4.moab__controlButton___AYQcE[data-active=true]:hover:not(:disabled) {\n background: rgba(60, 130, 247, 0.25);\n color: #3c82f7;\n}\n.moab__light___s-xQ4.moab__controlButton___AYQcE[data-active=true] {\n color: #3c82f7;\n background: rgba(60, 130, 247, 0.15);\n}\n.moab__light___s-xQ4.moab__controlButton___AYQcE[data-danger]:hover:not(:disabled) {\n background: rgba(255, 59, 48, 0.15);\n color: #ff3b30;\n}\n.moab__light___s-xQ4.moab__buttonTooltip___ZUL58 {\n background: #fff;\n color: rgba(0, 0, 0, 0.85);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04);\n}\n.moab__light___s-xQ4.moab__buttonTooltip___ZUL58::after {\n background: #fff;\n}\n.moab__light___s-xQ4.moab__divider___o4mDB {\n background: rgba(0, 0, 0, 0.1);\n}\n\n.moab-drawing-canvas {\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n z-index: 50 !important;\n touch-action: none !important;\n}';
97
+ var classNames = { "toolbar": "moab__toolbar___GXd-P", "toolbarContainer": "moab__toolbarContainer___sQuuF", "dragging": "moab__dragging___c6CuY", "entrance": "moab__entrance___kTkKT", "collapsed": "moab__collapsed___xYm1N", "expanded": "moab__expanded___Gshmq", "toggleContent": "moab__toggleContent___KfJ0y", "visible": "moab__visible___1M9br", "hidden": "moab__hidden___Zdoqy", "controlsContent": "moab__controlsContent___KGEHS", "buttonWrapper": "moab__buttonWrapper___ZDZWf", "controlButton": "moab__controlButton___AYQcE", "buttonTooltip": "moab__buttonTooltip___ZUL58", "divider": "moab__divider___o4mDB", "light": "moab__light___s-xQ4"};
98
+ if (typeof document !== "undefined") {
99
+ let style = document.getElementById("moab-components-DrawingTool");
100
+ if (!style) {
101
+ style = document.createElement("style");
102
+ style.id = "moab-components-DrawingTool";
103
+ document.head.appendChild(style);
104
+ }
105
+ style.textContent = css;
106
+ }
107
+ var DrawingTool_module_default = classNames;
108
+ var TOOLBAR_POSITION_STORAGE_KEY = "moab-drawing-toolbar-position";
109
+ var DrawingTool = () => {
110
+ const canvasRef = React.useRef(null);
111
+ const toolbarRef = React.useRef(null);
112
+ const toolbarWrapperRef = React.useRef(null);
113
+ const [isDrawing, setIsDrawing] = React.useState(false);
114
+ const brushColor = "#3B82F6";
105
115
  const brushSize = 5;
106
- const [canvasSize, setCanvasSize] = react.useState({ width: 0, height: 0 });
107
- const [enabled, setEnabled] = react.useState(false);
108
- const [hasContent, setHasContent] = react.useState(false);
109
- const [isDragging, setIsDragging] = react.useState(false);
110
- const [corner, setCorner] = react.useState("bottom-right");
111
- const [dragStart, setDragStart] = react.useState({ x: 0, y: 0 });
112
- const [showClearTooltip, setShowClearTooltip] = react.useState(false);
113
- react.useEffect(() => {
116
+ const [canvasSize, setCanvasSize] = React.useState({ width: 0, height: 0 });
117
+ const [isOpen, setIsOpen] = React.useState(false);
118
+ const [isPainting, setIsPainting] = React.useState(false);
119
+ const [hasContent, setHasContent] = React.useState(false);
120
+ const [isDraggingToolbar, setIsDraggingToolbar] = React.useState(false);
121
+ const [toolbarPosition, setToolbarPosition] = React.useState(null);
122
+ const [isToolbarReady, setIsToolbarReady] = React.useState(false);
123
+ const [dragStartPos, setDragStartPos] = React.useState(null);
124
+ const [dragRotation, setDragRotation] = React.useState(0);
125
+ const justFinishedToolbarDragRef = React.useRef(false);
126
+ React.useLayoutEffect(() => {
127
+ const toolbarWrapper = toolbarWrapperRef.current;
128
+ if (!toolbarWrapper) return;
129
+ const restorePosition = () => {
130
+ try {
131
+ const stored = localStorage.getItem(TOOLBAR_POSITION_STORAGE_KEY);
132
+ if (stored) {
133
+ const parsed = JSON.parse(stored);
134
+ if (typeof parsed?.x === "number" && typeof parsed?.y === "number") {
135
+ setToolbarPosition({ x: parsed.x, y: parsed.y });
136
+ setIsToolbarReady(true);
137
+ return;
138
+ }
139
+ }
140
+ } catch {
141
+ }
142
+ const rect = toolbarWrapper.getBoundingClientRect();
143
+ const padding = 20;
144
+ const startX = padding;
145
+ const startY = Math.max(
146
+ padding,
147
+ window.innerHeight - rect.height - padding
148
+ );
149
+ setToolbarPosition({ x: startX, y: startY });
150
+ setIsToolbarReady(true);
151
+ };
152
+ restorePosition();
153
+ }, []);
154
+ React.useEffect(() => {
155
+ if (!toolbarPosition) return;
156
+ try {
157
+ localStorage.setItem(
158
+ TOOLBAR_POSITION_STORAGE_KEY,
159
+ JSON.stringify(toolbarPosition)
160
+ );
161
+ } catch {
162
+ }
163
+ }, [toolbarPosition]);
164
+ React.useEffect(() => {
114
165
  const updateSize = () => {
115
166
  setCanvasSize({
116
167
  width: window.innerWidth,
@@ -121,7 +172,7 @@ var DrawingTool = ({
121
172
  window.addEventListener("resize", updateSize);
122
173
  return () => window.removeEventListener("resize", updateSize);
123
174
  }, []);
124
- react.useEffect(() => {
175
+ React.useEffect(() => {
125
176
  const canvas = canvasRef.current;
126
177
  if (!canvas || canvasSize.width === 0 || canvasSize.height === 0) return;
127
178
  const ctx = canvas.getContext("2d");
@@ -134,7 +185,7 @@ var DrawingTool = ({
134
185
  ctx.lineJoin = "round";
135
186
  setHasContent(false);
136
187
  }, [canvasSize, brushColor, brushSize]);
137
- const getCoordinates = react.useCallback(
188
+ const getCoordinates = React.useCallback(
138
189
  (e) => {
139
190
  const canvas = canvasRef.current;
140
191
  if (!canvas) return { x: 0, y: 0 };
@@ -155,9 +206,9 @@ var DrawingTool = ({
155
206
  },
156
207
  []
157
208
  );
158
- const startDrawing = react.useCallback(
209
+ const startDrawing = React.useCallback(
159
210
  (e) => {
160
- if (!enabled) return;
211
+ if (!isPainting) return;
161
212
  e.preventDefault();
162
213
  const canvas = canvasRef.current;
163
214
  const ctx = canvas?.getContext("2d");
@@ -167,9 +218,9 @@ var DrawingTool = ({
167
218
  ctx.moveTo(x, y);
168
219
  setIsDrawing(true);
169
220
  },
170
- [enabled, getCoordinates]
221
+ [isPainting, getCoordinates]
171
222
  );
172
- const draw = react.useCallback(
223
+ const draw = React.useCallback(
173
224
  (e) => {
174
225
  if (!isDrawing) return;
175
226
  e.preventDefault();
@@ -183,140 +234,175 @@ var DrawingTool = ({
183
234
  },
184
235
  [isDrawing, getCoordinates]
185
236
  );
186
- const stopDrawing = react.useCallback(() => {
237
+ const stopDrawing = React.useCallback(() => {
187
238
  setIsDrawing(false);
188
239
  }, []);
189
- const clearCanvas = react.useCallback(() => {
240
+ const clearCanvas = React.useCallback(() => {
190
241
  const canvas = canvasRef.current;
191
242
  const ctx = canvas?.getContext("2d");
192
243
  if (!canvas || !ctx) return;
193
244
  ctx.clearRect(0, 0, canvas.width, canvas.height);
194
245
  setHasContent(false);
195
246
  }, []);
196
- const takeScreenshot = react.useCallback(async () => {
247
+ const takeScreenshot = React.useCallback(async () => {
248
+ const toolbar = toolbarRef.current;
197
249
  try {
198
- const toolbar = toolbarRef.current;
199
250
  if (toolbar) {
200
- toolbar.style.opacity = "0";
251
+ toolbar.style.visibility = "hidden";
201
252
  }
202
- const canvas = await html2canvas__default.default(document.body, {
203
- useCORS: true,
204
- logging: false,
205
- windowWidth: window.innerWidth,
206
- windowHeight: window.innerHeight
253
+ await new Promise((resolve) => setTimeout(resolve, 50));
254
+ const dataUrl = await htmlToImage.toPng(document.body, {
255
+ width: window.innerWidth,
256
+ height: window.innerHeight,
257
+ style: {
258
+ transform: `translate(-${window.scrollX}px, -${window.scrollY}px)`
259
+ },
260
+ filter: (node) => {
261
+ if (node instanceof HTMLElement) {
262
+ if (node.closest?.("[data-drawing-toolbar]")) {
263
+ return false;
264
+ }
265
+ }
266
+ return true;
267
+ }
207
268
  });
208
269
  if (toolbar) {
209
- toolbar.style.opacity = "";
270
+ toolbar.style.visibility = "";
271
+ }
272
+ const response = await fetch(dataUrl);
273
+ const blob = await response.blob();
274
+ try {
275
+ await navigator.clipboard.write([
276
+ new ClipboardItem({
277
+ "image/png": blob
278
+ })
279
+ ]);
280
+ } catch (err) {
281
+ console.error("Failed to copy to clipboard:", err);
282
+ const url = URL.createObjectURL(blob);
283
+ const a = document.createElement("a");
284
+ a.href = url;
285
+ a.download = "screenshot.png";
286
+ a.click();
287
+ URL.revokeObjectURL(url);
210
288
  }
211
- canvas.toBlob(async (blob) => {
212
- if (!blob) return;
213
- try {
214
- await navigator.clipboard.write([
215
- new ClipboardItem({
216
- "image/png": blob
217
- })
218
- ]);
219
- } catch (err) {
220
- console.error("Failed to copy to clipboard:", err);
221
- const url = URL.createObjectURL(blob);
222
- const a = document.createElement("a");
223
- a.href = url;
224
- a.download = "screenshot.png";
225
- a.click();
226
- URL.revokeObjectURL(url);
227
- }
228
- }, "image/png");
229
289
  } catch (error) {
230
290
  console.error("Failed to take screenshot:", error);
291
+ if (toolbar) {
292
+ toolbar.style.visibility = "";
293
+ }
231
294
  }
232
295
  }, []);
233
- react.useEffect(() => {
296
+ React.useEffect(() => {
234
297
  const canvas = canvasRef.current;
235
298
  const ctx = canvas?.getContext("2d");
236
299
  if (!ctx) return;
237
300
  ctx.strokeStyle = brushColor;
238
301
  ctx.lineWidth = brushSize;
239
302
  }, [brushColor, brushSize]);
240
- const getTargetCorner = react.useCallback(
241
- (startX, startY, currentX, currentY) => {
242
- const deltaX = currentX - startX;
243
- const deltaY = currentY - startY;
244
- const threshold = 50;
245
- if (Math.abs(deltaX) < threshold && Math.abs(deltaY) < threshold) {
246
- return null;
303
+ const handleToolbarMouseDown = React.useCallback(
304
+ (e) => {
305
+ if (e.target.closest("button")) {
306
+ return;
247
307
  }
248
- const viewportWidth = window.innerWidth;
249
- const viewportHeight = window.innerHeight;
250
- const horizontal = currentX < viewportWidth / 2 ? "left" : "right";
251
- const vertical = currentY < viewportHeight / 2 ? "top" : "bottom";
252
- if (vertical === "top" && horizontal === "left") return "top-left";
253
- if (vertical === "top" && horizontal === "right") return "top-right";
254
- if (vertical === "bottom" && horizontal === "left") return "bottom-left";
255
- if (vertical === "bottom" && horizontal === "right")
256
- return "bottom-right";
257
- return null;
308
+ const toolbarWrapper = toolbarWrapperRef.current;
309
+ if (!toolbarWrapper) return;
310
+ e.preventDefault();
311
+ e.stopPropagation();
312
+ const rect = toolbarWrapper.getBoundingClientRect();
313
+ const randomRotation = (Math.random() - 0.5) * 10;
314
+ setDragRotation(randomRotation);
315
+ setDragStartPos({
316
+ x: e.clientX,
317
+ y: e.clientY,
318
+ toolbarX: toolbarPosition?.x ?? rect.left,
319
+ toolbarY: toolbarPosition?.y ?? rect.top,
320
+ toolbarWidth: rect.width,
321
+ toolbarHeight: rect.height
322
+ });
258
323
  },
259
- []
324
+ [toolbarPosition]
260
325
  );
261
- const handleMouseDown = react.useCallback((e) => {
262
- if (e.target.closest("button")) {
263
- return;
264
- }
265
- e.preventDefault();
266
- e.stopPropagation();
267
- setIsDragging(true);
268
- setDragStart({
269
- x: e.clientX,
270
- y: e.clientY
271
- });
272
- }, []);
273
- react.useEffect(() => {
326
+ React.useEffect(() => {
327
+ if (!dragStartPos) return;
328
+ const DRAG_THRESHOLD = 5;
274
329
  const handleMouseMove = (e) => {
275
- if (!isDragging) return;
276
- e.preventDefault();
277
- e.stopPropagation();
278
- const targetCorner = getTargetCorner(
279
- dragStart.x,
280
- dragStart.y,
281
- e.clientX,
282
- e.clientY
283
- );
284
- if (targetCorner) {
285
- setCorner(targetCorner);
286
- } else {
287
- const viewportWidth = window.innerWidth;
288
- const viewportHeight = window.innerHeight;
289
- const horizontal = e.clientX < viewportWidth / 2 ? "left" : "right";
290
- const vertical = e.clientY < viewportHeight / 2 ? "top" : "bottom";
291
- if (vertical === "top" && horizontal === "left") setCorner("top-left");
292
- else if (vertical === "top" && horizontal === "right")
293
- setCorner("top-right");
294
- else if (vertical === "bottom" && horizontal === "left")
295
- setCorner("bottom-left");
296
- else setCorner("bottom-right");
330
+ const deltaX = e.clientX - dragStartPos.x;
331
+ const deltaY = e.clientY - dragStartPos.y;
332
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
333
+ if (!isDraggingToolbar && distance > DRAG_THRESHOLD) {
334
+ setIsDraggingToolbar(true);
335
+ }
336
+ if (isDraggingToolbar || distance > DRAG_THRESHOLD) {
337
+ const padding = 20;
338
+ const maxX = window.innerWidth - dragStartPos.toolbarWidth - padding;
339
+ const maxY = window.innerHeight - dragStartPos.toolbarHeight - padding;
340
+ const newX = Math.max(
341
+ padding,
342
+ Math.min(maxX, dragStartPos.toolbarX + deltaX)
343
+ );
344
+ const newY = Math.max(
345
+ padding,
346
+ Math.min(maxY, dragStartPos.toolbarY + deltaY)
347
+ );
348
+ setToolbarPosition({ x: newX, y: newY });
297
349
  }
298
350
  };
299
- const handleMouseUp = (e) => {
300
- e.preventDefault();
301
- e.stopPropagation();
302
- setIsDragging(false);
351
+ const handleMouseUp = () => {
352
+ if (isDraggingToolbar) {
353
+ justFinishedToolbarDragRef.current = true;
354
+ setTimeout(() => {
355
+ justFinishedToolbarDragRef.current = false;
356
+ }, 50);
357
+ }
358
+ setIsDraggingToolbar(false);
359
+ setDragStartPos(null);
303
360
  };
304
- if (isDragging) {
305
- document.addEventListener("mousemove", handleMouseMove, {
306
- passive: false
307
- });
308
- document.addEventListener("mouseup", handleMouseUp, { passive: false });
309
- }
361
+ document.addEventListener("mousemove", handleMouseMove);
362
+ document.addEventListener("mouseup", handleMouseUp);
310
363
  return () => {
311
364
  document.removeEventListener("mousemove", handleMouseMove);
312
365
  document.removeEventListener("mouseup", handleMouseUp);
313
366
  };
314
- }, [isDragging, dragStart, getTargetCorner]);
315
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "contents" }, children: [
367
+ }, [dragStartPos, isDraggingToolbar]);
368
+ React.useEffect(() => {
369
+ if (!toolbarPosition) return;
370
+ const constrainPosition = () => {
371
+ const toolbarWrapper = toolbarWrapperRef.current;
372
+ const rect = toolbarWrapper?.getBoundingClientRect();
373
+ const width = rect?.width ?? 44;
374
+ const height = rect?.height ?? 44;
375
+ const padding = 20;
376
+ const maxX = window.innerWidth - width - padding;
377
+ const maxY = window.innerHeight - height - padding;
378
+ const newX = Math.max(padding, Math.min(maxX, toolbarPosition.x));
379
+ const newY = Math.max(padding, Math.min(maxY, toolbarPosition.y));
380
+ if (newX !== toolbarPosition.x || newY !== toolbarPosition.y) {
381
+ setToolbarPosition({ x: newX, y: newY });
382
+ }
383
+ };
384
+ constrainPosition();
385
+ window.addEventListener("resize", constrainPosition);
386
+ return () => window.removeEventListener("resize", constrainPosition);
387
+ }, [toolbarPosition, isOpen]);
388
+ const [showEntranceAnimation, setShowEntranceAnimation] = React.useState(false);
389
+ const [isDarkMode] = React.useState(true);
390
+ React.useEffect(() => {
391
+ setShowEntranceAnimation(true);
392
+ const timer = setTimeout(() => setShowEntranceAnimation(false), 500);
393
+ return () => clearTimeout(timer);
394
+ }, []);
395
+ React.useEffect(() => {
396
+ if (!isPainting) {
397
+ setIsDrawing(false);
398
+ }
399
+ }, [isPainting]);
400
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__default.default.Fragment, { children: [
316
401
  /* @__PURE__ */ jsxRuntime.jsx(
317
402
  "canvas",
318
403
  {
319
404
  ref: canvasRef,
405
+ className: "moab-drawing-canvas",
320
406
  onMouseDown: startDrawing,
321
407
  onMouseMove: draw,
322
408
  onMouseUp: stopDrawing,
@@ -324,186 +410,150 @@ var DrawingTool = ({
324
410
  onTouchStart: startDrawing,
325
411
  onTouchMove: draw,
326
412
  onTouchEnd: stopDrawing,
327
- className: `fixed top-0 left-0 touch-none z-50 transition-opacity ${enabled && !isDragging ? "cursor-crosshair pointer-events-auto opacity-100" : "pointer-events-none opacity-0"}`,
328
413
  style: {
329
414
  width: `${canvasSize.width}px`,
330
- height: `${canvasSize.height}px`
415
+ height: `${canvasSize.height}px`,
416
+ cursor: isPainting && !isDraggingToolbar ? "crosshair" : "default",
417
+ pointerEvents: isPainting && !isDraggingToolbar ? "auto" : "none",
418
+ opacity: isPainting && !isDraggingToolbar ? 1 : 0,
419
+ transition: "opacity 300ms ease-in-out"
331
420
  }
332
421
  }
333
422
  ),
334
- /* @__PURE__ */ jsxRuntime.jsxs(
423
+ /* @__PURE__ */ jsxRuntime.jsx(
335
424
  "div",
336
425
  {
337
- ref: toolbarRef,
338
- className: `fixed flex flex-row items-center px-2 py-1.5 bg-neutral-800 rounded-full shadow-lg z-[60] ${isDragging ? "cursor-grabbing" : "cursor-grab"} ${className}`,
339
- style: {
340
- gap: enabled ? "8px" : "0px",
341
- transition: "gap 300ms ease-in-out",
342
- ...corner === "top-left" ? { top: "16px", left: "16px", bottom: "auto", right: "auto" } : corner === "top-right" ? { top: "16px", right: "16px", bottom: "auto", left: "auto" } : corner === "bottom-left" ? { bottom: "16px", left: "16px", top: "auto", right: "auto" } : { bottom: "16px", right: "16px", top: "auto", left: "auto" }
343
- },
344
- onMouseDown: handleMouseDown,
345
- ...props,
346
- onClick: (e) => e.stopPropagation(),
347
- children: [
348
- /* @__PURE__ */ jsxRuntime.jsx(
349
- "div",
350
- {
351
- className: "overflow-hidden flex-shrink-0",
352
- style: {
353
- maxWidth: enabled ? "100px" : "0px",
354
- opacity: enabled ? 1 : 0,
355
- transition: "max-width 300ms ease-in-out, opacity 300ms ease-in-out"
356
- },
357
- children: /* @__PURE__ */ jsxRuntime.jsx(
358
- "button",
426
+ ref: toolbarWrapperRef,
427
+ className: DrawingTool_module_default.toolbar,
428
+ "data-drawing-toolbar": true,
429
+ style: toolbarPosition ? {
430
+ left: toolbarPosition.x,
431
+ top: toolbarPosition.y,
432
+ right: "auto",
433
+ bottom: "auto",
434
+ visibility: isToolbarReady ? "visible" : "hidden"
435
+ } : { visibility: "hidden" },
436
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
437
+ "div",
438
+ {
439
+ ref: toolbarRef,
440
+ className: `${DrawingTool_module_default.toolbarContainer} ${isOpen ? DrawingTool_module_default.expanded : DrawingTool_module_default.collapsed} ${showEntranceAnimation ? DrawingTool_module_default.entrance : ""} ${isDraggingToolbar ? DrawingTool_module_default.dragging : ""} ${!isDarkMode ? DrawingTool_module_default.light : ""}`,
441
+ onClick: !isOpen ? (e) => {
442
+ if (justFinishedToolbarDragRef.current) {
443
+ e.preventDefault();
444
+ return;
445
+ }
446
+ setIsOpen(true);
447
+ } : void 0,
448
+ onMouseDown: handleToolbarMouseDown,
449
+ role: !isOpen ? "button" : void 0,
450
+ tabIndex: !isOpen ? 0 : -1,
451
+ style: isDraggingToolbar ? {
452
+ cursor: "grabbing",
453
+ transform: `scale(1.05) rotate(${dragRotation}deg)`
454
+ } : void 0,
455
+ children: [
456
+ /* @__PURE__ */ jsxRuntime.jsx(
457
+ "div",
359
458
  {
360
- onClick: (e) => {
361
- e.stopPropagation();
362
- takeScreenshot();
363
- },
364
- className: "p-2 text-white hover:bg-neutral-600/30 rounded-full transition-colors focus:outline-none whitespace-nowrap",
365
- "aria-label": "Take screenshot",
366
- children: /* @__PURE__ */ jsxRuntime.jsxs(
367
- "svg",
368
- {
369
- xmlns: "http://www.w3.org/2000/svg",
370
- width: "18",
371
- height: "18",
372
- viewBox: "0 0 24 24",
373
- fill: "none",
374
- stroke: "currentColor",
375
- strokeWidth: "2",
376
- strokeLinecap: "round",
377
- strokeLinejoin: "round",
378
- className: "lucide lucide-camera",
379
- children: [
380
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z" }),
381
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "13", r: "3" })
382
- ]
383
- }
384
- )
459
+ className: `${DrawingTool_module_default.toggleContent} ${!isOpen ? DrawingTool_module_default.visible : DrawingTool_module_default.hidden}`,
460
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { size: 20, strokeWidth: 2 })
385
461
  }
386
- )
387
- }
388
- ),
389
- /* @__PURE__ */ jsxRuntime.jsxs(
390
- "div",
391
- {
392
- className: "overflow-hidden flex-shrink-0 relative",
393
- style: {
394
- maxWidth: enabled ? "100px" : "0px",
395
- opacity: enabled ? 1 : 0,
396
- transition: "max-width 300ms ease-in-out, opacity 300ms ease-in-out"
397
- },
398
- children: [
399
- /* @__PURE__ */ jsxRuntime.jsxs(
400
- "button",
401
- {
402
- onClick: (e) => {
403
- e.stopPropagation();
404
- clearCanvas();
405
- setShowClearTooltip(false);
406
- },
407
- onMouseEnter: () => {
408
- if (hasContent) {
409
- setShowClearTooltip(true);
410
- }
411
- },
412
- onMouseLeave: () => setShowClearTooltip(false),
413
- disabled: !hasContent,
414
- className: "p-2 text-white rounded-full focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed relative group",
415
- "aria-label": "Clear canvas",
416
- children: [
417
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-full bg-red-600/30 opacity-0 group-hover:opacity-100 transition-opacity disabled:group-hover:opacity-0" }),
462
+ ),
463
+ /* @__PURE__ */ jsxRuntime.jsxs(
464
+ "div",
465
+ {
466
+ className: `${DrawingTool_module_default.controlsContent} ${isOpen ? DrawingTool_module_default.visible : DrawingTool_module_default.hidden}`,
467
+ children: [
468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DrawingTool_module_default.buttonWrapper, children: [
418
469
  /* @__PURE__ */ jsxRuntime.jsx(
419
- lucideReact.Trash2,
470
+ "button",
420
471
  {
421
- size: 18,
422
- strokeWidth: 2,
423
- className: "relative z-10 group-hover:text-red-600 transition-colors"
472
+ className: `${DrawingTool_module_default.controlButton} ${!isDarkMode ? DrawingTool_module_default.light : ""}`,
473
+ onClick: (e) => {
474
+ e.stopPropagation();
475
+ setIsPainting((prev) => !prev);
476
+ },
477
+ "data-active": isPainting,
478
+ "aria-label": "Toggle draw mode",
479
+ "aria-pressed": isPainting,
480
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Brush, { size: 18, strokeWidth: 2 })
424
481
  }
425
- )
426
- ]
427
- }
428
- ),
429
- showClearTooltip && hasContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-1.5 bg-neutral-800 rounded-full shadow-lg whitespace-nowrap z-[70]", children: [
430
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
431
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-sm", children: "Clear all" }),
432
- /* @__PURE__ */ jsxRuntime.jsx(
433
- "button",
434
- {
435
- onClick: (e) => {
436
- e.stopPropagation();
437
- setShowClearTooltip(false);
438
- },
439
- onMouseDown: (e) => e.stopPropagation(),
440
- className: "text-white hover:text-neutral-300 transition-colors rounded-full",
441
- "aria-label": "Dismiss tooltip",
442
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14, strokeWidth: 2 })
443
- }
444
- )
445
- ] }),
446
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 -mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-neutral-800 rotate-45" }) })
447
- ] })
448
- ]
449
- }
450
- ),
451
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-shrink-0", children: [
452
- /* @__PURE__ */ jsxRuntime.jsx(
453
- "button",
454
- {
455
- onClick: (e) => {
456
- e.stopPropagation();
457
- setEnabled(!enabled);
458
- },
459
- className: "p-1.5 text-white hover:bg-neutral-600/30 rounded-full transition-all focus:outline-none",
460
- style: {
461
- opacity: enabled ? 0 : 1,
462
- pointerEvents: enabled ? "none" : "auto",
463
- transition: "opacity 300ms ease-in-out"
464
- },
465
- "aria-label": "Turn on drawing",
466
- children: /* @__PURE__ */ jsxRuntime.jsxs(
467
- "svg",
468
- {
469
- xmlns: "http://www.w3.org/2000/svg",
470
- width: "18",
471
- height: "18",
472
- viewBox: "0 0 24 24",
473
- fill: "none",
474
- stroke: "currentColor",
475
- strokeWidth: "2",
476
- strokeLinecap: "round",
477
- strokeLinejoin: "round",
478
- className: "lucide lucide-brush",
479
- children: [
480
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m11 10 3 3" }),
481
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 21A3.5 3.5 0 1 0 3 17.5a2.62 2.62 0 0 1-.708 1.792A1 1 0 0 0 3 21z" }),
482
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.969 17.031 21.378 5.624a1 1 0 0 0-3.002-3.002L6.967 14.031" })
483
- ]
484
- }
485
- )
486
- }
487
- ),
488
- /* @__PURE__ */ jsxRuntime.jsx(
489
- "button",
490
- {
491
- onClick: (e) => {
492
- e.stopPropagation();
493
- setEnabled(false);
494
- },
495
- className: "absolute inset-0 p-1.5 text-white hover:bg-neutral-600/30 rounded-full transition-all focus:outline-none",
496
- style: {
497
- opacity: enabled ? 1 : 0,
498
- pointerEvents: enabled ? "auto" : "none",
499
- transition: "opacity 300ms ease-in-out"
500
- },
501
- "aria-label": "Close",
502
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 18, strokeWidth: 2.5 })
503
- }
504
- )
505
- ] })
506
- ]
482
+ ),
483
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: DrawingTool_module_default.buttonTooltip, children: "Draw" })
484
+ ] }),
485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DrawingTool_module_default.buttonWrapper, children: [
486
+ /* @__PURE__ */ jsxRuntime.jsx(
487
+ "button",
488
+ {
489
+ className: `${DrawingTool_module_default.controlButton} ${!isDarkMode ? DrawingTool_module_default.light : ""}`,
490
+ onClick: (e) => {
491
+ e.stopPropagation();
492
+ takeScreenshot();
493
+ },
494
+ "aria-label": "Take screenshot",
495
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
496
+ "svg",
497
+ {
498
+ xmlns: "http://www.w3.org/2000/svg",
499
+ width: "18",
500
+ height: "18",
501
+ viewBox: "0 0 24 24",
502
+ fill: "none",
503
+ stroke: "currentColor",
504
+ strokeWidth: "2",
505
+ strokeLinecap: "round",
506
+ strokeLinejoin: "round",
507
+ children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z" }),
509
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "13", r: "3" })
510
+ ]
511
+ }
512
+ )
513
+ }
514
+ ),
515
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: DrawingTool_module_default.buttonTooltip, children: "Take screenshot" })
516
+ ] }),
517
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DrawingTool_module_default.buttonWrapper, children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx(
519
+ "button",
520
+ {
521
+ className: `${DrawingTool_module_default.controlButton} ${!isDarkMode ? DrawingTool_module_default.light : ""}`,
522
+ onClick: (e) => {
523
+ e.stopPropagation();
524
+ clearCanvas();
525
+ },
526
+ disabled: !hasContent,
527
+ "data-danger": true,
528
+ "aria-label": "Clear",
529
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 18, strokeWidth: 2 })
530
+ }
531
+ ),
532
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: DrawingTool_module_default.buttonTooltip, children: "Clear" })
533
+ ] }),
534
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${DrawingTool_module_default.divider} ${!isDarkMode ? DrawingTool_module_default.light : ""}` }),
535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DrawingTool_module_default.buttonWrapper, children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx(
537
+ "button",
538
+ {
539
+ className: `${DrawingTool_module_default.controlButton} ${!isDarkMode ? DrawingTool_module_default.light : ""}`,
540
+ onClick: (e) => {
541
+ e.stopPropagation();
542
+ setIsOpen(false);
543
+ setIsPainting(false);
544
+ },
545
+ "aria-label": "Close",
546
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 18, strokeWidth: 2.5 })
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: DrawingTool_module_default.buttonTooltip, children: "Close" })
550
+ ] })
551
+ ]
552
+ }
553
+ )
554
+ ]
555
+ }
556
+ )
507
557
  }
508
558
  )
509
559
  ] });