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