skema-core 0.1.0 → 0.1.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.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { forwardRef, useState, useRef, useEffect, useCallback, useImperativeHandle, useMemo } from 'react';
2
- import { StateNode, atom, Box, ArrowShapeKindStyle, Tldraw, TldrawOverlays, useEditor, useTools, useIsToolSelected, useValue } from 'tldraw';
2
+ import { StateNode, atom, Box, ArrowShapeKindStyle, Tldraw, TldrawOverlays, useEditor, useTools, useIsToolSelected, useValue, GeoShapeGeoStyle } from 'tldraw';
3
3
  import 'tldraw/tldraw.css';
4
+ import { motion, AnimatePresence } from 'framer-motion';
4
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
6
 
6
7
  // src/components/Skema.tsx
@@ -173,6 +174,217 @@ var LassoSelectTool = class extends StateNode {
173
174
  LassoSelectTool.id = "lasso-select";
174
175
  LassoSelectTool.initial = "idle";
175
176
  LassoSelectTool.children = () => [IdleState, LassoingState];
177
+ function useDaemon(options = {}) {
178
+ const {
179
+ url = "ws://localhost:9999",
180
+ autoConnect = true,
181
+ autoReconnect = true,
182
+ reconnectDelay = 2e3
183
+ } = options;
184
+ const [state, setState] = useState({
185
+ connected: false,
186
+ provider: "gemini",
187
+ availableProviders: [],
188
+ cwd: ""
189
+ });
190
+ const [isGenerating, setIsGenerating] = useState(false);
191
+ const [error, setError] = useState(null);
192
+ const wsRef = useRef(null);
193
+ const pendingRequests = useRef(/* @__PURE__ */ new Map());
194
+ const eventCallbacks = useRef(/* @__PURE__ */ new Map());
195
+ const reconnectTimeoutRef = useRef(null);
196
+ const messageIdRef = useRef(0);
197
+ const nextId = useCallback(() => {
198
+ messageIdRef.current += 1;
199
+ return `msg-${messageIdRef.current}-${Date.now()}`;
200
+ }, []);
201
+ const sendRequest = useCallback((type, payload = {}) => {
202
+ return new Promise((resolve, reject) => {
203
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
204
+ reject(new Error("Not connected to daemon"));
205
+ return;
206
+ }
207
+ const id = nextId();
208
+ pendingRequests.current.set(id, { resolve, reject });
209
+ wsRef.current.send(JSON.stringify({ id, type, ...payload }));
210
+ setTimeout(() => {
211
+ if (pendingRequests.current.has(id)) {
212
+ pendingRequests.current.delete(id);
213
+ reject(new Error("Request timeout"));
214
+ }
215
+ }, 3e4);
216
+ });
217
+ }, [nextId]);
218
+ const handleMessage = useCallback((event) => {
219
+ try {
220
+ const msg = JSON.parse(event.data);
221
+ if (msg.type === "connected") {
222
+ setState((prev) => ({
223
+ ...prev,
224
+ connected: true,
225
+ provider: msg.provider || prev.provider,
226
+ availableProviders: msg.availableProviders || prev.availableProviders,
227
+ cwd: msg.cwd || prev.cwd
228
+ }));
229
+ setError(null);
230
+ return;
231
+ }
232
+ if (msg.type === "ai-event" && msg.id) {
233
+ const callback = eventCallbacks.current.get(msg.id);
234
+ if (callback) {
235
+ callback(msg.event);
236
+ }
237
+ return;
238
+ }
239
+ if (msg.type === "generate-complete" && msg.id) {
240
+ const pending = pendingRequests.current.get(msg.id);
241
+ if (pending) {
242
+ pendingRequests.current.delete(msg.id);
243
+ eventCallbacks.current.delete(msg.id);
244
+ pending.resolve({ success: msg.success, annotationId: msg.annotationId });
245
+ }
246
+ setIsGenerating(false);
247
+ return;
248
+ }
249
+ if (msg.type === "provider-changed") {
250
+ setState((prev) => ({ ...prev, provider: msg.provider }));
251
+ }
252
+ if (msg.type === "error" && msg.id) {
253
+ const pending = pendingRequests.current.get(msg.id);
254
+ if (pending) {
255
+ pendingRequests.current.delete(msg.id);
256
+ eventCallbacks.current.delete(msg.id);
257
+ pending.reject(new Error(msg.error));
258
+ }
259
+ setIsGenerating(false);
260
+ return;
261
+ }
262
+ if (msg.id) {
263
+ const pending = pendingRequests.current.get(msg.id);
264
+ if (pending) {
265
+ pendingRequests.current.delete(msg.id);
266
+ pending.resolve(msg);
267
+ }
268
+ }
269
+ } catch (e) {
270
+ console.error("[useDaemon] Failed to parse message:", e);
271
+ }
272
+ }, []);
273
+ const connect = useCallback(() => {
274
+ if (wsRef.current?.readyState === WebSocket.OPEN) return;
275
+ if (reconnectTimeoutRef.current) {
276
+ clearTimeout(reconnectTimeoutRef.current);
277
+ reconnectTimeoutRef.current = null;
278
+ }
279
+ try {
280
+ const ws = new WebSocket(url);
281
+ ws.onopen = () => {
282
+ console.log("[useDaemon] Connected to daemon");
283
+ };
284
+ ws.onmessage = handleMessage;
285
+ ws.onclose = () => {
286
+ console.log("[useDaemon] Disconnected from daemon");
287
+ setState((prev) => ({ ...prev, connected: false }));
288
+ wsRef.current = null;
289
+ if (autoReconnect) {
290
+ reconnectTimeoutRef.current = setTimeout(() => {
291
+ console.log("[useDaemon] Attempting to reconnect...");
292
+ connect();
293
+ }, reconnectDelay);
294
+ }
295
+ };
296
+ ws.onerror = (e) => {
297
+ console.error("[useDaemon] WebSocket error:", e);
298
+ setError("Failed to connect to Skema daemon. Is it running?");
299
+ };
300
+ wsRef.current = ws;
301
+ } catch (e) {
302
+ console.error("[useDaemon] Failed to create WebSocket:", e);
303
+ setError("Failed to connect to Skema daemon");
304
+ }
305
+ }, [url, autoReconnect, reconnectDelay, handleMessage]);
306
+ const disconnect = useCallback(() => {
307
+ if (reconnectTimeoutRef.current) {
308
+ clearTimeout(reconnectTimeoutRef.current);
309
+ reconnectTimeoutRef.current = null;
310
+ }
311
+ if (wsRef.current) {
312
+ wsRef.current.close();
313
+ wsRef.current = null;
314
+ }
315
+ setState((prev) => ({ ...prev, connected: false }));
316
+ }, []);
317
+ const setProvider = useCallback(async (provider) => {
318
+ try {
319
+ const response = await sendRequest("set-provider", { provider });
320
+ return response.type === "provider-changed";
321
+ } catch (e) {
322
+ console.error("[useDaemon] Failed to set provider:", e);
323
+ return false;
324
+ }
325
+ }, [sendRequest]);
326
+ const generate = useCallback(async (annotation, onEvent) => {
327
+ const id = nextId();
328
+ if (onEvent) {
329
+ eventCallbacks.current.set(id, onEvent);
330
+ }
331
+ setIsGenerating(true);
332
+ return new Promise((resolve, reject) => {
333
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
334
+ setIsGenerating(false);
335
+ reject(new Error("Not connected to daemon"));
336
+ return;
337
+ }
338
+ pendingRequests.current.set(id, { resolve, reject });
339
+ wsRef.current.send(JSON.stringify({
340
+ id,
341
+ type: "generate",
342
+ annotation
343
+ }));
344
+ });
345
+ }, [nextId]);
346
+ const revert = useCallback(async (annotationId) => {
347
+ const response = await sendRequest("revert", { annotationId });
348
+ return { success: response.success, message: response.message || "" };
349
+ }, [sendRequest]);
350
+ const readFile = useCallback(async (path) => {
351
+ const response = await sendRequest("read-file", { path });
352
+ return response.content;
353
+ }, [sendRequest]);
354
+ const writeFile = useCallback(async (path, content) => {
355
+ const response = await sendRequest("write-file", { path, content });
356
+ return response.type === "write-success";
357
+ }, [sendRequest]);
358
+ const runCommand = useCallback(async (command) => {
359
+ const response = await sendRequest("run-command", { command });
360
+ return {
361
+ stdout: response.stdout || "",
362
+ stderr: response.stderr || "",
363
+ exitCode: response.exitCode ?? 0
364
+ };
365
+ }, [sendRequest]);
366
+ useEffect(() => {
367
+ if (autoConnect) {
368
+ connect();
369
+ }
370
+ return () => {
371
+ disconnect();
372
+ };
373
+ }, [autoConnect, connect, disconnect]);
374
+ return {
375
+ state,
376
+ isGenerating,
377
+ error,
378
+ connect,
379
+ disconnect,
380
+ setProvider,
381
+ generate,
382
+ revert,
383
+ readFile,
384
+ writeFile,
385
+ runCommand
386
+ };
387
+ }
176
388
 
177
389
  // src/utils/coordinates.ts
178
390
  function getViewportInfo() {
@@ -588,349 +800,777 @@ function findNearbyElementsWithStyles(bounds, maxElements = 5) {
588
800
  }
589
801
  return diverse.map(createNearbyElement);
590
802
  }
591
- var styles = {
592
- popup: {
803
+
804
+ // src/lib/utils.ts
805
+ async function blobToBase64(blob) {
806
+ return new Promise((resolve, reject) => {
807
+ const reader = new FileReader();
808
+ reader.onload = () => {
809
+ const result = reader.result;
810
+ resolve(result);
811
+ };
812
+ reader.onerror = () => reject(reader.error);
813
+ reader.readAsDataURL(blob);
814
+ });
815
+ }
816
+ function addGridToSvg(svgString, opts = {}) {
817
+ const { color = "#0066FF", size = 100, labels = true } = opts;
818
+ const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/);
819
+ if (!viewBoxMatch) return svgString;
820
+ const [x, y, w, h] = viewBoxMatch[1].split(" ").map(Number);
821
+ const gridElements = [];
822
+ for (let i = 0; i <= Math.ceil(w / size); i++) {
823
+ const xPos = i * size;
824
+ if (i > 0) {
825
+ gridElements.push(
826
+ `<line x1="${xPos}" y1="0" x2="${xPos}" y2="${h}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
827
+ );
828
+ }
829
+ if (labels) {
830
+ const colLabel = String.fromCharCode(65 + i);
831
+ gridElements.push(
832
+ `<text x="${xPos + size / 2}" y="16" fill="${color}" font-size="12" font-family="sans-serif" text-anchor="middle">${colLabel}</text>`
833
+ );
834
+ }
835
+ }
836
+ for (let i = 0; i <= Math.ceil(h / size); i++) {
837
+ const yPos = i * size;
838
+ gridElements.push(
839
+ `<line x1="0" y1="${yPos}" x2="${w}" y2="${yPos}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
840
+ );
841
+ if (labels && i < Math.ceil(h / size)) {
842
+ gridElements.push(
843
+ `<text x="8" y="${yPos + size / 2 + 4}" fill="${color}" font-size="12" font-family="sans-serif">${i}</text>`
844
+ );
845
+ }
846
+ }
847
+ const gridGroup = `<g id="skema-grid" transform="translate(${x}, ${y})">${gridElements.join("")}</g>`;
848
+ return svgString.replace("</svg>", `${gridGroup}</svg>`);
849
+ }
850
+ function getGridCellReference(x, y, gridSize = 100) {
851
+ const col = Math.floor(x / gridSize);
852
+ const row = Math.floor(y / gridSize);
853
+ const colLabel = String.fromCharCode(65 + col);
854
+ return `${colLabel}${row}`;
855
+ }
856
+ function extractTextFromShapes(shapes) {
857
+ const textContent = [];
858
+ for (const shape of shapes) {
859
+ const s = shape;
860
+ if (s.type === "text" || s.type === "note") {
861
+ if (s.props?.text) {
862
+ textContent.push(s.props.text);
863
+ }
864
+ if (s.props?.richText && typeof s.props.richText === "object") {
865
+ const rt = s.props.richText;
866
+ if (rt.content) {
867
+ for (const block of rt.content) {
868
+ if (block.content) {
869
+ for (const inline of block.content) {
870
+ if (inline.text) {
871
+ textContent.push(inline.text);
872
+ }
873
+ }
874
+ }
875
+ }
876
+ }
877
+ }
878
+ }
879
+ }
880
+ return textContent.filter(Boolean).join("\n");
881
+ }
882
+ var SLogoIcon = ({ size = 32 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 166 161", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
883
+ /* @__PURE__ */ jsx(
884
+ "path",
885
+ {
886
+ d: "M77.0782 5.17419C80.4699 2.21068 85.5301 2.21067 88.9218 5.17419L109.929 23.529C111.161 24.6057 112.663 25.3261 114.274 25.6126L141.769 30.5023C146.225 31.2947 149.399 35.2725 149.183 39.7929L147.86 67.4755C147.782 69.1226 148.157 70.7596 148.945 72.2079L162.203 96.5644C164.372 100.55 163.236 105.527 159.551 108.175L136.957 124.416C135.623 125.375 134.577 126.681 133.932 128.193L123.03 153.753C121.26 157.902 116.692 160.1 112.346 158.894L85.4054 151.422C83.8315 150.985 82.1685 150.985 80.5946 151.422L53.6541 158.894C49.3075 160.1 44.7399 157.902 42.9702 153.753L32.0681 128.193C31.4234 126.681 30.3771 125.375 29.0427 124.416L6.4487 108.175C2.76426 105.527 1.62755 100.55 3.79688 96.5644L17.0547 72.2079C17.8431 70.7596 18.2184 69.1226 18.1397 67.4755L16.8169 39.7929C16.6009 35.2725 19.7752 31.2947 24.2308 30.5023L51.7255 25.6126C53.3366 25.3261 54.8392 24.6057 56.0714 23.529L77.0782 5.17419Z",
887
+ fill: "#FF6800"
888
+ }
889
+ ),
890
+ /* @__PURE__ */ jsx(
891
+ "path",
892
+ {
893
+ d: "M65.0888 64.1092C65.0888 68.461 73.1316 69.0511 83.5428 69.7887C102.046 71.1901 128.049 73.1078 128 97.9645C128 126.288 106.289 133 83 133C59.7599 132.926 40.4672 128.722 38.0001 97.9645H65.0888C68.0494 105.635 74.9079 107.627 83 107.627C91.0428 107.627 100.911 105.635 100.911 97.9645C100.911 93.6128 92.8684 92.9489 82.4572 92.2113C63.954 90.8099 37.9507 88.8922 38.0001 64.1092C38.0001 35.7858 59.7106 29 83 29C106.24 29.2213 125.533 33.1305 128 64.1092H100.911C97.9506 56.2908 91.0921 54.4468 83 54.4468C74.9572 54.4468 65.0888 56.217 65.0888 64.1092Z",
894
+ fill: "white"
895
+ }
896
+ )
897
+ ] });
898
+ var ChevronLeftIcon = ({ size = 24 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx(
899
+ "path",
900
+ {
901
+ d: "M15 18L9 12L15 6",
902
+ stroke: "#9CA3AF",
903
+ strokeWidth: "2.5",
904
+ strokeLinecap: "round",
905
+ strokeLinejoin: "round"
906
+ }
907
+ ) });
908
+ var ChevronRightIcon = ({ size = 24 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx(
909
+ "path",
910
+ {
911
+ d: "M9 6L15 12L9 18",
912
+ stroke: "#9CA3AF",
913
+ strokeWidth: "2.5",
914
+ strokeLinecap: "round",
915
+ strokeLinejoin: "round"
916
+ }
917
+ ) });
918
+ var SelectIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
919
+ /* @__PURE__ */ jsx(
920
+ "path",
921
+ {
922
+ d: "M11.268 3C12.0378 1.6667 13.9623 1.6667 14.7321 3L25.1244 21C25.8942 22.3333 24.9319 24 23.3923 24H2.6077C1.0681 24 0.1058 22.3333 0.8756 21L11.268 3Z",
923
+ fill: "#F24E1E",
924
+ opacity: isSelected ? 1 : 0.7
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsx(
928
+ "path",
929
+ {
930
+ d: "M9 10L9 18.5L11.5 16L14 20L15.5 19L13 15L16 14.5L9 10Z",
931
+ fill: "white"
932
+ }
933
+ )
934
+ ] });
935
+ var DrawIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
936
+ /* @__PURE__ */ jsx("rect", { width: "25", height: "25", rx: "2", fill: isSelected ? "#00C851" : "#00C851", opacity: isSelected ? 1 : 0.7 }),
937
+ /* @__PURE__ */ jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsx(
938
+ "path",
939
+ {
940
+ fillRule: "evenodd",
941
+ clipRule: "evenodd",
942
+ d: "M13.6919 10.2852L14.2593 9.6908L14.8282 10.2864L14.2605 10.8808L13.6919 10.2852ZM9.5682 15.7944L8.9992 15.1988L13.1233 10.8808L13.6919 11.476L9.5682 15.7944ZM14.3284 8.5L8 15.1988V16.5H9.5682L16 10.0436L14.3284 8.5Z",
943
+ fill: "white"
944
+ }
945
+ ) })
946
+ ] });
947
+ var LassoIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
948
+ /* @__PURE__ */ jsx("rect", { width: "25", height: "25", rx: "12.5", fill: isSelected ? "#2C7FFF" : "#2C7FFF", opacity: isSelected ? 1 : 0.7 }),
949
+ /* @__PURE__ */ jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsx(
950
+ "path",
951
+ {
952
+ fillRule: "evenodd",
953
+ clipRule: "evenodd",
954
+ d: "M9.219 11.3C9.219 10.8021 9.504 10.3117 10.043 9.9297C10.582 9.5484 11.347 9.3 12.211 9.3C13.074 9.3 13.839 9.5484 14.378 9.9297C14.918 10.3117 15.202 10.8021 15.202 11.3C15.202 11.7979 14.918 12.2882 14.378 12.6702C13.839 13.0515 13.074 13.2999 12.211 13.2999C12.005 13.2999 11.805 13.2859 11.612 13.2591C11.586 12.5417 10.887 12.0999 10.216 12.0999C9.988 12.0999 9.768 12.147 9.572 12.234C9.339 11.9444 9.219 11.625 9.219 11.3ZM12.211 14.0999C11.908 14.0999 11.614 14.074 11.331 14.0249C11.298 14.0629 11.262 14.0988 11.224 14.1325C11.226 14.154 11.228 14.1774 11.229 14.2026C11.232 14.3182 11.216 14.4769 11.137 14.6456C10.97 15.0066 10.586 15.2824 9.919 15.3874C9.091 15.5175 8.878 15.7607 8.827 15.8497C8.8 15.8961 8.797 15.9328 8.798 15.9542C8.798 15.9629 8.799 15.9695 8.8 15.973C8.805 15.9901 8.81 16.0079 8.813 16.026C8.833 16.1321 8.809 16.2395 8.751 16.3254C8.718 16.3733 8.675 16.4145 8.623 16.445C8.584 16.4681 8.54 16.4847 8.494 16.4932C8.447 16.502 8.4 16.5021 8.355 16.4944C8.296 16.4846 8.242 16.4619 8.195 16.4294C8.148 16.3972 8.108 16.3549 8.078 16.304C8.063 16.278 8.05 16.2501 8.041 16.2208C8.04 16.217 8.038 16.2128 8.037 16.2083C8.032 16.1931 8.027 16.1741 8.022 16.1517C8.012 16.1071 8.002 16.0477 8 15.977C7.996 15.8338 8.023 15.6452 8.136 15.4492C8.365 15.0533 8.87 14.7426 9.795 14.5971C9.958 14.5714 10.079 14.5362 10.167 14.4992C9.499 14.478 8.82 14.0237 8.82 13.2999C8.82 13.0999 8.876 12.9166 8.969 12.758C8.629 12.344 8.421 11.8466 8.421 11.3C8.421 10.4724 8.896 9.7627 9.583 9.2761C10.272 8.7888 11.202 8.5 12.211 8.5C13.219 8.5 14.15 8.7888 14.838 9.2761C15.526 9.7627 16 10.4724 16 11.3C16 12.1275 15.526 12.8372 14.838 13.3238C14.15 13.8111 13.219 14.0999 12.211 14.0999ZM9.754 13.0514C9.859 12.9649 10.021 12.8999 10.216 12.8999C10.634 12.8999 10.815 13.1577 10.815 13.2999C10.815 13.3371 10.806 13.3739 10.789 13.4108C10.757 13.4794 10.69 13.5552 10.58 13.6136C10.482 13.666 10.357 13.6999 10.216 13.6999C9.798 13.6999 9.618 13.4422 9.618 13.2999C9.618 13.2236 9.655 13.1338 9.754 13.0514Z",
955
+ fill: "white"
956
+ }
957
+ ) })
958
+ ] });
959
+ var EraseIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "36", height: "30", viewBox: "0 0 30 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
960
+ /* @__PURE__ */ jsx(
961
+ "path",
962
+ {
963
+ d: "M0.308 1.2407C0.151 0.61 0.628 0 1.278 0H23.664C24.118 0 24.516 0.3065 24.631 0.746L30.671 23.746C30.837 24.38 30.359 25 29.704 25H6.982C6.523 25 6.122 24.6868 6.012 24.2407L0.308 1.2407Z",
964
+ fill: "#FFBA00",
965
+ opacity: isSelected ? 1 : 0.7
966
+ }
967
+ ),
968
+ /* @__PURE__ */ jsx("g", { transform: "translate(15, 12.5)", children: /* @__PURE__ */ jsxs("g", { transform: "rotate(-45)", children: [
969
+ /* @__PURE__ */ jsx("rect", { x: "-6", y: "-3", width: "12", height: "6", rx: "1", fill: "none", stroke: "white", strokeWidth: "1.5" }),
970
+ /* @__PURE__ */ jsx("line", { x1: "-2", y1: "-3", x2: "-2", y2: "3", stroke: "white", strokeWidth: "1.5" })
971
+ ] }) })
972
+ ] });
973
+ var ShapesIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
974
+ /* @__PURE__ */ jsx(
975
+ "path",
976
+ {
977
+ d: "M12.253 0.8403C12.65 0.393 13.35 0.393 13.747 0.8403L16.628 4.0796C16.805 4.2791 17.055 4.3993 17.321 4.4136L21.65 4.6461C22.248 4.6782 22.684 5.2247 22.582 5.8146L21.845 10.0863C21.8 10.3493 21.862 10.6196 22.017 10.8369L24.534 14.366C24.881 14.8533 24.726 15.5348 24.201 15.8231L20.402 17.9105C20.168 18.0391 19.995 18.2558 19.922 18.5125L18.732 22.6808C18.568 23.2564 17.938 23.5596 17.386 23.3292L13.385 21.6606C13.139 21.5578 12.861 21.5578 12.615 21.6606L8.614 23.3292C8.062 23.5596 7.432 23.2564 7.268 22.6808L6.078 18.5125C6.005 18.2558 5.832 18.0391 5.598 17.9105L1.799 15.8231C1.274 15.5348 1.119 14.8533 1.466 14.366L3.983 10.8369C4.138 10.6196 4.2 10.3493 4.155 10.0863L3.418 5.8146C3.316 5.2247 3.752 4.6782 4.35 4.6461L8.679 4.4136C8.945 4.3993 9.195 4.2791 9.372 4.0796L12.253 0.8403Z",
978
+ fill: "#FF6800",
979
+ opacity: isSelected ? 1 : 0.7
980
+ }
981
+ ),
982
+ /* @__PURE__ */ jsxs("g", { transform: "translate(5.5, 7)", children: [
983
+ /* @__PURE__ */ jsx("rect", { x: "0", y: "6", width: "4", height: "4", rx: "0.5", fill: "white" }),
984
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "4", r: "3", fill: "white" }),
985
+ /* @__PURE__ */ jsx("path", { d: "M5 11L7.5 6.5L10 11H5Z", fill: "white" })
986
+ ] })
987
+ ] });
988
+ var GEO_SHAPES = [
989
+ "rectangle",
990
+ "ellipse",
991
+ "triangle",
992
+ "diamond",
993
+ "pentagon",
994
+ "hexagon",
995
+ "octagon",
996
+ "star",
997
+ "rhombus",
998
+ "rhombus-2",
999
+ "oval",
1000
+ "trapezoid",
1001
+ "arrow-right",
1002
+ "arrow-left",
1003
+ "arrow-up",
1004
+ "arrow-down",
1005
+ "x-box",
1006
+ "check-box",
1007
+ "cloud",
1008
+ "heart"
1009
+ ];
1010
+ var ShapeIcon = ({ shape, size = 20 }) => {
1011
+ const stroke = "currentColor";
1012
+ const strokeWidth = 1.5;
1013
+ const fill = "none";
1014
+ switch (shape) {
1015
+ case "rectangle":
1016
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "1" }) });
1017
+ case "ellipse":
1018
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "12", rx: "9", ry: "7" }) });
1019
+ case "triangle":
1020
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 4L21 20H3L12 4Z" }) });
1021
+ case "diamond":
1022
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 2L22 12L12 22L2 12L12 2Z" }) });
1023
+ case "pentagon":
1024
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 2L22 9L18 21H6L2 9L12 2Z" }) });
1025
+ case "hexagon":
1026
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 2L21 7V17L12 22L3 17V7L12 2Z" }) });
1027
+ case "octagon":
1028
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M8 2H16L22 8V16L16 22H8L2 16V8L8 2Z" }) });
1029
+ case "star":
1030
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 2L14.5 9H22L16 13.5L18.5 21L12 16.5L5.5 21L8 13.5L2 9H9.5L12 2Z" }) });
1031
+ case "rhombus":
1032
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 3L20 12L12 21L4 12L12 3Z" }) });
1033
+ case "rhombus-2":
1034
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M2 12L12 4L22 12L12 20L2 12Z" }) });
1035
+ case "oval":
1036
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "12", rx: "6", ry: "9" }) });
1037
+ case "trapezoid":
1038
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M6 6H18L21 18H3L6 6Z" }) });
1039
+ case "arrow-right":
1040
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M4 8V16H12V20L20 12L12 4V8H4Z" }) });
1041
+ case "arrow-left":
1042
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M20 8V16H12V20L4 12L12 4V8H20Z" }) });
1043
+ case "arrow-up":
1044
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M8 22V10L4 10L12 2L20 10H16V22H8Z" }) });
1045
+ case "arrow-down":
1046
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M8 2V14L4 14L12 22L20 14H16V2H8Z" }) });
1047
+ case "x-box":
1048
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: [
1049
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }),
1050
+ /* @__PURE__ */ jsx("path", { d: "M8 8L16 16M16 8L8 16" })
1051
+ ] });
1052
+ case "check-box":
1053
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: [
1054
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }),
1055
+ /* @__PURE__ */ jsx("path", { d: "M7 12L10 15L17 8" })
1056
+ ] });
1057
+ case "cloud":
1058
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M6.5 19C4 19 2 17 2 14.5C2 12.5 3.5 10.5 5.5 10C5.5 7 8 4 12 4C15.5 4 18 6.5 18.5 9.5C20.5 10 22 11.5 22 14C22 16.5 20 19 17.5 19H6.5Z" }) });
1059
+ case "heart":
1060
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("path", { d: "M12 21C12 21 3 15 3 9C3 6 5.5 3 8.5 3C10.5 3 12 4.5 12 4.5C12 4.5 13.5 3 15.5 3C18.5 3 21 6 21 9C21 15 12 21 12 21Z" }) });
1061
+ default:
1062
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }) });
1063
+ }
1064
+ };
1065
+ var ShapePicker = ({ isOpen, onClose, onSelectShape, anchorRef }) => {
1066
+ const editor = useEditor();
1067
+ const pickerRef = useRef(null);
1068
+ const [selectedShape, setSelectedShape] = useState("rectangle");
1069
+ useEffect(() => {
1070
+ const currentGeo = editor.getStyleForNextShape(GeoShapeGeoStyle);
1071
+ if (currentGeo && GEO_SHAPES.includes(currentGeo)) {
1072
+ setSelectedShape(currentGeo);
1073
+ }
1074
+ }, [editor, isOpen]);
1075
+ useEffect(() => {
1076
+ if (!isOpen) return;
1077
+ const handleClickOutside = (e) => {
1078
+ if (pickerRef.current && !pickerRef.current.contains(e.target) && anchorRef.current && !anchorRef.current.contains(e.target)) {
1079
+ onClose();
1080
+ }
1081
+ };
1082
+ document.addEventListener("mousedown", handleClickOutside);
1083
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1084
+ }, [isOpen, onClose, anchorRef]);
1085
+ useEffect(() => {
1086
+ if (!isOpen) return;
1087
+ const handleKeyDown = (e) => {
1088
+ if (e.key === "Escape") {
1089
+ onClose();
1090
+ }
1091
+ };
1092
+ document.addEventListener("keydown", handleKeyDown);
1093
+ return () => document.removeEventListener("keydown", handleKeyDown);
1094
+ }, [isOpen, onClose]);
1095
+ if (!isOpen) return null;
1096
+ const handleShapeClick = (e, shape) => {
1097
+ e.preventDefault();
1098
+ e.stopPropagation();
1099
+ setSelectedShape(shape);
1100
+ onSelectShape(shape);
1101
+ };
1102
+ const stopAllEvents = (e) => {
1103
+ e.preventDefault();
1104
+ e.stopPropagation();
1105
+ };
1106
+ const anchorRect = anchorRef.current?.getBoundingClientRect();
1107
+ const pickerStyle = anchorRect ? {
593
1108
  position: "fixed",
594
- transform: "translateX(-50%)",
595
- width: 280,
596
- padding: "12px 16px 14px",
597
- background: "#1a1a1a",
598
- borderRadius: 16,
599
- boxShadow: "0 4px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08)",
600
- cursor: "default",
601
- zIndex: 100001,
602
- fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1109
+ bottom: window.innerHeight - anchorRect.top + 8,
1110
+ left: anchorRect.left + anchorRect.width / 2,
1111
+ transform: "translateX(-50%)"
1112
+ } : {};
1113
+ return /* @__PURE__ */ jsx(
1114
+ "div",
1115
+ {
1116
+ ref: pickerRef,
1117
+ style: {
1118
+ ...pickerStyle,
1119
+ backgroundColor: "white",
1120
+ borderRadius: 8,
1121
+ boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
1122
+ padding: 8,
1123
+ zIndex: 1e5,
1124
+ pointerEvents: "auto"
1125
+ },
1126
+ onClick: stopAllEvents,
1127
+ onPointerDown: stopAllEvents,
1128
+ onMouseDown: stopAllEvents,
1129
+ children: /* @__PURE__ */ jsx(
1130
+ "div",
1131
+ {
1132
+ style: {
1133
+ display: "grid",
1134
+ gridTemplateColumns: "repeat(4, 1fr)",
1135
+ gap: 2
1136
+ },
1137
+ children: GEO_SHAPES.map((shape) => /* @__PURE__ */ jsx(
1138
+ "button",
1139
+ {
1140
+ onClick: (e) => handleShapeClick(e, shape),
1141
+ onPointerDown: stopAllEvents,
1142
+ onMouseDown: stopAllEvents,
1143
+ title: shape.replace(/-/g, " "),
1144
+ style: {
1145
+ display: "flex",
1146
+ alignItems: "center",
1147
+ justifyContent: "center",
1148
+ width: 32,
1149
+ height: 32,
1150
+ border: "none",
1151
+ borderRadius: 4,
1152
+ backgroundColor: selectedShape === shape ? "#e8f4ff" : "transparent",
1153
+ color: selectedShape === shape ? "#2c7fff" : "#333",
1154
+ cursor: "pointer",
1155
+ transition: "background-color 0.1s"
1156
+ },
1157
+ onMouseEnter: (e) => {
1158
+ if (selectedShape !== shape) {
1159
+ e.currentTarget.style.backgroundColor = "#f5f5f5";
1160
+ }
1161
+ },
1162
+ onMouseLeave: (e) => {
1163
+ if (selectedShape !== shape) {
1164
+ e.currentTarget.style.backgroundColor = "transparent";
1165
+ }
1166
+ },
1167
+ children: /* @__PURE__ */ jsx(ShapeIcon, { shape, size: 18 })
1168
+ },
1169
+ shape
1170
+ ))
1171
+ }
1172
+ )
1173
+ }
1174
+ );
1175
+ };
1176
+ var TOOL_COUNT = 4;
1177
+ var EXIT_DURATION = 0.12;
1178
+ var EXIT_STAGGER = 0.01;
1179
+ var smoothEasing = [0.4, 0, 0.2, 1];
1180
+ var toolVariants = {
1181
+ hidden: {
603
1182
  opacity: 0,
604
- transition: "opacity 0.2s ease, transform 0.2s ease"
1183
+ scale: 0.8,
1184
+ width: 0,
1185
+ marginRight: -6
605
1186
  },
606
- popupEnter: {
1187
+ visible: (i) => ({
607
1188
  opacity: 1,
608
- transform: "translateX(-50%) scale(1) translateY(0)"
609
- },
610
- popupExit: {
1189
+ scale: 1,
1190
+ width: 40,
1191
+ marginRight: 0,
1192
+ transition: {
1193
+ opacity: { duration: 0.15, delay: i * 0.04 },
1194
+ scale: { type: "spring", stiffness: 400, damping: 25, delay: i * 0.04 },
1195
+ width: { type: "spring", stiffness: 400, damping: 30, delay: i * 0.03 },
1196
+ marginRight: { duration: 0.1, delay: i * 0.03 }
1197
+ }
1198
+ }),
1199
+ exit: (i) => ({
611
1200
  opacity: 0,
612
- transform: "translateX(-50%) scale(0.95) translateY(4px)"
613
- },
614
- header: {
615
- display: "flex",
616
- alignItems: "center",
617
- justifyContent: "space-between",
618
- marginBottom: 9
619
- },
620
- element: {
621
- fontSize: 12,
622
- fontWeight: 400,
623
- color: "rgba(255, 255, 255, 0.5)",
624
- maxWidth: "100%",
625
- overflow: "hidden",
626
- textOverflow: "ellipsis",
627
- whiteSpace: "nowrap",
628
- flex: 1
629
- },
630
- quote: {
631
- fontSize: 12,
632
- fontStyle: "italic",
633
- color: "rgba(255, 255, 255, 0.6)",
634
- marginBottom: 8,
635
- padding: "6px 8px",
636
- background: "rgba(255, 255, 255, 0.05)",
637
- borderRadius: 4,
638
- lineHeight: 1.45
639
- },
640
- textarea: {
641
- width: "100%",
642
- padding: "8px 10px",
643
- fontSize: 13,
644
- fontFamily: "inherit",
645
- background: "rgba(255, 255, 255, 0.05)",
646
- color: "#fff",
647
- border: "1px solid rgba(255, 255, 255, 0.15)",
648
- borderRadius: 8,
649
- resize: "none",
650
- outline: "none",
651
- transition: "border-color 0.15s ease",
652
- boxSizing: "border-box"
653
- },
654
- actions: {
655
- display: "flex",
656
- justifyContent: "flex-end",
657
- gap: 6,
658
- marginTop: 8
659
- },
660
- button: {
661
- padding: "6px 14px",
662
- fontSize: 12,
663
- fontWeight: 500,
664
- borderRadius: 16,
665
- border: "none",
666
- cursor: "pointer",
667
- transition: "background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease"
1201
+ scale: 0.8,
1202
+ width: 0,
1203
+ marginRight: -6,
1204
+ transition: {
1205
+ opacity: { duration: 0.06, ease: "easeOut" },
1206
+ scale: { duration: EXIT_DURATION, ease: smoothEasing },
1207
+ width: { duration: EXIT_DURATION, ease: smoothEasing, delay: EXIT_STAGGER * (TOOL_COUNT - 1 - i) },
1208
+ marginRight: { duration: EXIT_DURATION, ease: smoothEasing, delay: EXIT_STAGGER * (TOOL_COUNT - 1 - i) }
1209
+ }
1210
+ })
1211
+ };
1212
+ var chevronVariants = {
1213
+ hidden: {
1214
+ opacity: 0,
1215
+ scale: 0.8,
1216
+ width: 0
668
1217
  },
669
- cancelButton: {
670
- background: "transparent",
671
- color: "rgba(255, 255, 255, 0.5)"
1218
+ visible: {
1219
+ opacity: 0.6,
1220
+ scale: 1,
1221
+ width: 28,
1222
+ transition: {
1223
+ opacity: { delay: 0.1, duration: 0.1 },
1224
+ scale: { delay: 0.1, type: "spring", stiffness: 500, damping: 30 },
1225
+ width: { delay: 0.08, duration: 0.12, ease: "easeOut" }
1226
+ }
672
1227
  },
673
- submitButton: {
674
- color: "white"
1228
+ exit: {
1229
+ opacity: 0,
1230
+ scale: 0.8,
1231
+ width: 0,
1232
+ transition: {
1233
+ opacity: { duration: 0.06 },
1234
+ scale: { duration: 0.08 },
1235
+ width: { duration: 0.1, ease: smoothEasing }
1236
+ }
675
1237
  }
676
1238
  };
677
- var AnnotationPopup = forwardRef(
678
- function AnnotationPopup2({
679
- element,
680
- selectedText,
681
- placeholder = "What should change?",
682
- initialValue = "",
683
- submitLabel = "Add",
684
- onSubmit,
685
- onCancel,
686
- style,
687
- accentColor = "#3c82f7",
688
- isExiting = false,
689
- isMultiSelect = false
690
- }, ref) {
691
- const [text, setText] = useState(initialValue);
692
- const [isShaking, setIsShaking] = useState(false);
693
- const [animState, setAnimState] = useState("initial");
694
- const [isFocused, setIsFocused] = useState(false);
695
- const textareaRef = useRef(null);
696
- const popupRef = useRef(null);
697
- useEffect(() => {
698
- if (isExiting && animState !== "exit") {
699
- setAnimState("exit");
700
- }
701
- }, [isExiting, animState]);
702
- useEffect(() => {
703
- requestAnimationFrame(() => {
704
- setAnimState("enter");
705
- });
706
- const enterTimer = setTimeout(() => {
707
- setAnimState("entered");
708
- }, 200);
709
- const focusTimer = setTimeout(() => {
710
- const textarea = textareaRef.current;
711
- if (textarea) {
712
- textarea.focus();
713
- textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
714
- textarea.scrollTop = textarea.scrollHeight;
715
- }
716
- }, 50);
717
- return () => {
718
- clearTimeout(enterTimer);
719
- clearTimeout(focusTimer);
720
- };
721
- }, []);
722
- const shake = useCallback(() => {
723
- setIsShaking(true);
724
- setTimeout(() => {
725
- setIsShaking(false);
726
- textareaRef.current?.focus();
727
- }, 250);
728
- }, []);
729
- useImperativeHandle(ref, () => ({
730
- shake
731
- }), [shake]);
732
- const handleCancel = useCallback(() => {
733
- setAnimState("exit");
734
- setTimeout(() => {
735
- onCancel();
736
- }, 150);
737
- }, [onCancel]);
738
- const handleSubmit = useCallback(() => {
739
- if (!text.trim()) return;
740
- onSubmit(text.trim());
741
- }, [text, onSubmit]);
742
- const handleKeyDown = useCallback(
743
- (e) => {
744
- if (e.nativeEvent.isComposing) return;
745
- if (e.key === "Enter" && !e.shiftKey) {
746
- e.preventDefault();
747
- handleSubmit();
748
- }
749
- if (e.key === "Escape") {
750
- handleCancel();
1239
+ var ToolbarButton = ({
1240
+ onClick,
1241
+ isSelected,
1242
+ icon,
1243
+ label,
1244
+ buttonRef,
1245
+ index = 0
1246
+ }) => {
1247
+ const handleClick = (e) => {
1248
+ e.preventDefault();
1249
+ e.stopPropagation();
1250
+ onClick();
1251
+ };
1252
+ return /* @__PURE__ */ jsx(
1253
+ motion.button,
1254
+ {
1255
+ ref: buttonRef,
1256
+ custom: index,
1257
+ variants: toolVariants,
1258
+ initial: "hidden",
1259
+ animate: "visible",
1260
+ exit: "exit",
1261
+ onClick: handleClick,
1262
+ title: label,
1263
+ type: "button",
1264
+ whileHover: { scale: 1.05 },
1265
+ whileTap: { scale: 0.95 },
1266
+ style: {
1267
+ display: "flex",
1268
+ alignItems: "center",
1269
+ justifyContent: "center",
1270
+ height: 40,
1271
+ minWidth: 0,
1272
+ border: "none",
1273
+ borderRadius: 8,
1274
+ backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1275
+ cursor: "pointer",
1276
+ pointerEvents: "auto",
1277
+ overflow: "hidden",
1278
+ flexShrink: 0
1279
+ },
1280
+ children: /* @__PURE__ */ jsx(
1281
+ motion.span,
1282
+ {
1283
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
1284
+ initial: { opacity: 0, scale: 0.8 },
1285
+ animate: { opacity: 1, scale: 1 },
1286
+ exit: { opacity: 0, scale: 0.8 },
1287
+ transition: { duration: 0.12 },
1288
+ children: icon
751
1289
  }
1290
+ )
1291
+ }
1292
+ );
1293
+ };
1294
+ var LogoShapesButton = ({
1295
+ isExpanded,
1296
+ isHovered,
1297
+ isSelected,
1298
+ onClick,
1299
+ onShapesClick,
1300
+ onMouseEnter,
1301
+ onMouseLeave,
1302
+ buttonRef
1303
+ }) => {
1304
+ const handleClick = (e) => {
1305
+ e.preventDefault();
1306
+ e.stopPropagation();
1307
+ if (isExpanded) {
1308
+ onShapesClick();
1309
+ } else {
1310
+ onClick();
1311
+ }
1312
+ };
1313
+ return /* @__PURE__ */ jsx(
1314
+ motion.button,
1315
+ {
1316
+ ref: buttonRef,
1317
+ onClick: handleClick,
1318
+ onMouseEnter,
1319
+ onMouseLeave,
1320
+ title: isExpanded ? "Shapes (G)" : "Expand toolbar",
1321
+ type: "button",
1322
+ whileHover: { scale: 1.08 },
1323
+ whileTap: { scale: 0.95 },
1324
+ style: {
1325
+ display: "flex",
1326
+ alignItems: "center",
1327
+ justifyContent: "center",
1328
+ width: 40,
1329
+ height: 40,
1330
+ border: "none",
1331
+ borderRadius: 8,
1332
+ backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1333
+ cursor: "pointer",
1334
+ pointerEvents: "auto",
1335
+ position: "relative"
752
1336
  },
753
- [handleSubmit, handleCancel]
754
- );
755
- const popupStyle = {
756
- ...styles.popup,
757
- ...animState === "enter" || animState === "entered" ? styles.popupEnter : {},
758
- ...animState === "exit" ? styles.popupExit : {},
759
- ...isShaking ? {
760
- animation: "skema-shake 0.25s ease-out"
761
- } : {},
762
- ...style
763
- };
764
- const textareaStyle = {
765
- ...styles.textarea,
766
- ...isFocused ? { borderColor: accentColor } : {}
767
- };
768
- const effectiveAccentColor = isMultiSelect ? "#34C759" : accentColor;
769
- return /* @__PURE__ */ jsxs(Fragment, { children: [
770
- /* @__PURE__ */ jsx("style", { children: `
771
- @keyframes skema-shake {
772
- 0%, 100% { transform: translateX(-50%) scale(1) translateY(0) translateX(0); }
773
- 20% { transform: translateX(-50%) scale(1) translateY(0) translateX(-3px); }
774
- 40% { transform: translateX(-50%) scale(1) translateY(0) translateX(3px); }
775
- 60% { transform: translateX(-50%) scale(1) translateY(0) translateX(-2px); }
776
- 80% { transform: translateX(-50%) scale(1) translateY(0) translateX(2px); }
777
- }
778
- ` }),
779
- /* @__PURE__ */ jsxs(
780
- "div",
1337
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: isExpanded ? /* @__PURE__ */ jsx(
1338
+ motion.div,
781
1339
  {
782
- ref: popupRef,
783
- "data-skema": "annotation-popup",
784
- style: popupStyle,
785
- onClick: (e) => e.stopPropagation(),
786
- onPointerDown: (e) => e.stopPropagation(),
787
- children: [
788
- /* @__PURE__ */ jsx("div", { style: styles.header, children: /* @__PURE__ */ jsx("span", { style: styles.element, children: element }) }),
789
- selectedText && /* @__PURE__ */ jsxs("div", { style: styles.quote, children: [
790
- "\u201C",
791
- selectedText.slice(0, 80),
792
- selectedText.length > 80 ? "..." : "",
793
- "\u201D"
794
- ] }),
795
- /* @__PURE__ */ jsx(
796
- "textarea",
797
- {
798
- ref: textareaRef,
799
- style: textareaStyle,
800
- placeholder,
801
- value: text,
802
- onChange: (e) => setText(e.target.value),
803
- onFocus: () => setIsFocused(true),
804
- onBlur: () => setIsFocused(false),
805
- rows: 2,
806
- onKeyDown: handleKeyDown
807
- }
808
- ),
809
- /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
810
- /* @__PURE__ */ jsx(
811
- "button",
812
- {
813
- style: { ...styles.button, ...styles.cancelButton },
814
- onClick: handleCancel,
815
- onMouseEnter: (e) => {
816
- e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
817
- e.currentTarget.style.color = "rgba(255, 255, 255, 0.8)";
818
- },
819
- onMouseLeave: (e) => {
820
- e.currentTarget.style.background = "transparent";
821
- e.currentTarget.style.color = "rgba(255, 255, 255, 0.5)";
822
- },
823
- children: "Cancel"
824
- }
825
- ),
826
- /* @__PURE__ */ jsx(
827
- "button",
828
- {
829
- style: {
830
- ...styles.button,
831
- ...styles.submitButton,
832
- backgroundColor: effectiveAccentColor,
833
- opacity: text.trim() ? 1 : 0.4
834
- },
835
- onClick: handleSubmit,
836
- disabled: !text.trim(),
837
- onMouseEnter: (e) => {
838
- if (text.trim()) {
839
- e.currentTarget.style.filter = "brightness(0.9)";
840
- }
841
- },
842
- onMouseLeave: (e) => {
843
- e.currentTarget.style.filter = "none";
844
- },
845
- children: submitLabel
846
- }
847
- )
848
- ] })
849
- ]
1340
+ initial: { opacity: 0, scale: 0.8 },
1341
+ animate: { opacity: 1, scale: 1 },
1342
+ exit: { opacity: 0, scale: 0.85 },
1343
+ transition: {
1344
+ type: "spring",
1345
+ stiffness: 500,
1346
+ damping: 30,
1347
+ opacity: { duration: 0.08 }
1348
+ },
1349
+ children: /* @__PURE__ */ jsx(ShapesIcon, { isSelected })
1350
+ },
1351
+ "shapes"
1352
+ ) : isHovered ? /* @__PURE__ */ jsx(
1353
+ motion.div,
1354
+ {
1355
+ initial: { opacity: 0, x: -3 },
1356
+ animate: { opacity: 1, x: 0 },
1357
+ exit: { opacity: 0, x: 3 },
1358
+ transition: { duration: 0.08, ease: "easeOut" },
1359
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { size: 20 })
1360
+ },
1361
+ "chevron"
1362
+ ) : /* @__PURE__ */ jsx(
1363
+ motion.div,
1364
+ {
1365
+ initial: { opacity: 0, scale: 0.9 },
1366
+ animate: { opacity: 1, scale: 1 },
1367
+ exit: { opacity: 0, scale: 0.85 },
1368
+ transition: {
1369
+ type: "spring",
1370
+ stiffness: 500,
1371
+ damping: 30,
1372
+ opacity: { duration: 0.06 }
1373
+ },
1374
+ children: /* @__PURE__ */ jsx(SLogoIcon, { size: 32 })
1375
+ },
1376
+ "s"
1377
+ ) })
1378
+ }
1379
+ );
1380
+ };
1381
+ var CollapseButton = ({ onClick }) => {
1382
+ return /* @__PURE__ */ jsx(
1383
+ motion.button,
1384
+ {
1385
+ variants: chevronVariants,
1386
+ initial: "hidden",
1387
+ animate: "visible",
1388
+ exit: "exit",
1389
+ onClick: (e) => {
1390
+ e.preventDefault();
1391
+ e.stopPropagation();
1392
+ onClick();
1393
+ },
1394
+ title: "Collapse toolbar",
1395
+ type: "button",
1396
+ whileHover: { opacity: 1, scale: 1.1 },
1397
+ whileTap: { scale: 0.9 },
1398
+ style: {
1399
+ display: "flex",
1400
+ alignItems: "center",
1401
+ justifyContent: "center",
1402
+ height: 40,
1403
+ minWidth: 0,
1404
+ border: "none",
1405
+ borderRadius: 6,
1406
+ backgroundColor: "transparent",
1407
+ cursor: "pointer",
1408
+ pointerEvents: "auto",
1409
+ marginLeft: 2,
1410
+ overflow: "hidden",
1411
+ flexShrink: 0
1412
+ },
1413
+ children: /* @__PURE__ */ jsx(
1414
+ motion.span,
1415
+ {
1416
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
1417
+ initial: { opacity: 0 },
1418
+ animate: { opacity: 1 },
1419
+ exit: { opacity: 0 },
1420
+ transition: { duration: 0.1 },
1421
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { size: 20 })
850
1422
  }
851
1423
  )
852
- ] });
853
- }
854
- );
855
-
856
- // src/lib/utils.ts
857
- async function blobToBase64(blob) {
858
- return new Promise((resolve, reject) => {
859
- const reader = new FileReader();
860
- reader.onload = () => {
861
- const result = reader.result;
862
- resolve(result);
863
- };
864
- reader.onerror = () => reject(reader.error);
865
- reader.readAsDataURL(blob);
866
- });
867
- }
868
- function addGridToSvg(svgString, opts = {}) {
869
- const { color = "#0066FF", size = 100, labels = true } = opts;
870
- const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/);
871
- if (!viewBoxMatch) return svgString;
872
- const [x, y, w, h] = viewBoxMatch[1].split(" ").map(Number);
873
- const gridElements = [];
874
- for (let i = 0; i <= Math.ceil(w / size); i++) {
875
- const xPos = i * size;
876
- if (i > 0) {
877
- gridElements.push(
878
- `<line x1="${xPos}" y1="0" x2="${xPos}" y2="${h}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
879
- );
880
- }
881
- if (labels) {
882
- const colLabel = String.fromCharCode(65 + i);
883
- gridElements.push(
884
- `<text x="${xPos + size / 2}" y="16" fill="${color}" font-size="12" font-family="sans-serif" text-anchor="middle">${colLabel}</text>`
885
- );
886
1424
  }
887
- }
888
- for (let i = 0; i <= Math.ceil(h / size); i++) {
889
- const yPos = i * size;
890
- gridElements.push(
891
- `<line x1="0" y1="${yPos}" x2="${w}" y2="${yPos}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
892
- );
893
- if (labels && i < Math.ceil(h / size)) {
894
- gridElements.push(
895
- `<text x="8" y="${yPos + size / 2 + 4}" fill="${color}" font-size="12" font-family="sans-serif">${i}</text>`
896
- );
1425
+ );
1426
+ };
1427
+ var SkemaToolbar = ({ onExpandedChange, onStylePanelChange }) => {
1428
+ const editor = useEditor();
1429
+ const tools = useTools();
1430
+ const shapesButtonRef = useRef(null);
1431
+ const [isExpanded, setIsExpanded] = useState(false);
1432
+ const [isLogoHovered, setIsLogoHovered] = useState(false);
1433
+ const [isShapePickerOpen, setIsShapePickerOpen] = useState(false);
1434
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState(false);
1435
+ const isSelectSelected = useIsToolSelected(tools["select"]);
1436
+ const isDrawSelected = useIsToolSelected(tools["draw"]);
1437
+ const isLassoSelected = useIsToolSelected(tools["lasso-select"]);
1438
+ const isEraseSelected = useIsToolSelected(tools["eraser"]);
1439
+ const isGeoSelected = useIsToolSelected(tools["geo"]);
1440
+ const handleExpand = (expanded) => {
1441
+ setIsExpanded(expanded);
1442
+ onExpandedChange?.(expanded);
1443
+ if (!expanded) {
1444
+ setIsStylePanelOpen(false);
1445
+ onStylePanelChange?.(false);
897
1446
  }
898
- }
899
- const gridGroup = `<g id="skema-grid" transform="translate(${x}, ${y})">${gridElements.join("")}</g>`;
900
- return svgString.replace("</svg>", `${gridGroup}</svg>`);
901
- }
902
- function getGridCellReference(x, y, gridSize = 100) {
903
- const col = Math.floor(x / gridSize);
904
- const row = Math.floor(y / gridSize);
905
- const colLabel = String.fromCharCode(65 + col);
906
- return `${colLabel}${row}`;
907
- }
908
- function extractTextFromShapes(shapes) {
909
- const textContent = [];
910
- for (const shape of shapes) {
911
- const s = shape;
912
- if (s.type === "text" || s.type === "note") {
913
- if (s.props?.text) {
914
- textContent.push(s.props.text);
915
- }
916
- if (s.props?.richText && typeof s.props.richText === "object") {
917
- const rt = s.props.richText;
918
- if (rt.content) {
919
- for (const block of rt.content) {
920
- if (block.content) {
921
- for (const inline of block.content) {
922
- if (inline.text) {
923
- textContent.push(inline.text);
1447
+ };
1448
+ const handleLogoClick = () => {
1449
+ handleExpand(true);
1450
+ };
1451
+ const handleCollapse = () => {
1452
+ handleExpand(false);
1453
+ setIsShapePickerOpen(false);
1454
+ };
1455
+ const handleShapesClick = () => {
1456
+ setIsShapePickerOpen((prev) => !prev);
1457
+ };
1458
+ const handleSelectShape = (shape) => {
1459
+ editor.setStyleForNextShapes(GeoShapeGeoStyle, shape);
1460
+ editor.setCurrentTool("geo");
1461
+ setIsShapePickerOpen(false);
1462
+ };
1463
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1464
+ /* @__PURE__ */ jsx(
1465
+ "div",
1466
+ {
1467
+ style: {
1468
+ position: "absolute",
1469
+ bottom: 16,
1470
+ left: 0,
1471
+ right: 0,
1472
+ display: "flex",
1473
+ justifyContent: "center",
1474
+ pointerEvents: "none",
1475
+ zIndex: 99999
1476
+ },
1477
+ children: /* @__PURE__ */ jsxs(
1478
+ motion.div,
1479
+ {
1480
+ "data-skema": "toolbar",
1481
+ style: {
1482
+ display: "flex",
1483
+ alignItems: "center",
1484
+ gap: 6,
1485
+ padding: "6px 12px",
1486
+ backgroundColor: "white",
1487
+ borderRadius: 28,
1488
+ boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
1489
+ pointerEvents: "auto"
1490
+ },
1491
+ children: [
1492
+ /* @__PURE__ */ jsx(
1493
+ LogoShapesButton,
1494
+ {
1495
+ isExpanded,
1496
+ isHovered: isLogoHovered,
1497
+ isSelected: isGeoSelected || isShapePickerOpen,
1498
+ onClick: handleLogoClick,
1499
+ onShapesClick: handleShapesClick,
1500
+ onMouseEnter: () => setIsLogoHovered(true),
1501
+ onMouseLeave: () => setIsLogoHovered(false),
1502
+ buttonRef: shapesButtonRef
924
1503
  }
925
- }
926
- }
1504
+ ),
1505
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: isExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
1506
+ /* @__PURE__ */ jsx(
1507
+ ToolbarButton,
1508
+ {
1509
+ onClick: () => editor.setCurrentTool("select"),
1510
+ isSelected: isSelectSelected,
1511
+ icon: /* @__PURE__ */ jsx(SelectIcon, { isSelected: isSelectSelected }),
1512
+ label: "Select (S)",
1513
+ index: 0
1514
+ },
1515
+ "select"
1516
+ ),
1517
+ /* @__PURE__ */ jsx(
1518
+ ToolbarButton,
1519
+ {
1520
+ onClick: () => editor.setCurrentTool("draw"),
1521
+ isSelected: isDrawSelected,
1522
+ icon: /* @__PURE__ */ jsx(DrawIcon, { isSelected: isDrawSelected }),
1523
+ label: "Draw (D)",
1524
+ index: 1
1525
+ },
1526
+ "draw"
1527
+ ),
1528
+ /* @__PURE__ */ jsx(
1529
+ ToolbarButton,
1530
+ {
1531
+ onClick: () => editor.setCurrentTool("lasso-select"),
1532
+ isSelected: isLassoSelected,
1533
+ icon: /* @__PURE__ */ jsx(LassoIcon, { isSelected: isLassoSelected }),
1534
+ label: "Lasso Select (L)",
1535
+ index: 2
1536
+ },
1537
+ "lasso"
1538
+ ),
1539
+ /* @__PURE__ */ jsx(
1540
+ ToolbarButton,
1541
+ {
1542
+ onClick: () => editor.setCurrentTool("eraser"),
1543
+ isSelected: isEraseSelected,
1544
+ icon: /* @__PURE__ */ jsx(EraseIcon, { isSelected: isEraseSelected }),
1545
+ label: "Eraser (E)",
1546
+ index: 3
1547
+ },
1548
+ "erase"
1549
+ ),
1550
+ /* @__PURE__ */ jsx(
1551
+ CollapseButton,
1552
+ {
1553
+ onClick: handleCollapse
1554
+ },
1555
+ "collapse"
1556
+ )
1557
+ ] }) })
1558
+ ]
927
1559
  }
928
- }
1560
+ )
929
1561
  }
930
- }
931
- }
932
- return textContent.filter(Boolean).join("\n");
933
- }
1562
+ ),
1563
+ /* @__PURE__ */ jsx(
1564
+ ShapePicker,
1565
+ {
1566
+ isOpen: isShapePickerOpen,
1567
+ onClose: () => setIsShapePickerOpen(false),
1568
+ onSelectShape: handleSelectShape,
1569
+ anchorRef: shapesButtonRef
1570
+ }
1571
+ )
1572
+ ] });
1573
+ };
934
1574
  var AnnotationMarker = ({
935
1575
  annotation,
936
1576
  index,
@@ -986,7 +1626,7 @@ var AnnotationMarker = ({
986
1626
  justifyContent: "center",
987
1627
  fontSize: 11,
988
1628
  fontWeight: 600,
989
- fontFamily: "system-ui, -apple-system, sans-serif",
1629
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
990
1630
  cursor: "pointer",
991
1631
  boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
992
1632
  transition: "all 0.15s ease",
@@ -1042,7 +1682,13 @@ var AnnotationMarker = ({
1042
1682
  }
1043
1683
  );
1044
1684
  };
1045
- var AnnotationMarkersLayer = ({ annotations, scrollOffset, hoveredMarkerId, onHover, onDelete }) => {
1685
+ var AnnotationMarkersLayer = ({
1686
+ annotations,
1687
+ scrollOffset,
1688
+ hoveredMarkerId,
1689
+ onHover,
1690
+ onDelete
1691
+ }) => {
1046
1692
  return /* @__PURE__ */ jsx(Fragment, { children: annotations.map((annotation, index) => /* @__PURE__ */ jsx(
1047
1693
  AnnotationMarker,
1048
1694
  {
@@ -1056,292 +1702,14 @@ var AnnotationMarkersLayer = ({ annotations, scrollOffset, hoveredMarkerId, onHo
1056
1702
  annotation.id
1057
1703
  )) });
1058
1704
  };
1059
- var SelectIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1060
- /* @__PURE__ */ jsx(
1061
- "path",
1062
- {
1063
- d: "M11.268 3C12.0378 1.6667 13.9623 1.6667 14.7321 3L25.1244 21C25.8942 22.3333 24.9319 24 23.3923 24H2.6077C1.0681 24 0.1058 22.3333 0.8756 21L11.268 3Z",
1064
- fill: "#F24E1E",
1065
- opacity: isSelected ? 1 : 0.7
1066
- }
1067
- ),
1068
- /* @__PURE__ */ jsx(
1069
- "path",
1070
- {
1071
- d: "M9 10L9 18.5L11.5 16L14 20L15.5 19L13 15L16 14.5L9 10Z",
1072
- fill: "white"
1073
- }
1074
- )
1075
- ] });
1076
- var DrawIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1077
- /* @__PURE__ */ jsx("rect", { width: "25", height: "25", rx: "2", fill: isSelected ? "#00C851" : "#00C851", opacity: isSelected ? 1 : 0.7 }),
1078
- /* @__PURE__ */ jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsx(
1079
- "path",
1080
- {
1081
- fillRule: "evenodd",
1082
- clipRule: "evenodd",
1083
- d: "M13.6919 10.2852L14.2593 9.6908L14.8282 10.2864L14.2605 10.8808L13.6919 10.2852ZM9.5682 15.7944L8.9992 15.1988L13.1233 10.8808L13.6919 11.476L9.5682 15.7944ZM14.3284 8.5L8 15.1988V16.5H9.5682L16 10.0436L14.3284 8.5Z",
1084
- fill: "white"
1085
- }
1086
- ) })
1087
- ] });
1088
- var LassoIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1089
- /* @__PURE__ */ jsx("rect", { width: "25", height: "25", rx: "12.5", fill: isSelected ? "#2C7FFF" : "#2C7FFF", opacity: isSelected ? 1 : 0.7 }),
1090
- /* @__PURE__ */ jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsx(
1091
- "path",
1092
- {
1093
- fillRule: "evenodd",
1094
- clipRule: "evenodd",
1095
- d: "M9.219 11.3C9.219 10.8021 9.504 10.3117 10.043 9.9297C10.582 9.5484 11.347 9.3 12.211 9.3C13.074 9.3 13.839 9.5484 14.378 9.9297C14.918 10.3117 15.202 10.8021 15.202 11.3C15.202 11.7979 14.918 12.2882 14.378 12.6702C13.839 13.0515 13.074 13.2999 12.211 13.2999C12.005 13.2999 11.805 13.2859 11.612 13.2591C11.586 12.5417 10.887 12.0999 10.216 12.0999C9.988 12.0999 9.768 12.147 9.572 12.234C9.339 11.9444 9.219 11.625 9.219 11.3ZM12.211 14.0999C11.908 14.0999 11.614 14.074 11.331 14.0249C11.298 14.0629 11.262 14.0988 11.224 14.1325C11.226 14.154 11.228 14.1774 11.229 14.2026C11.232 14.3182 11.216 14.4769 11.137 14.6456C10.97 15.0066 10.586 15.2824 9.919 15.3874C9.091 15.5175 8.878 15.7607 8.827 15.8497C8.8 15.8961 8.797 15.9328 8.798 15.9542C8.798 15.9629 8.799 15.9695 8.8 15.973C8.805 15.9901 8.81 16.0079 8.813 16.026C8.833 16.1321 8.809 16.2395 8.751 16.3254C8.718 16.3733 8.675 16.4145 8.623 16.445C8.584 16.4681 8.54 16.4847 8.494 16.4932C8.447 16.502 8.4 16.5021 8.355 16.4944C8.296 16.4846 8.242 16.4619 8.195 16.4294C8.148 16.3972 8.108 16.3549 8.078 16.304C8.063 16.278 8.05 16.2501 8.041 16.2208C8.04 16.217 8.038 16.2128 8.037 16.2083C8.032 16.1931 8.027 16.1741 8.022 16.1517C8.012 16.1071 8.002 16.0477 8 15.977C7.996 15.8338 8.023 15.6452 8.136 15.4492C8.365 15.0533 8.87 14.7426 9.795 14.5971C9.958 14.5714 10.079 14.5362 10.167 14.4992C9.499 14.478 8.82 14.0237 8.82 13.2999C8.82 13.0999 8.876 12.9166 8.969 12.758C8.629 12.344 8.421 11.8466 8.421 11.3C8.421 10.4724 8.896 9.7627 9.583 9.2761C10.272 8.7888 11.202 8.5 12.211 8.5C13.219 8.5 14.15 8.7888 14.838 9.2761C15.526 9.7627 16 10.4724 16 11.3C16 12.1275 15.526 12.8372 14.838 13.3238C14.15 13.8111 13.219 14.0999 12.211 14.0999ZM9.754 13.0514C9.859 12.9649 10.021 12.8999 10.216 12.8999C10.634 12.8999 10.815 13.1577 10.815 13.2999C10.815 13.3371 10.806 13.3739 10.789 13.4108C10.757 13.4794 10.69 13.5552 10.58 13.6136C10.482 13.666 10.357 13.6999 10.216 13.6999C9.798 13.6999 9.618 13.4422 9.618 13.2999C9.618 13.2236 9.655 13.1338 9.754 13.0514Z",
1096
- fill: "white"
1097
- }
1098
- ) })
1099
- ] });
1100
- var EraseIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "50", height: "42", viewBox: "0 0 30 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1101
- /* @__PURE__ */ jsx(
1102
- "path",
1103
- {
1104
- d: "M0.308 1.2407C0.151 0.61 0.628 0 1.278 0H23.664C24.118 0 24.516 0.3065 24.631 0.746L30.671 23.746C30.837 24.38 30.359 25 29.704 25H6.982C6.523 25 6.122 24.6868 6.012 24.2407L0.308 1.2407Z",
1105
- fill: "#FFBA00",
1106
- opacity: isSelected ? 1 : 0.7
1107
- }
1108
- ),
1109
- /* @__PURE__ */ jsx("g", { transform: "translate(15, 12.5)", children: /* @__PURE__ */ jsxs("g", { transform: "rotate(-45)", children: [
1110
- /* @__PURE__ */ jsx("rect", { x: "-6", y: "-3", width: "12", height: "6", rx: "1", fill: "none", stroke: "white", strokeWidth: "1.5" }),
1111
- /* @__PURE__ */ jsx("line", { x1: "-2", y1: "-3", x2: "-2", y2: "3", stroke: "white", strokeWidth: "1.5" })
1112
- ] }) })
1113
- ] });
1114
- var StarIcon = ({ isSelected }) => /* @__PURE__ */ jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1115
- /* @__PURE__ */ jsx(
1116
- "path",
1117
- {
1118
- d: "M12.253 0.8403C12.65 0.393 13.35 0.393 13.747 0.8403L16.628 4.0796C16.805 4.2791 17.055 4.3993 17.321 4.4136L21.65 4.6461C22.248 4.6782 22.684 5.2247 22.582 5.8146L21.845 10.0863C21.8 10.3493 21.862 10.6196 22.017 10.8369L24.534 14.366C24.881 14.8533 24.726 15.5348 24.201 15.8231L20.402 17.9105C20.168 18.0391 19.995 18.2558 19.922 18.5125L18.732 22.6808C18.568 23.2564 17.938 23.5596 17.386 23.3292L13.385 21.6606C13.139 21.5578 12.861 21.5578 12.615 21.6606L8.614 23.3292C8.062 23.5596 7.432 23.2564 7.268 22.6808L6.078 18.5125C6.005 18.2558 5.832 18.0391 5.598 17.9105L1.799 15.8231C1.274 15.5348 1.119 14.8533 1.466 14.366L3.983 10.8369C4.138 10.6196 4.2 10.3493 4.155 10.0863L3.418 5.8146C3.316 5.2247 3.752 4.6782 4.35 4.6461L8.679 4.4136C8.945 4.3993 9.195 4.2791 9.372 4.0796L12.253 0.8403Z",
1119
- fill: "#FF6800",
1120
- opacity: isSelected ? 1 : 0.7
1121
- }
1122
- ),
1123
- /* @__PURE__ */ jsx("g", { transform: "translate(6.5, 6) scale(0.5)", children: /* @__PURE__ */ jsx(
1124
- "path",
1125
- {
1126
- d: "M23.546 10.93L13.067 0.452c-0.604-0.603-1.582-0.603-2.188 0L8.708 2.627l2.76 2.76c0.645-0.215 1.379-0.07 1.889 0.441 0.516 0.516 0.658 1.258 0.438 1.9l2.658 2.66c0.645-0.223 1.387-0.078 1.9 0.435 0.721 0.72 0.721 1.884 0 2.604-0.719 0.719-1.881 0.719-2.6 0-0.539-0.541-0.674-1.337-0.404-1.996L12.86 8.955v6.525c0.176 0.086 0.342 0.203 0.488 0.348 0.713 0.721 0.713 1.883 0 2.6-0.719 0.721-1.889 0.721-2.609 0-0.719-0.719-0.719-1.879 0-2.598 0.182-0.18 0.387-0.316 0.605-0.406V8.835c-0.217-0.091-0.424-0.222-0.6-0.401-0.545-0.545-0.676-1.342-0.396-2.009L7.636 3.7 0.45 10.881c-0.6 0.605-0.6 1.584 0 2.189l10.48 10.477c0.604 0.604 1.582 0.604 2.186 0l10.43-10.43c0.605-0.603 0.605-1.582 0-2.187",
1127
- fill: "white"
1128
- }
1129
- ) })
1130
- ] });
1131
- var ToolbarButton = ({ onClick, isSelected, icon, label }) => {
1132
- const handleClick = (e) => {
1133
- e.preventDefault();
1134
- e.stopPropagation();
1135
- onClick();
1136
- };
1137
- return /* @__PURE__ */ jsx(
1138
- "button",
1139
- {
1140
- onClick: handleClick,
1141
- title: label,
1142
- type: "button",
1143
- style: {
1144
- display: "flex",
1145
- alignItems: "center",
1146
- justifyContent: "center",
1147
- width: 56,
1148
- height: 56,
1149
- border: "none",
1150
- borderRadius: 11,
1151
- backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1152
- cursor: "pointer",
1153
- transition: "background-color 0.15s ease",
1154
- pointerEvents: "auto"
1155
- },
1156
- children: icon
1157
- }
1158
- );
1159
- };
1160
- var SkemaToolbar = () => {
1161
- const editor = useEditor();
1162
- const tools = useTools();
1163
- const isSelectSelected = useIsToolSelected(tools["select"]);
1164
- const isDrawSelected = useIsToolSelected(tools["draw"]);
1165
- const isLassoSelected = useIsToolSelected(tools["lasso-select"]);
1166
- const isEraseSelected = useIsToolSelected(tools["eraser"]);
1167
- const [isStarSelected] = useState(false);
1168
- return /* @__PURE__ */ jsxs(
1169
- "div",
1170
- {
1171
- "data-skema": "toolbar",
1172
- style: {
1173
- position: "absolute",
1174
- bottom: 16,
1175
- left: "50%",
1176
- transform: "translateX(-50%)",
1177
- display: "flex",
1178
- alignItems: "center",
1179
- gap: 11,
1180
- padding: "11px 17px",
1181
- backgroundColor: "white",
1182
- borderRadius: 36,
1183
- boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
1184
- pointerEvents: "auto",
1185
- zIndex: 99999
1186
- },
1187
- children: [
1188
- /* @__PURE__ */ jsx(
1189
- ToolbarButton,
1190
- {
1191
- onClick: () => editor.setCurrentTool("select"),
1192
- isSelected: isSelectSelected,
1193
- icon: /* @__PURE__ */ jsx(SelectIcon, { isSelected: isSelectSelected }),
1194
- label: "Select (V)"
1195
- }
1196
- ),
1197
- /* @__PURE__ */ jsx(
1198
- ToolbarButton,
1199
- {
1200
- onClick: () => editor.setCurrentTool("lasso-select"),
1201
- isSelected: isLassoSelected,
1202
- icon: /* @__PURE__ */ jsx(LassoIcon, { isSelected: isLassoSelected }),
1203
- label: "Lasso Select (L)"
1204
- }
1205
- ),
1206
- /* @__PURE__ */ jsx(
1207
- ToolbarButton,
1208
- {
1209
- onClick: () => editor.setCurrentTool("draw"),
1210
- isSelected: isDrawSelected,
1211
- icon: /* @__PURE__ */ jsx(DrawIcon, { isSelected: isDrawSelected }),
1212
- label: "Draw (D)"
1213
- }
1214
- ),
1215
- /* @__PURE__ */ jsx(
1216
- ToolbarButton,
1217
- {
1218
- onClick: () => editor.setCurrentTool("eraser"),
1219
- isSelected: isEraseSelected,
1220
- icon: /* @__PURE__ */ jsx(EraseIcon, { isSelected: isEraseSelected }),
1221
- label: "Eraser (E)"
1222
- }
1223
- ),
1224
- /* @__PURE__ */ jsx(
1225
- "div",
1226
- {
1227
- style: {
1228
- width: 3,
1229
- height: 36,
1230
- backgroundColor: "#C4C2C2",
1231
- borderRadius: 1.5,
1232
- margin: "0 6px"
1233
- }
1234
- }
1235
- ),
1236
- /* @__PURE__ */ jsx(
1237
- ToolbarButton,
1238
- {
1239
- onClick: () => {
1240
- console.log("Star button clicked - placeholder for future feature");
1241
- },
1242
- isSelected: isStarSelected,
1243
- icon: /* @__PURE__ */ jsx(StarIcon, { isSelected: isStarSelected }),
1244
- label: "Special (Coming Soon)"
1245
- }
1246
- )
1247
- ]
1248
- }
1249
- );
1250
- };
1251
- var LassoOverlay = () => {
1252
- const editor = useEditor();
1253
- const lassoPoints = useValue(
1254
- "lasso points",
1255
- () => {
1256
- if (!editor.isIn("lasso-select.lassoing")) return [];
1257
- const lassoing = editor.getStateDescendant("lasso-select.lassoing");
1258
- return lassoing?.points?.get() ?? [];
1259
- },
1260
- [editor]
1261
- );
1262
- const svgPath = useMemo(() => {
1263
- if (lassoPoints.length < 2) return "";
1264
- let path = `M ${lassoPoints[0].x} ${lassoPoints[0].y}`;
1265
- for (let i = 1; i < lassoPoints.length; i++) {
1266
- path += ` L ${lassoPoints[i].x} ${lassoPoints[i].y}`;
1267
- }
1268
- path += " Z";
1269
- return path;
1270
- }, [lassoPoints]);
1271
- if (lassoPoints.length === 0) return null;
1272
- return /* @__PURE__ */ jsx("svg", { className: "tl-overlays__item", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1273
- "path",
1274
- {
1275
- d: svgPath,
1276
- fill: "none",
1277
- stroke: "rgba(59, 130, 246, 1)",
1278
- strokeWidth: "calc(2px / var(--tl-zoom))",
1279
- strokeLinecap: "round",
1280
- strokeLinejoin: "round",
1281
- strokeDasharray: "4 4"
1282
- }
1283
- ) });
1284
- };
1285
- var SkemaOverlays = () => {
1286
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1287
- /* @__PURE__ */ jsx(TldrawOverlays, {}),
1288
- /* @__PURE__ */ jsx(LassoOverlay, {})
1289
- ] });
1290
- };
1291
- var SelectionOverlay = ({ selections }) => {
1292
- const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 });
1293
- useEffect(() => {
1294
- const handleScroll = () => {
1295
- setScrollPos({ x: window.scrollX, y: window.scrollY });
1296
- };
1297
- handleScroll();
1298
- window.addEventListener("scroll", handleScroll, { passive: true });
1299
- return () => window.removeEventListener("scroll", handleScroll);
1300
- }, []);
1301
- return /* @__PURE__ */ jsx(Fragment, { children: selections.map((selection) => {
1302
- const viewportX = selection.boundingBox.x - scrollPos.x;
1303
- const viewportY = selection.boundingBox.y - scrollPos.y;
1304
- return /* @__PURE__ */ jsx(
1305
- "div",
1306
- {
1307
- "data-skema": "selection",
1308
- style: {
1309
- position: "fixed",
1310
- left: viewportX,
1311
- top: viewportY,
1312
- width: selection.boundingBox.width,
1313
- height: selection.boundingBox.height,
1314
- border: "2px solid #10b981",
1315
- backgroundColor: "rgba(16, 185, 129, 0.1)",
1316
- pointerEvents: "none",
1317
- zIndex: 999997
1318
- },
1319
- children: /* @__PURE__ */ jsx(
1320
- "span",
1321
- {
1322
- style: {
1323
- position: "absolute",
1324
- top: -20,
1325
- left: 0,
1326
- backgroundColor: "#10b981",
1327
- color: "white",
1328
- padding: "2px 6px",
1329
- fontSize: "11px",
1330
- borderRadius: "3px",
1331
- whiteSpace: "nowrap"
1332
- },
1333
- children: selection.tagName
1334
- }
1335
- )
1336
- },
1337
- selection.id
1338
- );
1339
- }) });
1340
- };
1341
- var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1342
- const [isOpen, setIsOpen] = useState(false);
1343
- return /* @__PURE__ */ jsxs(
1344
- "div",
1705
+ var AnnotationsSidebar = ({
1706
+ annotations,
1707
+ onClear,
1708
+ onExport
1709
+ }) => {
1710
+ const [isOpen, setIsOpen] = useState(false);
1711
+ return /* @__PURE__ */ jsxs(
1712
+ "div",
1345
1713
  {
1346
1714
  "data-skema": "sidebar",
1347
1715
  style: {
@@ -1443,9 +1811,9 @@ var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1443
1811
  },
1444
1812
  children: [
1445
1813
  /* @__PURE__ */ jsxs("div", { style: { fontWeight: 500, marginBottom: "4px" }, children: [
1446
- annotation.type === "dom_selection" && `\u{1F3AF} ${annotation.tagName}`,
1447
- annotation.type === "drawing" && `\u270F\uFE0F ${annotation.comment || "Drawing"}`,
1448
- annotation.type === "gesture" && `\u{1F446} ${annotation.gesture}`
1814
+ annotation.type === "dom_selection" && `[DOM] ${annotation.tagName}`,
1815
+ annotation.type === "drawing" && `[Draw] ${annotation.comment || "Drawing"}`,
1816
+ annotation.type === "gesture" && `[Gesture] ${annotation.gesture}`
1449
1817
  ] }),
1450
1818
  annotation.type === "dom_selection" && /* @__PURE__ */ jsxs(Fragment, { children: [
1451
1819
  annotation.comment && /* @__PURE__ */ jsx("div", { style: { color: "#374151", fontSize: "12px", marginBottom: "4px" }, children: annotation.comment }),
@@ -1459,37 +1827,556 @@ var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1459
1827
  }
1460
1828
  );
1461
1829
  };
1462
- var Skema = ({
1463
- enabled = true,
1464
- onAnnotationsChange,
1465
- onAnnotationSubmit,
1466
- onAnnotationDelete,
1467
- toggleShortcut = "mod+shift+e",
1468
- initialAnnotations = [],
1469
- zIndex = 99999
1470
- }) => {
1471
- const [isActive, setIsActive] = useState(enabled);
1472
- const [annotations, setAnnotations] = useState(initialAnnotations);
1473
- const [domSelections, setDomSelections] = useState([]);
1474
- const [pendingAnnotation, setPendingAnnotation] = useState(null);
1475
- const [pendingExiting, setPendingExiting] = useState(false);
1476
- const [hoveredMarkerId, setHoveredMarkerId] = useState(null);
1477
- const editorRef = useRef(null);
1478
- const popupRef = useRef(null);
1479
- const lastDoubleClickRef = useRef(0);
1480
- useRef(false);
1481
- const cleanupRef = useRef(null);
1830
+ var SelectionOverlay = ({ selections }) => {
1831
+ const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 });
1832
+ useEffect(() => {
1833
+ const handleScrollOrResize = () => {
1834
+ setScrollPos({ x: window.scrollX, y: window.scrollY });
1835
+ };
1836
+ handleScrollOrResize();
1837
+ window.addEventListener("scroll", handleScrollOrResize, { passive: true });
1838
+ window.addEventListener("resize", handleScrollOrResize);
1839
+ return () => {
1840
+ window.removeEventListener("scroll", handleScrollOrResize);
1841
+ window.removeEventListener("resize", handleScrollOrResize);
1842
+ };
1843
+ }, []);
1844
+ return /* @__PURE__ */ jsx(Fragment, { children: selections.map((selection) => {
1845
+ const viewportX = selection.boundingBox.x - scrollPos.x;
1846
+ const viewportY = selection.boundingBox.y - scrollPos.y;
1847
+ return /* @__PURE__ */ jsx(
1848
+ "div",
1849
+ {
1850
+ "data-skema": "selection",
1851
+ style: {
1852
+ position: "fixed",
1853
+ left: viewportX,
1854
+ top: viewportY,
1855
+ width: selection.boundingBox.width,
1856
+ height: selection.boundingBox.height,
1857
+ border: "2px solid #10b981",
1858
+ backgroundColor: "rgba(16, 185, 129, 0.1)",
1859
+ pointerEvents: "none",
1860
+ zIndex: 999997
1861
+ },
1862
+ children: /* @__PURE__ */ jsx(
1863
+ "span",
1864
+ {
1865
+ style: {
1866
+ position: "absolute",
1867
+ top: -20,
1868
+ left: 0,
1869
+ backgroundColor: "#10b981",
1870
+ color: "white",
1871
+ padding: "2px 6px",
1872
+ fontSize: "11px",
1873
+ borderRadius: "3px",
1874
+ whiteSpace: "nowrap"
1875
+ },
1876
+ children: selection.tagName
1877
+ }
1878
+ )
1879
+ },
1880
+ selection.id
1881
+ );
1882
+ }) });
1883
+ };
1884
+ var ShapeLoader = () => {
1885
+ return /* @__PURE__ */ jsxs(
1886
+ "div",
1887
+ {
1888
+ style: {
1889
+ position: "relative",
1890
+ width: 28,
1891
+ height: 28,
1892
+ display: "flex",
1893
+ alignItems: "center",
1894
+ justifyContent: "center"
1895
+ },
1896
+ children: [
1897
+ /* @__PURE__ */ jsx(
1898
+ "svg",
1899
+ {
1900
+ className: "skema-shape skema-shape-1",
1901
+ viewBox: "0 0 24 24",
1902
+ style: {
1903
+ position: "absolute",
1904
+ width: 24,
1905
+ height: 24
1906
+ },
1907
+ children: /* @__PURE__ */ jsx(
1908
+ "polygon",
1909
+ {
1910
+ points: "12,2 15,9 22,9 16,14 18,22 12,17 6,22 8,14 2,9 9,9",
1911
+ fill: "#F97316"
1912
+ }
1913
+ )
1914
+ }
1915
+ ),
1916
+ /* @__PURE__ */ jsx(
1917
+ "svg",
1918
+ {
1919
+ className: "skema-shape skema-shape-2",
1920
+ viewBox: "0 0 24 24",
1921
+ style: {
1922
+ position: "absolute",
1923
+ width: 24,
1924
+ height: 24
1925
+ },
1926
+ children: /* @__PURE__ */ jsx(
1927
+ "polygon",
1928
+ {
1929
+ points: "6,4 22,4 18,20 2,20",
1930
+ fill: "#FACC15"
1931
+ }
1932
+ )
1933
+ }
1934
+ ),
1935
+ /* @__PURE__ */ jsx(
1936
+ "svg",
1937
+ {
1938
+ className: "skema-shape skema-shape-3",
1939
+ viewBox: "0 0 24 24",
1940
+ style: {
1941
+ position: "absolute",
1942
+ width: 24,
1943
+ height: 24
1944
+ },
1945
+ children: /* @__PURE__ */ jsx(
1946
+ "polygon",
1947
+ {
1948
+ points: "12,3 22,21 2,21",
1949
+ fill: "#EF4444"
1950
+ }
1951
+ )
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ jsx(
1955
+ "svg",
1956
+ {
1957
+ className: "skema-shape skema-shape-4",
1958
+ viewBox: "0 0 24 24",
1959
+ style: {
1960
+ position: "absolute",
1961
+ width: 24,
1962
+ height: 24
1963
+ },
1964
+ children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", fill: "#3B82F6" })
1965
+ }
1966
+ ),
1967
+ /* @__PURE__ */ jsx(
1968
+ "svg",
1969
+ {
1970
+ className: "skema-shape skema-shape-5",
1971
+ viewBox: "0 0 24 24",
1972
+ style: {
1973
+ position: "absolute",
1974
+ width: 24,
1975
+ height: 24
1976
+ },
1977
+ children: /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", fill: "#22C55E" })
1978
+ }
1979
+ )
1980
+ ]
1981
+ }
1982
+ );
1983
+ };
1984
+ var ProcessingOverlayStyles = `
1985
+ @keyframes skema-processing-pulse {
1986
+ 0%, 100% {
1987
+ opacity: 0.7;
1988
+ transform: scale(1);
1989
+ }
1990
+ 50% {
1991
+ opacity: 0.95;
1992
+ transform: scale(1.02);
1993
+ }
1994
+ }
1995
+ @keyframes skema-processing-shimmer {
1996
+ 0% {
1997
+ background-position: -200% 0;
1998
+ }
1999
+ 100% {
2000
+ background-position: 200% 0;
2001
+ }
2002
+ }
2003
+ @keyframes skema-processing-border {
2004
+ 0%, 100% {
2005
+ border-color: rgba(139, 92, 246, 0.85);
2006
+ }
2007
+ 50% {
2008
+ border-color: rgba(139, 92, 246, 1);
2009
+ }
2010
+ }
2011
+
2012
+ /* Shape loader animations */
2013
+ .skema-shape {
2014
+ opacity: 0;
2015
+ transform: scale(0.5) rotate(-180deg);
2016
+ animation: skema-shape-cycle 2.5s ease-in-out infinite;
2017
+ }
2018
+ .skema-shape-1 { animation-delay: 0s; }
2019
+ .skema-shape-2 { animation-delay: 0.5s; }
2020
+ .skema-shape-3 { animation-delay: 1s; }
2021
+ .skema-shape-4 { animation-delay: 1.5s; }
2022
+ .skema-shape-5 { animation-delay: 2s; }
2023
+
2024
+ @keyframes skema-shape-cycle {
2025
+ 0%, 100% {
2026
+ opacity: 0;
2027
+ transform: scale(0.5) rotate(-180deg);
2028
+ }
2029
+ 10%, 30% {
2030
+ opacity: 1;
2031
+ transform: scale(1) rotate(0deg);
2032
+ }
2033
+ 40% {
2034
+ opacity: 0;
2035
+ transform: scale(0.5) rotate(180deg);
2036
+ }
2037
+ }
2038
+ `;
2039
+ var ProcessingOverlay = ({ boundingBox, scrollOffset }) => {
2040
+ const viewportX = boundingBox.x - scrollOffset.x;
2041
+ const viewportY = boundingBox.y - scrollOffset.y;
2042
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2043
+ /* @__PURE__ */ jsx("style", { children: ProcessingOverlayStyles }),
2044
+ /* @__PURE__ */ jsxs(
2045
+ "div",
2046
+ {
2047
+ "data-skema": "processing-overlay",
2048
+ style: {
2049
+ position: "fixed",
2050
+ left: viewportX,
2051
+ top: viewportY,
2052
+ width: boundingBox.width,
2053
+ height: boundingBox.height,
2054
+ border: "3px solid rgba(139, 92, 246, 0.95)",
2055
+ borderRadius: 4,
2056
+ pointerEvents: "none",
2057
+ zIndex: 999998,
2058
+ animation: "skema-processing-pulse 1.5s ease-in-out infinite, skema-processing-border 1.5s ease-in-out infinite",
2059
+ background: "linear-gradient(90deg, rgba(139, 92, 246, 0.15) 0%, rgba(139, 92, 246, 0.35) 50%, rgba(139, 92, 246, 0.15) 100%)",
2060
+ backgroundSize: "200% 100%"
2061
+ },
2062
+ children: [
2063
+ /* @__PURE__ */ jsx(
2064
+ "div",
2065
+ {
2066
+ style: {
2067
+ position: "absolute",
2068
+ inset: 0,
2069
+ background: "linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.25) 50%, transparent 100%)",
2070
+ backgroundSize: "200% 100%",
2071
+ animation: "skema-processing-shimmer 2s linear infinite",
2072
+ borderRadius: 2
2073
+ }
2074
+ }
2075
+ ),
2076
+ /* @__PURE__ */ jsx(
2077
+ "div",
2078
+ {
2079
+ style: {
2080
+ position: "absolute",
2081
+ top: -18,
2082
+ left: "50%",
2083
+ transform: "translateX(-50%)",
2084
+ display: "flex",
2085
+ alignItems: "center",
2086
+ justifyContent: "center",
2087
+ padding: "8px 14px",
2088
+ backgroundColor: "#FFFFFF",
2089
+ borderRadius: 20,
2090
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08)"
2091
+ },
2092
+ children: /* @__PURE__ */ jsx(ShapeLoader, {})
2093
+ }
2094
+ )
2095
+ ]
2096
+ }
2097
+ )
2098
+ ] });
2099
+ };
2100
+ var styles = {
2101
+ popup: {
2102
+ position: "fixed",
2103
+ transform: "translateX(-50%)",
2104
+ width: 280,
2105
+ padding: "12px 16px 14px",
2106
+ background: "#1a1a1a",
2107
+ borderRadius: 16,
2108
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08)",
2109
+ cursor: "default",
2110
+ zIndex: 100001,
2111
+ fontFamily: '"Clash Display", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
2112
+ opacity: 0,
2113
+ transition: "opacity 0.2s ease, transform 0.2s ease"
2114
+ },
2115
+ popupEnter: {
2116
+ opacity: 1,
2117
+ transform: "translateX(-50%) scale(1) translateY(0)"
2118
+ },
2119
+ popupExit: {
2120
+ opacity: 0,
2121
+ transform: "translateX(-50%) scale(0.95) translateY(4px)"
2122
+ },
2123
+ header: {
2124
+ display: "flex",
2125
+ alignItems: "center",
2126
+ justifyContent: "space-between",
2127
+ marginBottom: 9
2128
+ },
2129
+ element: {
2130
+ fontSize: 12,
2131
+ fontWeight: 400,
2132
+ color: "rgba(255, 255, 255, 0.5)",
2133
+ maxWidth: "100%",
2134
+ overflow: "hidden",
2135
+ textOverflow: "ellipsis",
2136
+ whiteSpace: "nowrap",
2137
+ flex: 1
2138
+ },
2139
+ quote: {
2140
+ fontSize: 12,
2141
+ fontStyle: "italic",
2142
+ color: "rgba(255, 255, 255, 0.6)",
2143
+ marginBottom: 8,
2144
+ padding: "6px 8px",
2145
+ background: "rgba(255, 255, 255, 0.05)",
2146
+ borderRadius: 4,
2147
+ lineHeight: 1.45
2148
+ },
2149
+ textarea: {
2150
+ width: "100%",
2151
+ padding: "8px 10px",
2152
+ fontSize: 13,
2153
+ fontFamily: "inherit",
2154
+ background: "rgba(255, 255, 255, 0.05)",
2155
+ color: "#fff",
2156
+ border: "1px solid rgba(255, 255, 255, 0.15)",
2157
+ borderRadius: 8,
2158
+ resize: "none",
2159
+ outline: "none",
2160
+ transition: "border-color 0.15s ease",
2161
+ boxSizing: "border-box"
2162
+ },
2163
+ actions: {
2164
+ display: "flex",
2165
+ justifyContent: "flex-end",
2166
+ gap: 6,
2167
+ marginTop: 8
2168
+ },
2169
+ button: {
2170
+ padding: "6px 14px",
2171
+ fontSize: 12,
2172
+ fontWeight: 500,
2173
+ borderRadius: 16,
2174
+ border: "none",
2175
+ cursor: "pointer",
2176
+ transition: "background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease"
2177
+ },
2178
+ cancelButton: {
2179
+ background: "transparent",
2180
+ color: "rgba(255, 255, 255, 0.5)"
2181
+ },
2182
+ submitButton: {
2183
+ color: "white"
2184
+ }
2185
+ };
2186
+ var AnnotationPopup = forwardRef(
2187
+ function AnnotationPopup2({
2188
+ element,
2189
+ selectedText,
2190
+ placeholder = "What should change?",
2191
+ initialValue = "",
2192
+ submitLabel = "Add",
2193
+ onSubmit,
2194
+ onCancel,
2195
+ style,
2196
+ accentColor = "#3c82f7",
2197
+ isExiting = false,
2198
+ isMultiSelect = false
2199
+ }, ref) {
2200
+ const [text, setText] = useState(initialValue);
2201
+ const [isShaking, setIsShaking] = useState(false);
2202
+ const [animState, setAnimState] = useState("initial");
2203
+ const [isFocused, setIsFocused] = useState(false);
2204
+ const textareaRef = useRef(null);
2205
+ const popupRef = useRef(null);
2206
+ useEffect(() => {
2207
+ if (isExiting && animState !== "exit") {
2208
+ setAnimState("exit");
2209
+ }
2210
+ }, [isExiting, animState]);
2211
+ useEffect(() => {
2212
+ requestAnimationFrame(() => {
2213
+ setAnimState("enter");
2214
+ });
2215
+ const enterTimer = setTimeout(() => {
2216
+ setAnimState("entered");
2217
+ }, 200);
2218
+ const focusTimer = setTimeout(() => {
2219
+ const textarea = textareaRef.current;
2220
+ if (textarea) {
2221
+ textarea.focus();
2222
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
2223
+ textarea.scrollTop = textarea.scrollHeight;
2224
+ }
2225
+ }, 50);
2226
+ return () => {
2227
+ clearTimeout(enterTimer);
2228
+ clearTimeout(focusTimer);
2229
+ };
2230
+ }, []);
2231
+ const shake = useCallback(() => {
2232
+ setIsShaking(true);
2233
+ setTimeout(() => {
2234
+ setIsShaking(false);
2235
+ textareaRef.current?.focus();
2236
+ }, 250);
2237
+ }, []);
2238
+ useImperativeHandle(ref, () => ({
2239
+ shake
2240
+ }), [shake]);
2241
+ const handleCancel = useCallback(() => {
2242
+ setAnimState("exit");
2243
+ setTimeout(() => {
2244
+ onCancel();
2245
+ }, 150);
2246
+ }, [onCancel]);
2247
+ const handleSubmit = useCallback(() => {
2248
+ if (!text.trim()) return;
2249
+ onSubmit(text.trim());
2250
+ }, [text, onSubmit]);
2251
+ const handleKeyDown = useCallback(
2252
+ (e) => {
2253
+ if (e.nativeEvent.isComposing) return;
2254
+ if (e.key === "Enter" && !e.shiftKey) {
2255
+ e.preventDefault();
2256
+ handleSubmit();
2257
+ }
2258
+ if (e.key === "Escape") {
2259
+ handleCancel();
2260
+ }
2261
+ },
2262
+ [handleSubmit, handleCancel]
2263
+ );
2264
+ const popupStyle = {
2265
+ ...styles.popup,
2266
+ ...animState === "enter" || animState === "entered" ? styles.popupEnter : {},
2267
+ ...animState === "exit" ? styles.popupExit : {},
2268
+ ...isShaking ? {
2269
+ animation: "skema-shake 0.25s ease-out"
2270
+ } : {},
2271
+ ...style
2272
+ };
2273
+ const textareaStyle = {
2274
+ ...styles.textarea,
2275
+ ...isFocused ? { borderColor: accentColor } : {}
2276
+ };
2277
+ const effectiveAccentColor = isMultiSelect ? "#34C759" : accentColor;
2278
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2279
+ /* @__PURE__ */ jsx("style", { children: `
2280
+ @keyframes skema-shake {
2281
+ 0%, 100% { transform: translateX(-50%) scale(1) translateY(0) translateX(0); }
2282
+ 20% { transform: translateX(-50%) scale(1) translateY(0) translateX(-3px); }
2283
+ 40% { transform: translateX(-50%) scale(1) translateY(0) translateX(3px); }
2284
+ 60% { transform: translateX(-50%) scale(1) translateY(0) translateX(-2px); }
2285
+ 80% { transform: translateX(-50%) scale(1) translateY(0) translateX(2px); }
2286
+ }
2287
+ ` }),
2288
+ /* @__PURE__ */ jsxs(
2289
+ "div",
2290
+ {
2291
+ ref: popupRef,
2292
+ "data-skema": "annotation-popup",
2293
+ style: popupStyle,
2294
+ onClick: (e) => e.stopPropagation(),
2295
+ onPointerDown: (e) => e.stopPropagation(),
2296
+ children: [
2297
+ /* @__PURE__ */ jsx("div", { style: styles.header, children: /* @__PURE__ */ jsx("span", { style: styles.element, children: element }) }),
2298
+ selectedText && /* @__PURE__ */ jsxs("div", { style: styles.quote, children: [
2299
+ "\u201C",
2300
+ selectedText.slice(0, 80),
2301
+ selectedText.length > 80 ? "..." : "",
2302
+ "\u201D"
2303
+ ] }),
2304
+ /* @__PURE__ */ jsx(
2305
+ "textarea",
2306
+ {
2307
+ ref: textareaRef,
2308
+ style: textareaStyle,
2309
+ placeholder,
2310
+ value: text,
2311
+ onChange: (e) => setText(e.target.value),
2312
+ onFocus: () => setIsFocused(true),
2313
+ onBlur: () => setIsFocused(false),
2314
+ rows: 2,
2315
+ onKeyDown: handleKeyDown
2316
+ }
2317
+ ),
2318
+ /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
2319
+ /* @__PURE__ */ jsx(
2320
+ "button",
2321
+ {
2322
+ style: { ...styles.button, ...styles.cancelButton },
2323
+ onClick: handleCancel,
2324
+ onMouseEnter: (e) => {
2325
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
2326
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.8)";
2327
+ },
2328
+ onMouseLeave: (e) => {
2329
+ e.currentTarget.style.background = "transparent";
2330
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.5)";
2331
+ },
2332
+ children: "Cancel"
2333
+ }
2334
+ ),
2335
+ /* @__PURE__ */ jsx(
2336
+ "button",
2337
+ {
2338
+ style: {
2339
+ ...styles.button,
2340
+ ...styles.submitButton,
2341
+ backgroundColor: effectiveAccentColor,
2342
+ opacity: text.trim() ? 1 : 0.4
2343
+ },
2344
+ onClick: handleSubmit,
2345
+ disabled: !text.trim(),
2346
+ onMouseEnter: (e) => {
2347
+ if (text.trim()) {
2348
+ e.currentTarget.style.filter = "brightness(0.9)";
2349
+ }
2350
+ },
2351
+ onMouseLeave: (e) => {
2352
+ e.currentTarget.style.filter = "none";
2353
+ },
2354
+ children: submitLabel
2355
+ }
2356
+ )
2357
+ ] })
2358
+ ]
2359
+ }
2360
+ )
2361
+ ] });
2362
+ }
2363
+ );
2364
+ function useKeyboardShortcuts({ onToggle, shortcut = "mod+shift+e" }) {
1482
2365
  useEffect(() => {
1483
2366
  const handleKeyDown = (e) => {
1484
2367
  const isMod = e.metaKey || e.ctrlKey;
1485
- if (isMod && e.shiftKey && e.key.toLowerCase() === "e") {
1486
- e.preventDefault();
1487
- setIsActive((prev) => !prev);
2368
+ if (shortcut === "mod+shift+e") {
2369
+ if (isMod && e.shiftKey && e.key.toLowerCase() === "e") {
2370
+ e.preventDefault();
2371
+ onToggle();
2372
+ }
1488
2373
  }
1489
2374
  };
1490
2375
  window.addEventListener("keydown", handleKeyDown);
1491
2376
  return () => window.removeEventListener("keydown", handleKeyDown);
1492
- }, []);
2377
+ }, [onToggle, shortcut]);
2378
+ }
2379
+ function useScrollSync(isActive, editorRef) {
1493
2380
  const [scrollOffset, setScrollOffset] = useState({ x: 0, y: 0 });
1494
2381
  useEffect(() => {
1495
2382
  if (!isActive) return;
@@ -1502,10 +2389,15 @@ var Skema = ({
1502
2389
  };
1503
2390
  syncScroll();
1504
2391
  window.addEventListener("scroll", syncScroll, { passive: true });
2392
+ window.addEventListener("resize", syncScroll);
1505
2393
  return () => {
1506
2394
  window.removeEventListener("scroll", syncScroll);
2395
+ window.removeEventListener("resize", syncScroll);
1507
2396
  };
1508
- }, [isActive]);
2397
+ }, [isActive, editorRef]);
2398
+ return scrollOffset;
2399
+ }
2400
+ function useWheelIntercept(isActive) {
1509
2401
  useEffect(() => {
1510
2402
  if (!isActive) return;
1511
2403
  const handleWheel = (e) => {
@@ -1524,9 +2416,586 @@ var Skema = ({
1524
2416
  document.removeEventListener("wheel", handleWheel, { capture: true });
1525
2417
  };
1526
2418
  }, [isActive]);
2419
+ }
2420
+ function useShapePersistence(isActive, editorRef) {
2421
+ const savedShapesRef = useRef(null);
2422
+ const wasActiveRef = useRef(isActive);
2423
+ useEffect(() => {
2424
+ const editor = editorRef.current;
2425
+ if (wasActiveRef.current && !isActive && editor) {
2426
+ const allShapes = editor.getCurrentPageShapes();
2427
+ const drawingShapes = allShapes.filter(
2428
+ (shape) => ["draw", "line", "arrow", "geo", "text", "note", "frame"].includes(shape.type)
2429
+ );
2430
+ if (drawingShapes.length > 0) {
2431
+ const shapeRecords = {};
2432
+ for (const shape of drawingShapes) {
2433
+ shapeRecords[shape.id] = shape;
2434
+ }
2435
+ savedShapesRef.current = shapeRecords;
2436
+ const shapeIds = drawingShapes.map((s) => s.id);
2437
+ editor.deleteShapes(shapeIds);
2438
+ console.log(`[Skema] Hiding: saved ${drawingShapes.length} shape(s) to memory`);
2439
+ }
2440
+ }
2441
+ wasActiveRef.current = isActive;
2442
+ }, [isActive, editorRef]);
2443
+ useEffect(() => {
2444
+ if (!isActive) return;
2445
+ const editor = editorRef.current;
2446
+ if (!editor || !savedShapesRef.current) return;
2447
+ const timeoutId = setTimeout(() => {
2448
+ const savedShapes = savedShapesRef.current;
2449
+ const currentEditor = editorRef.current;
2450
+ if (!savedShapes || !currentEditor) return;
2451
+ const shapesToRestore = Object.values(savedShapes);
2452
+ if (shapesToRestore.length > 0) {
2453
+ currentEditor.createShapes(shapesToRestore);
2454
+ console.log(`[Skema] Restoring: loaded ${shapesToRestore.length} shape(s) from memory`);
2455
+ savedShapesRef.current = null;
2456
+ }
2457
+ }, 100);
2458
+ return () => clearTimeout(timeoutId);
2459
+ }, [isActive, editorRef]);
2460
+ }
2461
+
2462
+ // src/utils/gesture-recognizer.ts
2463
+ function angleBetweenVectors(v1, v2) {
2464
+ const dot = v1.x * v2.x + v1.y * v2.y;
2465
+ const mag1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
2466
+ const mag2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
2467
+ if (mag1 === 0 || mag2 === 0) return 0;
2468
+ const cosAngle = Math.max(-1, Math.min(1, dot / (mag1 * mag2)));
2469
+ return Math.acos(cosAngle);
2470
+ }
2471
+ function calculatePathLength(points) {
2472
+ let length = 0;
2473
+ for (let i = 1; i < points.length; i++) {
2474
+ const dx = points[i].x - points[i - 1].x;
2475
+ const dy = points[i].y - points[i - 1].y;
2476
+ length += Math.sqrt(dx * dx + dy * dy);
2477
+ }
2478
+ return length;
2479
+ }
2480
+ function calculateBoundingBox(points) {
2481
+ if (points.length === 0) {
2482
+ return { width: 0, height: 0, diagonal: 0 };
2483
+ }
2484
+ let minX = Infinity, maxX = -Infinity;
2485
+ let minY = Infinity, maxY = -Infinity;
2486
+ for (const point of points) {
2487
+ minX = Math.min(minX, point.x);
2488
+ maxX = Math.max(maxX, point.x);
2489
+ minY = Math.min(minY, point.y);
2490
+ maxY = Math.max(maxY, point.y);
2491
+ }
2492
+ const width = maxX - minX;
2493
+ const height = maxY - minY;
2494
+ const diagonal = Math.sqrt(width * width + height * height);
2495
+ return { width, height, diagonal };
2496
+ }
2497
+ function getPointsBounds(points) {
2498
+ if (points.length === 0) {
2499
+ return null;
2500
+ }
2501
+ let minX = Infinity, maxX = -Infinity;
2502
+ let minY = Infinity, maxY = -Infinity;
2503
+ for (const point of points) {
2504
+ minX = Math.min(minX, point.x);
2505
+ maxX = Math.max(maxX, point.x);
2506
+ minY = Math.min(minY, point.y);
2507
+ maxY = Math.max(maxY, point.y);
2508
+ }
2509
+ return {
2510
+ minX,
2511
+ minY,
2512
+ maxX,
2513
+ maxY,
2514
+ width: maxX - minX,
2515
+ height: maxY - minY
2516
+ };
2517
+ }
2518
+ function countDirectionChanges(points, angleThreshold = Math.PI / 2) {
2519
+ if (points.length < 3) return 0;
2520
+ let changes = 0;
2521
+ const sampleRate = Math.max(1, Math.floor(points.length / 50));
2522
+ const sampledPoints = [];
2523
+ for (let i = 0; i < points.length; i += sampleRate) {
2524
+ sampledPoints.push(points[i]);
2525
+ }
2526
+ if (sampledPoints[sampledPoints.length - 1] !== points[points.length - 1]) {
2527
+ sampledPoints.push(points[points.length - 1]);
2528
+ }
2529
+ for (let i = 1; i < sampledPoints.length - 1; i++) {
2530
+ const v1 = {
2531
+ x: sampledPoints[i].x - sampledPoints[i - 1].x,
2532
+ y: sampledPoints[i].y - sampledPoints[i - 1].y
2533
+ };
2534
+ const v2 = {
2535
+ x: sampledPoints[i + 1].x - sampledPoints[i].x,
2536
+ y: sampledPoints[i + 1].y - sampledPoints[i].y
2537
+ };
2538
+ const angle = angleBetweenVectors(v1, v2);
2539
+ if (angle > angleThreshold) {
2540
+ changes++;
2541
+ }
2542
+ }
2543
+ return changes;
2544
+ }
2545
+ function isRealtimeScribble(points) {
2546
+ if (points.length < 15) {
2547
+ return {
2548
+ isScribble: false,
2549
+ confidence: 0,
2550
+ directionChanges: 0,
2551
+ compactness: 0
2552
+ };
2553
+ }
2554
+ const directionChanges = countDirectionChanges(points, Math.PI / 3);
2555
+ const pathLength = calculatePathLength(points);
2556
+ const { diagonal } = calculateBoundingBox(points);
2557
+ const compactness = diagonal > 0 ? pathLength / diagonal : 0;
2558
+ const minDirectionChanges = 5;
2559
+ const minCompactness = 1.8;
2560
+ const minPathLength = 80;
2561
+ const meetsDirectionCriteria = directionChanges >= minDirectionChanges;
2562
+ const meetsCompactnessCriteria = compactness >= minCompactness;
2563
+ const meetsLengthCriteria = pathLength >= minPathLength;
2564
+ const isScribble = meetsDirectionCriteria && meetsCompactnessCriteria && meetsLengthCriteria;
2565
+ let confidence = 0;
2566
+ if (isScribble) {
2567
+ const directionScore = Math.min(1, (directionChanges - minDirectionChanges) / 4);
2568
+ const compactnessScore = Math.min(1, (compactness - minCompactness) / 2);
2569
+ confidence = (directionScore + compactnessScore) / 2;
2570
+ }
2571
+ return {
2572
+ isScribble,
2573
+ confidence,
2574
+ directionChanges,
2575
+ compactness
2576
+ };
2577
+ }
2578
+ function findOverlappingShapesFromBounds(editor, bounds, excludeIds = []) {
2579
+ const allShapes = editor.getCurrentPageShapes();
2580
+ const overlapping = [];
2581
+ for (const shape of allShapes) {
2582
+ if (excludeIds.includes(shape.id)) continue;
2583
+ if (!["draw", "line", "arrow", "geo", "text", "note", "frame"].includes(shape.type)) {
2584
+ continue;
2585
+ }
2586
+ const shapeBounds = editor.getShapePageBounds(shape.id);
2587
+ if (shapeBounds) {
2588
+ const intersects = !(bounds.maxX < shapeBounds.x || bounds.minX > shapeBounds.x + shapeBounds.w || bounds.maxY < shapeBounds.y || bounds.minY > shapeBounds.y + shapeBounds.h);
2589
+ if (intersects) {
2590
+ overlapping.push(shape.id);
2591
+ }
2592
+ }
2593
+ }
2594
+ return overlapping;
2595
+ }
2596
+
2597
+ // src/hooks/useScribbleDelete.ts
2598
+ function useScribbleDelete({
2599
+ isActive,
2600
+ editorRef,
2601
+ setAnnotations,
2602
+ setScribbleToast
2603
+ }) {
2604
+ const scribblePointsRef = useRef([]);
2605
+ const isDrawingRef = useRef(false);
2606
+ const scribbleDetectedRef = useRef(false);
2607
+ useEffect(() => {
2608
+ if (!isActive) return;
2609
+ const handleScribbleDelete = (overlappingIds) => {
2610
+ const editor = editorRef.current;
2611
+ if (!editor || overlappingIds.length === 0) return;
2612
+ editor.cancel();
2613
+ editor.deleteShapes(overlappingIds);
2614
+ setAnnotations((prev) => prev.filter((annotation) => {
2615
+ if (annotation.type === "drawing") {
2616
+ const drawingShapes = annotation.shapes;
2617
+ return !drawingShapes.some(
2618
+ (shapeId) => overlappingIds.includes(shapeId)
2619
+ );
2620
+ }
2621
+ return true;
2622
+ }));
2623
+ const message = overlappingIds.length === 1 ? "Deleted 1 shape" : `Deleted ${overlappingIds.length} shapes`;
2624
+ setScribbleToast(message);
2625
+ setTimeout(() => setScribbleToast(null), 2e3);
2626
+ console.log(`[Skema] Scribble-delete: removed ${overlappingIds.length} shape(s)`);
2627
+ };
2628
+ const handlePointerDown = (e) => {
2629
+ const editor = editorRef.current;
2630
+ if (!editor) return;
2631
+ const currentTool = editor.getCurrentToolId();
2632
+ if (currentTool !== "draw") return;
2633
+ isDrawingRef.current = true;
2634
+ scribbleDetectedRef.current = false;
2635
+ scribblePointsRef.current = [{
2636
+ x: e.clientX + window.scrollX,
2637
+ y: e.clientY + window.scrollY
2638
+ }];
2639
+ };
2640
+ const handlePointerMove = (e) => {
2641
+ const editor = editorRef.current;
2642
+ if (!editor || !isDrawingRef.current || scribbleDetectedRef.current) return;
2643
+ const currentTool = editor.getCurrentToolId();
2644
+ if (currentTool !== "draw") {
2645
+ isDrawingRef.current = false;
2646
+ return;
2647
+ }
2648
+ const newPoint = {
2649
+ x: e.clientX + window.scrollX,
2650
+ y: e.clientY + window.scrollY
2651
+ };
2652
+ const points = scribblePointsRef.current;
2653
+ const lastPoint = points[points.length - 1];
2654
+ if (lastPoint) {
2655
+ const dx = newPoint.x - lastPoint.x;
2656
+ const dy = newPoint.y - lastPoint.y;
2657
+ const dist = Math.sqrt(dx * dx + dy * dy);
2658
+ if (dist < 3) return;
2659
+ }
2660
+ points.push(newPoint);
2661
+ scribblePointsRef.current = points;
2662
+ if (points.length >= 20 && points.length % 5 === 0) {
2663
+ const gestureResult = isRealtimeScribble(points);
2664
+ if (gestureResult.isScribble) {
2665
+ const bounds = getPointsBounds(points);
2666
+ if (bounds) {
2667
+ const overlappingIds = findOverlappingShapesFromBounds(editor, bounds, []);
2668
+ if (overlappingIds.length > 0) {
2669
+ scribbleDetectedRef.current = true;
2670
+ isDrawingRef.current = false;
2671
+ handleScribbleDelete(overlappingIds);
2672
+ }
2673
+ }
2674
+ }
2675
+ }
2676
+ };
2677
+ const handlePointerUp = () => {
2678
+ isDrawingRef.current = false;
2679
+ scribblePointsRef.current = [];
2680
+ scribbleDetectedRef.current = false;
2681
+ };
2682
+ document.addEventListener("pointerdown", handlePointerDown, { capture: true });
2683
+ document.addEventListener("pointermove", handlePointerMove, { capture: true });
2684
+ document.addEventListener("pointerup", handlePointerUp, { capture: true });
2685
+ document.addEventListener("pointercancel", handlePointerUp, { capture: true });
2686
+ return () => {
2687
+ document.removeEventListener("pointerdown", handlePointerDown, { capture: true });
2688
+ document.removeEventListener("pointermove", handlePointerMove, { capture: true });
2689
+ document.removeEventListener("pointerup", handlePointerUp, { capture: true });
2690
+ document.removeEventListener("pointercancel", handlePointerUp, { capture: true });
2691
+ };
2692
+ }, [isActive, editorRef, setAnnotations, setScribbleToast]);
2693
+ }
2694
+ var LassoOverlay = () => {
2695
+ const editor = useEditor();
2696
+ const lassoPoints = useValue(
2697
+ "lasso points",
2698
+ () => {
2699
+ if (!editor.isIn("lasso-select.lassoing")) return [];
2700
+ const lassoing = editor.getStateDescendant("lasso-select.lassoing");
2701
+ return lassoing?.points?.get() ?? [];
2702
+ },
2703
+ [editor]
2704
+ );
2705
+ const svgPath = useMemo(() => {
2706
+ if (lassoPoints.length < 2) return "";
2707
+ let path = `M ${lassoPoints[0].x} ${lassoPoints[0].y}`;
2708
+ for (let i = 1; i < lassoPoints.length; i++) {
2709
+ path += ` L ${lassoPoints[i].x} ${lassoPoints[i].y}`;
2710
+ }
2711
+ path += " Z";
2712
+ return path;
2713
+ }, [lassoPoints]);
2714
+ if (lassoPoints.length === 0) return null;
2715
+ return /* @__PURE__ */ jsx("svg", { className: "tl-overlays__item", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
2716
+ "path",
2717
+ {
2718
+ d: svgPath,
2719
+ fill: "none",
2720
+ stroke: "rgba(59, 130, 246, 1)",
2721
+ strokeWidth: "calc(2px / var(--tl-zoom))",
2722
+ strokeLinecap: "round",
2723
+ strokeLinejoin: "round",
2724
+ strokeDasharray: "4 4"
2725
+ }
2726
+ ) });
2727
+ };
2728
+ var SkemaOverlays = () => {
2729
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2730
+ /* @__PURE__ */ jsx(TldrawOverlays, {}),
2731
+ /* @__PURE__ */ jsx(LassoOverlay, {})
2732
+ ] });
2733
+ };
2734
+
2735
+ // src/lib/tldrawConfig.ts
2736
+ var skemaComponents = {
2737
+ Toolbar: null,
2738
+ Overlays: SkemaOverlays,
2739
+ // Hide background to make canvas transparent (so website shows through)
2740
+ Background: null,
2741
+ // Hide UI elements we don't need
2742
+ SharePanel: null,
2743
+ MenuPanel: null,
2744
+ TopPanel: null,
2745
+ PageMenu: null,
2746
+ NavigationPanel: null,
2747
+ HelpMenu: null,
2748
+ Minimap: null,
2749
+ // Hide "Back to Content" button (HelperButtons contains this)
2750
+ HelperButtons: null,
2751
+ QuickActions: null,
2752
+ ZoomMenu: null,
2753
+ ActionsMenu: null,
2754
+ DebugPanel: null,
2755
+ DebugMenu: null,
2756
+ // Hide canvas overlays
2757
+ OnTheCanvas: null,
2758
+ InFrontOfTheCanvas: null
2759
+ };
2760
+ var skemaOverrides = {
2761
+ tools(editor, tools) {
2762
+ return {
2763
+ ...tools,
2764
+ "select": {
2765
+ ...tools["select"],
2766
+ kbd: "s"
2767
+ },
2768
+ "lasso-select": {
2769
+ id: "lasso-select",
2770
+ label: "Lasso Select",
2771
+ icon: "blob",
2772
+ kbd: "l",
2773
+ onSelect: () => {
2774
+ editor.setCurrentTool("lasso-select");
2775
+ }
2776
+ }
2777
+ };
2778
+ }
2779
+ };
2780
+ var skemaHiddenUiStyles = `
2781
+ .tlui-button[data-testid="back-to-content"],
2782
+ .tlui-offscreen-indicator,
2783
+ [class*="back-to-content"],
2784
+ .tl-offscreen-indicator {
2785
+ display: none !important;
2786
+ }
2787
+ `;
2788
+ var skemaToastStyles = `
2789
+ @keyframes skema-toast-fade {
2790
+ from {
2791
+ opacity: 0;
2792
+ transform: translateX(-50%) translateY(10px);
2793
+ }
2794
+ to {
2795
+ opacity: 1;
2796
+ transform: translateX(-50%) translateY(0);
2797
+ }
2798
+ }
2799
+ `;
2800
+ var Skema = ({
2801
+ enabled = true,
2802
+ daemonUrl = "ws://localhost:9999",
2803
+ onAnnotationsChange,
2804
+ onAnnotationSubmit: externalOnAnnotationSubmit,
2805
+ onAnnotationDelete: externalOnAnnotationDelete,
2806
+ onProcessingCancel: externalOnProcessingCancel,
2807
+ toggleShortcut = "mod+shift+e",
2808
+ initialAnnotations = [],
2809
+ zIndex = 99999,
2810
+ isProcessing: externalIsProcessing
2811
+ }) => {
2812
+ const [isActive, setIsActive] = useState(enabled);
2813
+ const [annotations, setAnnotations] = useState(initialAnnotations);
2814
+ const [domSelections, setDomSelections] = useState([]);
2815
+ const [pendingAnnotation, setPendingAnnotation] = useState(null);
2816
+ const [pendingExiting, setPendingExiting] = useState(false);
2817
+ const [hoveredMarkerId, setHoveredMarkerId] = useState(null);
2818
+ const [processingBoundingBox, setProcessingBoundingBox] = useState(null);
2819
+ const [scribbleToast, setScribbleToast] = useState(null);
2820
+ const [internalIsProcessing, setInternalIsProcessing] = useState(false);
2821
+ const [isToolbarExpanded, setIsToolbarExpanded] = useState(false);
2822
+ const [isStylePanelOpen, setIsStylePanelOpen] = useState(false);
2823
+ const annotationChangesRef = useRef(/* @__PURE__ */ new Map());
2824
+ const {
2825
+ state: daemonState,
2826
+ isGenerating,
2827
+ generate,
2828
+ revert
2829
+ } = useDaemon({
2830
+ url: daemonUrl || "ws://localhost:9999",
2831
+ autoConnect: daemonUrl !== null,
2832
+ autoReconnect: daemonUrl !== null
2833
+ });
2834
+ const isProcessing = externalIsProcessing !== void 0 ? externalIsProcessing : internalIsProcessing || isGenerating;
2835
+ const internalOnAnnotationSubmit = useCallback(async (annotation, comment) => {
2836
+ if (!daemonState.connected) {
2837
+ console.warn("[Skema] Not connected to daemon. Run: npx skema-serve");
2838
+ return;
2839
+ }
2840
+ setInternalIsProcessing(true);
2841
+ try {
2842
+ const result = await generate(
2843
+ { ...annotation, comment },
2844
+ (event) => {
2845
+ if (event.type === "text") {
2846
+ console.log(`%c[Skema ${daemonState.provider}]`, "color: #10b981", event.content);
2847
+ } else if (event.type === "error") {
2848
+ console.error("[Skema Error]", event.content);
2849
+ }
2850
+ }
2851
+ );
2852
+ if (result.annotationId) {
2853
+ annotationChangesRef.current.set(annotation.id, result.annotationId);
2854
+ }
2855
+ } catch (error) {
2856
+ console.error("[Skema] Failed to generate:", error);
2857
+ } finally {
2858
+ setInternalIsProcessing(false);
2859
+ }
2860
+ }, [daemonState.connected, daemonState.provider, generate]);
2861
+ const internalOnAnnotationDelete = useCallback(async (annotationId) => {
2862
+ const trackedId = annotationChangesRef.current.get(annotationId);
2863
+ if (!trackedId) {
2864
+ return;
2865
+ }
2866
+ try {
2867
+ await revert(trackedId);
2868
+ annotationChangesRef.current.delete(annotationId);
2869
+ } catch (error) {
2870
+ console.error("[Skema] Failed to revert:", error);
2871
+ }
2872
+ }, [revert]);
2873
+ const internalOnProcessingCancel = useCallback(() => {
2874
+ setInternalIsProcessing(false);
2875
+ }, []);
2876
+ const onAnnotationSubmit = externalOnAnnotationSubmit || (daemonUrl !== null ? internalOnAnnotationSubmit : void 0);
2877
+ const onAnnotationDelete = externalOnAnnotationDelete || (daemonUrl !== null ? internalOnAnnotationDelete : void 0);
2878
+ const onProcessingCancel = externalOnProcessingCancel || internalOnProcessingCancel;
2879
+ const editorRef = useRef(null);
2880
+ const popupRef = useRef(null);
2881
+ const lastDoubleClickRef = useRef(0);
2882
+ const cleanupRef = useRef(null);
2883
+ useKeyboardShortcuts({
2884
+ onToggle: useCallback(() => setIsActive((prev) => !prev), []),
2885
+ shortcut: toggleShortcut
2886
+ });
2887
+ const scrollOffset = useScrollSync(isActive, editorRef);
2888
+ useWheelIntercept(isActive);
2889
+ useShapePersistence(isActive, editorRef);
2890
+ useScribbleDelete({
2891
+ isActive,
2892
+ editorRef,
2893
+ setAnnotations,
2894
+ setScribbleToast
2895
+ });
1527
2896
  useEffect(() => {
1528
2897
  onAnnotationsChange?.(annotations);
1529
2898
  }, [annotations, onAnnotationsChange]);
2899
+ useEffect(() => {
2900
+ if (!isProcessing) {
2901
+ setProcessingBoundingBox(null);
2902
+ }
2903
+ }, [isProcessing]);
2904
+ useEffect(() => {
2905
+ return () => {
2906
+ if (cleanupRef.current) {
2907
+ cleanupRef.current();
2908
+ }
2909
+ };
2910
+ }, []);
2911
+ useEffect(() => {
2912
+ if (!isActive) return;
2913
+ const handleResize = () => {
2914
+ setDomSelections((prevSelections) => {
2915
+ if (prevSelections.length === 0) return prevSelections;
2916
+ return prevSelections.map((selection) => {
2917
+ if (selection.isMultiSelect && selection.elements) {
2918
+ const updatedElements = selection.elements.map((el) => {
2919
+ const domElement2 = document.querySelector(el.selector);
2920
+ if (domElement2) {
2921
+ return { ...el, boundingBox: getBoundingBox(domElement2) };
2922
+ }
2923
+ return el;
2924
+ });
2925
+ const validElements = updatedElements.filter((el) => {
2926
+ const domEl = document.querySelector(el.selector);
2927
+ return domEl !== null;
2928
+ });
2929
+ if (validElements.length > 0) {
2930
+ const minX = Math.min(...updatedElements.map((e) => e.boundingBox.x));
2931
+ const minY = Math.min(...updatedElements.map((e) => e.boundingBox.y));
2932
+ const maxX = Math.max(...updatedElements.map((e) => e.boundingBox.x + e.boundingBox.width));
2933
+ const maxY = Math.max(...updatedElements.map((e) => e.boundingBox.y + e.boundingBox.height));
2934
+ return {
2935
+ ...selection,
2936
+ elements: updatedElements,
2937
+ boundingBox: {
2938
+ x: minX,
2939
+ y: minY,
2940
+ width: maxX - minX,
2941
+ height: maxY - minY
2942
+ }
2943
+ };
2944
+ }
2945
+ return selection;
2946
+ }
2947
+ const domElement = document.querySelector(selection.selector);
2948
+ if (domElement) {
2949
+ return { ...selection, boundingBox: getBoundingBox(domElement) };
2950
+ }
2951
+ return selection;
2952
+ });
2953
+ });
2954
+ setAnnotations((prevAnnotations) => {
2955
+ if (prevAnnotations.length === 0) return prevAnnotations;
2956
+ return prevAnnotations.map((annotation) => {
2957
+ if (annotation.type !== "dom_selection") return annotation;
2958
+ if (annotation.isMultiSelect && annotation.elements) {
2959
+ const updatedElements = annotation.elements.map((el) => {
2960
+ const domElement2 = document.querySelector(el.selector);
2961
+ if (domElement2) {
2962
+ return { ...el, boundingBox: getBoundingBox(domElement2) };
2963
+ }
2964
+ return el;
2965
+ });
2966
+ const validElements = updatedElements.filter((el) => {
2967
+ const domEl = document.querySelector(el.selector);
2968
+ return domEl !== null;
2969
+ });
2970
+ if (validElements.length > 0) {
2971
+ const minX = Math.min(...updatedElements.map((e) => e.boundingBox.x));
2972
+ const minY = Math.min(...updatedElements.map((e) => e.boundingBox.y));
2973
+ const maxX = Math.max(...updatedElements.map((e) => e.boundingBox.x + e.boundingBox.width));
2974
+ const maxY = Math.max(...updatedElements.map((e) => e.boundingBox.y + e.boundingBox.height));
2975
+ return {
2976
+ ...annotation,
2977
+ elements: updatedElements,
2978
+ boundingBox: {
2979
+ x: minX,
2980
+ y: minY,
2981
+ width: maxX - minX,
2982
+ height: maxY - minY
2983
+ }
2984
+ };
2985
+ }
2986
+ return annotation;
2987
+ }
2988
+ const domElement = document.querySelector(annotation.selector);
2989
+ if (domElement) {
2990
+ return { ...annotation, boundingBox: getBoundingBox(domElement) };
2991
+ }
2992
+ return annotation;
2993
+ });
2994
+ });
2995
+ };
2996
+ window.addEventListener("resize", handleResize);
2997
+ return () => window.removeEventListener("resize", handleResize);
2998
+ }, [isActive]);
1530
2999
  const getSelectedDrawings = useCallback(() => {
1531
3000
  if (!editorRef.current) return [];
1532
3001
  const editor = editorRef.current;
@@ -1536,6 +3005,32 @@ var Skema = ({
1536
3005
  (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
1537
3006
  );
1538
3007
  }, []);
3008
+ const findDOMElementsInBounds = useCallback((bounds) => {
3009
+ const elements = [];
3010
+ const allElements = document.querySelectorAll("*");
3011
+ allElements.forEach((el) => {
3012
+ if (!(el instanceof HTMLElement)) return;
3013
+ if (shouldIgnoreElement(el)) return;
3014
+ const rect = el.getBoundingClientRect();
3015
+ const elBounds = {
3016
+ x: rect.left,
3017
+ y: rect.top,
3018
+ width: rect.width,
3019
+ height: rect.height
3020
+ };
3021
+ if (elBounds.width < 10 || elBounds.height < 10) return;
3022
+ if (bboxIntersects(bounds, elBounds)) {
3023
+ const isParent = elements.some((existing) => el.contains(existing));
3024
+ if (!isParent) {
3025
+ const filtered = elements.filter((existing) => !existing.contains(el) && !el.contains(existing));
3026
+ filtered.push(el);
3027
+ elements.length = 0;
3028
+ elements.push(...filtered);
3029
+ }
3030
+ }
3031
+ });
3032
+ return elements;
3033
+ }, []);
1539
3034
  const handleDOMSelect = useCallback((selection) => {
1540
3035
  const selectedDrawings = getSelectedDrawings();
1541
3036
  const hasDrawings = selectedDrawings.length > 0;
@@ -1598,25 +3093,83 @@ var Skema = ({
1598
3093
  shapeIds: hasDrawings ? editorRef.current?.getSelectedShapeIds() : void 0
1599
3094
  });
1600
3095
  }, [getSelectedDrawings]);
3096
+ const handleDrawingAnnotation = useCallback((selectedIds, skipDomElements = false) => {
3097
+ if (!editorRef.current || selectedIds.length === 0) return;
3098
+ if (pendingAnnotation) return;
3099
+ const editor = editorRef.current;
3100
+ const selectedShapes = selectedIds.map((id) => editor.getShape(id)).filter(Boolean);
3101
+ if (selectedShapes.length === 0) return;
3102
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
3103
+ for (const shape of selectedShapes) {
3104
+ if (!shape) continue;
3105
+ const bounds = editor.getShapePageBounds(shape.id);
3106
+ if (bounds) {
3107
+ minX = Math.min(minX, bounds.x);
3108
+ minY = Math.min(minY, bounds.y);
3109
+ maxX = Math.max(maxX, bounds.x + bounds.width);
3110
+ maxY = Math.max(maxY, bounds.y + bounds.height);
3111
+ }
3112
+ }
3113
+ if (minX === Infinity) return;
3114
+ const selectionBounds = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
3115
+ const drawingShapes = selectedShapes.filter(
3116
+ (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
3117
+ );
3118
+ if (drawingShapes.length === 0) return;
3119
+ let domElements = [];
3120
+ if (!skipDomElements) {
3121
+ const viewportBounds = {
3122
+ x: selectionBounds.x - window.scrollX,
3123
+ y: selectionBounds.y - window.scrollY,
3124
+ width: selectionBounds.width,
3125
+ height: selectionBounds.height
3126
+ };
3127
+ domElements = findDOMElementsInBounds(viewportBounds);
3128
+ }
3129
+ const centerX = selectionBounds.x + selectionBounds.width / 2;
3130
+ const centerY = selectionBounds.y + selectionBounds.height / 2;
3131
+ const x = (centerX - window.scrollX) / window.innerWidth * 100;
3132
+ const clientY = centerY - window.scrollY;
3133
+ let elementDesc = `Drawing (${drawingShapes.length} shape${drawingShapes.length > 1 ? "s" : ""})`;
3134
+ if (domElements.length > 0) {
3135
+ const domNames = domElements.slice(0, 2).map((el) => el.tagName.toLowerCase()).join(", ");
3136
+ const domSuffix = domElements.length > 2 ? ` +${domElements.length - 2} more` : "";
3137
+ elementDesc += ` + ${domNames}${domSuffix}`;
3138
+ }
3139
+ const newDomSelections = domElements.map((el) => createDOMSelection(el));
3140
+ setPendingAnnotation({
3141
+ x,
3142
+ y: centerY,
3143
+ clientY,
3144
+ element: elementDesc,
3145
+ elementPath: "drawing",
3146
+ boundingBox: selectionBounds,
3147
+ isMultiSelect: drawingShapes.length > 1 || domElements.length > 0,
3148
+ annotationType: "drawing",
3149
+ shapeIds: selectedIds,
3150
+ selections: newDomSelections.length > 0 ? newDomSelections : void 0
3151
+ });
3152
+ }, [pendingAnnotation, findDOMElementsInBounds]);
1601
3153
  const handleAnnotationSubmit = useCallback(async (comment) => {
1602
3154
  if (!pendingAnnotation) return;
3155
+ const now = Date.now();
3156
+ let submittedAnnotation;
1603
3157
  if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1604
3158
  const selections = pendingAnnotation.selections;
1605
3159
  if (selections.length === 1) {
1606
3160
  const selection = { ...selections[0], comment };
1607
3161
  setDomSelections((prev) => [...prev, selection]);
1608
3162
  setAnnotations((prev) => [...prev, { type: "dom_selection", ...selection }]);
3163
+ submittedAnnotation = { type: "dom_selection", ...selections[0], comment };
1609
3164
  } else {
1610
3165
  const groupedSelection = {
1611
- id: `group-${Date.now()}`,
3166
+ id: `group-${now}`,
1612
3167
  selector: selections.map((s) => s.selector).join(", "),
1613
3168
  tagName: pendingAnnotation.element,
1614
- // Already formatted as "3 elements: div, span, p"
1615
3169
  elementPath: selections[0].elementPath,
1616
- // Use first element's path as reference
1617
3170
  text: selections.map((s) => s.text).filter(Boolean).join(" | ").slice(0, 200),
1618
3171
  boundingBox: pendingAnnotation.boundingBox,
1619
- timestamp: Date.now(),
3172
+ timestamp: now,
1620
3173
  pathname: selections[0].pathname,
1621
3174
  comment,
1622
3175
  isMultiSelect: true,
@@ -1632,174 +3185,64 @@ var Skema = ({
1632
3185
  };
1633
3186
  setDomSelections((prev) => [...prev, groupedSelection]);
1634
3187
  setAnnotations((prev) => [...prev, { type: "dom_selection", ...groupedSelection }]);
3188
+ submittedAnnotation = { type: "dom_selection", ...groupedSelection };
1635
3189
  }
1636
3190
  } else if (pendingAnnotation.annotationType === "drawing" && pendingAnnotation.shapeIds) {
1637
- const bbox = pendingAnnotation.boundingBox;
1638
- const nearbyElements = pendingAnnotation.selections?.map((s) => ({
1639
- selector: s.selector,
1640
- tagName: s.tagName,
1641
- text: s.text?.slice(0, 100)
1642
- })) || [];
3191
+ const editor = editorRef.current;
3192
+ let drawingSvg;
3193
+ let drawingImage;
3194
+ let extractedText;
3195
+ const gridConfig = { color: "#0066FF", size: 100, labels: true };
3196
+ if (editor && pendingAnnotation.shapeIds && pendingAnnotation.shapeIds.length > 0) {
3197
+ const shapeIds = pendingAnnotation.shapeIds;
3198
+ try {
3199
+ const svgResult = await editor.getSvgString(shapeIds, { padding: 20, background: false });
3200
+ if (svgResult?.svg) {
3201
+ drawingSvg = addGridToSvg(svgResult.svg, gridConfig);
3202
+ }
3203
+ } catch (e) {
3204
+ console.warn("[Skema] Failed to export drawing SVG:", e);
3205
+ }
3206
+ try {
3207
+ const imageResult = await editor.toImage(shapeIds, { format: "png", padding: 20, background: true });
3208
+ if (imageResult?.blob) {
3209
+ drawingImage = await blobToBase64(imageResult.blob);
3210
+ }
3211
+ } catch (e) {
3212
+ console.warn("[Skema] Failed to export drawing image:", e);
3213
+ }
3214
+ try {
3215
+ const shapes = shapeIds.map((id) => editor.getShape(id)).filter(Boolean);
3216
+ extractedText = extractTextFromShapes(shapes);
3217
+ } catch (e) {
3218
+ console.warn("[Skema] Failed to extract text from shapes:", e);
3219
+ }
3220
+ }
3221
+ const nearbyElements = pendingAnnotation.boundingBox ? findNearbyElementsWithStyles(pendingAnnotation.boundingBox, 5) : [];
3222
+ const projectStyles = extractProjectStyleContext();
3223
+ const viewport = getViewportInfo();
1643
3224
  const drawingAnnotation = {
1644
- id: `drawing-${Date.now()}`,
3225
+ id: `drawing-${now}`,
1645
3226
  type: "drawing",
1646
3227
  tool: "draw",
1647
3228
  shapes: pendingAnnotation.shapeIds,
1648
- boundingBox: bbox,
1649
- timestamp: Date.now(),
3229
+ boundingBox: pendingAnnotation.boundingBox,
3230
+ timestamp: now,
1650
3231
  comment,
1651
- nearbyElements
3232
+ drawingSvg,
3233
+ drawingImage,
3234
+ extractedText: extractedText || void 0,
3235
+ gridConfig,
3236
+ nearbyElements,
3237
+ viewport,
3238
+ projectStyles
1652
3239
  };
1653
3240
  setAnnotations((prev) => [...prev, drawingAnnotation]);
1654
- const drawingLog = `
1655
- ### ${annotations.length + 1}. Drawing (${pendingAnnotation.shapeIds?.length || 0} shapes)
1656
- **Position:** x:${Math.round(bbox.x)}, y:${Math.round(bbox.y)} (${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px)
1657
- **Annotation at:** ${((bbox.x + bbox.width / 2) / window.innerWidth * 100).toFixed(1)}% from left, ${Math.round(bbox.y + bbox.height / 2)}px from top
1658
- **Shape IDs:** ${pendingAnnotation.shapeIds?.join(", ") || "none"}
1659
- **Nearby Elements:** ${nearbyElements.map((e) => e.tagName).join(", ") || "none"}
1660
- **Feedback:** ${comment}
1661
- `;
1662
- console.log(drawingLog);
3241
+ submittedAnnotation = drawingAnnotation;
1663
3242
  }
1664
- if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1665
- const selections = pendingAnnotation.selections;
1666
- const annotationIndex = annotations.length + 1;
1667
- const elementDescs = selections.map((s) => {
1668
- const textPreview = s.text?.slice(0, 50) || "";
1669
- return `${s.tagName.toLowerCase()}${textPreview ? `: "${textPreview}..."` : ""}`;
1670
- }).join(", ");
1671
- const firstSelection = selections[0];
1672
- const firstElement = document.querySelector(firstSelection.selector);
1673
- let computedStylesStr = "N/A";
1674
- let nearbyElements = "N/A";
1675
- if (firstElement) {
1676
- const styles2 = window.getComputedStyle(firstElement);
1677
- computedStylesStr = [
1678
- `color: ${styles2.color}`,
1679
- `border-color: ${styles2.borderColor}`,
1680
- `font-size: ${styles2.fontSize}`,
1681
- `font-weight: ${styles2.fontWeight}`,
1682
- `font-family: ${styles2.fontFamily}`,
1683
- `line-height: ${styles2.lineHeight}`,
1684
- `letter-spacing: ${styles2.letterSpacing}`,
1685
- `text-align: ${styles2.textAlign}`,
1686
- `width: ${styles2.width}`,
1687
- `height: ${styles2.height}`,
1688
- `border: ${styles2.border}`,
1689
- `display: ${styles2.display}`,
1690
- `flex-direction: ${styles2.flexDirection}`,
1691
- `opacity: ${styles2.opacity}`
1692
- ].join("; ");
1693
- const parent = firstElement.parentElement;
1694
- if (parent) {
1695
- const siblings = Array.from(parent.children).filter((el) => el !== firstElement).slice(0, 3).map((el) => el.tagName.toLowerCase());
1696
- nearbyElements = siblings.length > 0 ? siblings.join(", ") : "none";
1697
- }
1698
- }
1699
- const bbox = pendingAnnotation.boundingBox;
1700
- const forensicLog = `
1701
- ### ${annotationIndex}. ${selections.length > 1 ? `${selections.length} elements: ` : ""}${elementDescs}
1702
- ${selections.length > 1 ? "*Forensic data shown for first element of selection*\n" : ""}**Full DOM Path:** ${firstSelection.elementPath}
1703
- **Position:** x:${Math.round(bbox.x)}, y:${Math.round(bbox.y)} (${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px)
1704
- **Annotation at:** ${((bbox.x + bbox.width / 2) / window.innerWidth * 100).toFixed(1)}% from left, ${Math.round(bbox.y + bbox.height / 2)}px from top
1705
- **Computed Styles:** ${computedStylesStr}
1706
- **Nearby Elements:** ${nearbyElements}
1707
- **Feedback:** ${comment}
1708
- `;
1709
- console.log(forensicLog);
1710
- }
1711
- if (onAnnotationSubmit) {
1712
- let submittedAnnotation;
1713
- if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1714
- const selections = pendingAnnotation.selections;
1715
- if (selections.length === 1) {
1716
- submittedAnnotation = { type: "dom_selection", ...selections[0], comment };
1717
- } else {
1718
- submittedAnnotation = {
1719
- type: "dom_selection",
1720
- id: `group-${Date.now()}`,
1721
- selector: selections.map((s) => s.selector).join(", "),
1722
- tagName: pendingAnnotation.element,
1723
- elementPath: selections[0].elementPath,
1724
- text: selections.map((s) => s.text).filter(Boolean).join(" | ").slice(0, 200),
1725
- boundingBox: pendingAnnotation.boundingBox,
1726
- timestamp: Date.now(),
1727
- pathname: selections[0].pathname,
1728
- comment,
1729
- isMultiSelect: true,
1730
- elements: selections.map((s) => ({
1731
- selector: s.selector,
1732
- tagName: s.tagName,
1733
- elementPath: s.elementPath,
1734
- text: s.text,
1735
- boundingBox: s.boundingBox,
1736
- cssClasses: s.cssClasses,
1737
- attributes: s.attributes
1738
- }))
1739
- };
1740
- }
1741
- } else {
1742
- const editor = editorRef.current;
1743
- let drawingSvg;
1744
- let drawingImage;
1745
- let extractedText;
1746
- const gridConfig = { color: "#0066FF", size: 100, labels: true };
1747
- console.log("[Skema] Drawing annotation - shapeIds:", pendingAnnotation.shapeIds);
1748
- if (editor && pendingAnnotation.shapeIds && pendingAnnotation.shapeIds.length > 0) {
1749
- const shapeIds = pendingAnnotation.shapeIds;
1750
- console.log("[Skema] Exporting", shapeIds.length, "shapes for image...");
1751
- try {
1752
- const svgResult = await editor.getSvgString(shapeIds, {
1753
- padding: 20,
1754
- background: false
1755
- });
1756
- if (svgResult?.svg) {
1757
- drawingSvg = addGridToSvg(svgResult.svg, gridConfig);
1758
- console.log("[Skema] SVG export successful");
1759
- }
1760
- } catch (e) {
1761
- console.warn("[Skema] Failed to export drawing SVG:", e);
1762
- }
1763
- try {
1764
- const imageResult = await editor.toImage(shapeIds, {
1765
- format: "png",
1766
- padding: 20,
1767
- background: true
1768
- });
1769
- console.log("[Skema] toImage result:", imageResult ? "got result" : "null", imageResult?.blob ? "has blob" : "no blob");
1770
- if (imageResult?.blob) {
1771
- drawingImage = await blobToBase64(imageResult.blob);
1772
- console.log("[Skema] Image export successful, base64 length:", drawingImage?.length);
1773
- }
1774
- } catch (e) {
1775
- console.warn("[Skema] Failed to export drawing image:", e);
1776
- }
1777
- try {
1778
- const shapes = shapeIds.map((id) => editor.getShape(id)).filter(Boolean);
1779
- extractedText = extractTextFromShapes(shapes);
1780
- } catch (e) {
1781
- console.warn("[Skema] Failed to extract text from shapes:", e);
1782
- }
1783
- }
1784
- const nearbyElements = pendingAnnotation.boundingBox ? findNearbyElementsWithStyles(pendingAnnotation.boundingBox, 5) : [];
1785
- const projectStyles = extractProjectStyleContext();
1786
- const viewport = getViewportInfo();
1787
- submittedAnnotation = {
1788
- id: `drawing-${Date.now()}`,
1789
- type: "drawing",
1790
- tool: "draw",
1791
- shapes: pendingAnnotation.shapeIds || [],
1792
- boundingBox: pendingAnnotation.boundingBox,
1793
- timestamp: Date.now(),
1794
- comment,
1795
- drawingSvg,
1796
- drawingImage,
1797
- extractedText: extractedText || void 0,
1798
- gridConfig,
1799
- nearbyElements,
1800
- viewport,
1801
- projectStyles
1802
- };
3243
+ if (onAnnotationSubmit && submittedAnnotation) {
3244
+ if (pendingAnnotation.boundingBox) {
3245
+ setProcessingBoundingBox(pendingAnnotation.boundingBox);
1803
3246
  }
1804
3247
  onAnnotationSubmit(submittedAnnotation, comment);
1805
3248
  }
@@ -1811,19 +3254,27 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1811
3254
  }, [pendingAnnotation, onAnnotationSubmit]);
1812
3255
  const handleAnnotationCancel = useCallback(() => {
1813
3256
  setPendingExiting(true);
3257
+ if (isProcessing) {
3258
+ onProcessingCancel?.();
3259
+ }
3260
+ setProcessingBoundingBox(null);
1814
3261
  setTimeout(() => {
1815
3262
  setPendingAnnotation(null);
1816
3263
  setPendingExiting(false);
1817
3264
  }, 150);
1818
- }, []);
3265
+ }, [isProcessing, onProcessingCancel]);
1819
3266
  const handleDeleteAnnotation = useCallback((annotation) => {
1820
3267
  setAnnotations((prev) => prev.filter((a) => a.id !== annotation.id));
1821
3268
  if (annotation.type === "dom_selection") {
1822
3269
  setDomSelections((prev) => prev.filter((s) => s.id !== annotation.id));
1823
3270
  }
1824
3271
  setHoveredMarkerId(null);
3272
+ if (isProcessing) {
3273
+ onProcessingCancel?.();
3274
+ setProcessingBoundingBox(null);
3275
+ }
1825
3276
  onAnnotationDelete?.(annotation.id);
1826
- }, [onAnnotationDelete]);
3277
+ }, [onAnnotationDelete, isProcessing, onProcessingCancel]);
1827
3278
  const handleClear = useCallback(() => {
1828
3279
  setAnnotations([]);
1829
3280
  setDomSelections([]);
@@ -1844,97 +3295,14 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1844
3295
  console.log("[Skema] Exported annotations:", exportData);
1845
3296
  alert("Annotations copied to clipboard!");
1846
3297
  }, [annotations]);
1847
- const findDOMElementsInBounds = useCallback((bounds) => {
1848
- const elements = [];
1849
- const allElements = document.querySelectorAll("*");
1850
- allElements.forEach((el) => {
1851
- if (!(el instanceof HTMLElement)) return;
1852
- if (shouldIgnoreElement(el)) return;
1853
- const rect = el.getBoundingClientRect();
1854
- const elBounds = {
1855
- x: rect.left,
1856
- y: rect.top,
1857
- width: rect.width,
1858
- height: rect.height
1859
- };
1860
- if (elBounds.width < 10 || elBounds.height < 10) return;
1861
- if (bboxIntersects(bounds, elBounds)) {
1862
- const isParent = elements.some((existing) => el.contains(existing));
1863
- if (!isParent) {
1864
- const filtered = elements.filter((existing) => !existing.contains(el) && !el.contains(existing));
1865
- filtered.push(el);
1866
- elements.length = 0;
1867
- elements.push(...filtered);
1868
- }
1869
- }
1870
- });
1871
- return elements;
1872
- }, []);
1873
- const handleDrawingAnnotation = useCallback((selectedIds) => {
1874
- if (!editorRef.current || selectedIds.length === 0) return;
1875
- if (pendingAnnotation) return;
1876
- const editor = editorRef.current;
1877
- const selectedShapes = selectedIds.map((id) => editor.getShape(id)).filter(Boolean);
1878
- if (selectedShapes.length === 0) return;
1879
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1880
- for (const shape of selectedShapes) {
1881
- if (!shape) continue;
1882
- const bounds = editor.getShapePageBounds(shape.id);
1883
- if (bounds) {
1884
- minX = Math.min(minX, bounds.x);
1885
- minY = Math.min(minY, bounds.y);
1886
- maxX = Math.max(maxX, bounds.x + bounds.width);
1887
- maxY = Math.max(maxY, bounds.y + bounds.height);
3298
+ const handleBrushSelection = useCallback((brushBounds) => {
3299
+ if (editorRef.current) {
3300
+ const selectedShapeIds = editorRef.current.getSelectedShapeIds();
3301
+ if (selectedShapeIds.length > 0) {
3302
+ handleDrawingAnnotation(selectedShapeIds, true);
3303
+ return;
1888
3304
  }
1889
3305
  }
1890
- if (minX === Infinity) return;
1891
- const selectionBounds = {
1892
- x: minX,
1893
- y: minY,
1894
- width: maxX - minX,
1895
- height: maxY - minY
1896
- };
1897
- const drawingShapes = selectedShapes.filter(
1898
- (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
1899
- );
1900
- if (drawingShapes.length === 0) return;
1901
- const viewportBounds = {
1902
- x: selectionBounds.x - window.scrollX,
1903
- y: selectionBounds.y - window.scrollY,
1904
- width: selectionBounds.width,
1905
- height: selectionBounds.height
1906
- };
1907
- const domElements = findDOMElementsInBounds(viewportBounds);
1908
- const centerX = selectionBounds.x + selectionBounds.width / 2;
1909
- const centerY = selectionBounds.y + selectionBounds.height / 2;
1910
- const x = (centerX - window.scrollX) / window.innerWidth * 100;
1911
- const clientY = centerY - window.scrollY;
1912
- let elementDesc = `Drawing (${drawingShapes.length} shape${drawingShapes.length > 1 ? "s" : ""})`;
1913
- if (domElements.length > 0) {
1914
- const domNames = domElements.slice(0, 2).map((el) => el.tagName.toLowerCase()).join(", ");
1915
- const domSuffix = domElements.length > 2 ? ` +${domElements.length - 2} more` : "";
1916
- elementDesc += ` + ${domNames}${domSuffix}`;
1917
- }
1918
- const newDomSelections = domElements.map((el) => createDOMSelection(el));
1919
- setPendingAnnotation({
1920
- x,
1921
- y: centerY,
1922
- clientY,
1923
- element: elementDesc,
1924
- elementPath: "drawing",
1925
- boundingBox: {
1926
- x: selectionBounds.x,
1927
- y: selectionBounds.y,
1928
- width: selectionBounds.width,
1929
- height: selectionBounds.height
1930
- },
1931
- isMultiSelect: drawingShapes.length > 1 || domElements.length > 0,
1932
- annotationType: "drawing",
1933
- shapeIds: selectedIds,
1934
- selections: newDomSelections.length > 0 ? newDomSelections : void 0
1935
- });
1936
- }, [pendingAnnotation, findDOMElementsInBounds]);
1937
- const handleBrushSelection = useCallback((brushBounds) => {
1938
3306
  const viewportBounds = {
1939
3307
  x: brushBounds.x - window.scrollX,
1940
3308
  y: brushBounds.y - window.scrollY,
@@ -1955,24 +3323,16 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1955
3323
  } else {
1956
3324
  handleMultiDOMSelect(selections);
1957
3325
  }
1958
- }, [findDOMElementsInBounds, domSelections, handleDOMSelect, handleMultiDOMSelect]);
1959
- const isPointInPolygon = useCallback((point, polygon) => {
1960
- let inside = false;
1961
- const n = polygon.length;
1962
- for (let i = 0, j = n - 1; i < n; j = i++) {
1963
- const xi = polygon[i].x;
1964
- const yi = polygon[i].y;
1965
- const xj = polygon[j].x;
1966
- const yj = polygon[j].y;
1967
- const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi;
1968
- if (intersect) {
1969
- inside = !inside;
1970
- }
1971
- }
1972
- return inside;
1973
- }, []);
3326
+ }, [findDOMElementsInBounds, domSelections, handleDOMSelect, handleMultiDOMSelect, handleDrawingAnnotation]);
1974
3327
  const handleLassoSelection = useCallback((lassoPoints) => {
1975
3328
  if (lassoPoints.length < 3) return;
3329
+ if (editorRef.current) {
3330
+ const selectedShapeIds = editorRef.current.getSelectedShapeIds();
3331
+ if (selectedShapeIds.length > 0) {
3332
+ handleDrawingAnnotation(selectedShapeIds, true);
3333
+ return;
3334
+ }
3335
+ }
1976
3336
  const viewportPoints = lassoPoints.map((p) => ({
1977
3337
  x: p.x - window.scrollX,
1978
3338
  y: p.y - window.scrollY
@@ -2016,16 +3376,16 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2016
3376
  } else {
2017
3377
  handleMultiDOMSelect(selections);
2018
3378
  }
2019
- }, [isPointInPolygon, domSelections, handleDOMSelect, handleMultiDOMSelect]);
3379
+ }, [domSelections, handleDOMSelect, handleMultiDOMSelect, handleDrawingAnnotation]);
2020
3380
  const handleMount = useCallback((editor) => {
2021
3381
  editorRef.current = editor;
2022
3382
  editor.setStyleForNextShapes(ArrowShapeKindStyle, "arc");
2023
3383
  try {
2024
3384
  const selectIdleState = editor.getStateDescendant("select.idle");
2025
3385
  if (selectIdleState) {
2026
- selectIdleState.handleDoubleClickOnCanvas = (info) => {
3386
+ selectIdleState.handleDoubleClickOnCanvas = (_info) => {
2027
3387
  lastDoubleClickRef.current = Date.now();
2028
- const point = editor.pageToViewport(info.point);
3388
+ const point = editor.inputs.currentScreenPoint;
2029
3389
  const elements = document.elementsFromPoint(point.x, point.y);
2030
3390
  const target = elements.find(
2031
3391
  (el) => el instanceof HTMLElement && !shouldIgnoreElement(el)
@@ -2049,6 +3409,14 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2049
3409
  });
2050
3410
  }
2051
3411
  };
3412
+ selectIdleState.handleDoubleClickOnShape = (_info, shape) => {
3413
+ lastDoubleClickRef.current = Date.now();
3414
+ if (shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)) {
3415
+ const selectedIds = editor.getSelectedShapeIds();
3416
+ const shapeIds = selectedIds.length > 0 ? selectedIds : [shape.id];
3417
+ handleDrawingAnnotation(shapeIds, true);
3418
+ }
3419
+ };
2052
3420
  }
2053
3421
  } catch (e) {
2054
3422
  console.warn("Failed to override double click behavior", e);
@@ -2061,12 +3429,10 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2061
3429
  }
2062
3430
  });
2063
3431
  const lassoSelectTool = editor.root.children?.["lasso-select"];
2064
- if (lassoSelectTool) {
2065
- if ("setOnLassoComplete" in lassoSelectTool) {
2066
- lassoSelectTool.setOnLassoComplete((points) => {
2067
- handleLassoSelection(points);
2068
- });
2069
- }
3432
+ if (lassoSelectTool && "setOnLassoComplete" in lassoSelectTool) {
3433
+ lassoSelectTool.setOnLassoComplete((points) => {
3434
+ handleLassoSelection(points);
3435
+ });
2070
3436
  }
2071
3437
  let lastBrush = null;
2072
3438
  editor.sideEffects.registerAfterChangeHandler("instance", (prev, next) => {
@@ -2082,16 +3448,8 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2082
3448
  } else if (next.brush) {
2083
3449
  lastBrush = next.brush;
2084
3450
  }
2085
- return;
2086
3451
  });
2087
3452
  }, [handleDOMSelect, handleBrushSelection, handleLassoSelection, handleMultiDOMSelect, handleDrawingAnnotation]);
2088
- useEffect(() => {
2089
- return () => {
2090
- if (cleanupRef.current) {
2091
- cleanupRef.current();
2092
- }
2093
- };
2094
- }, []);
2095
3453
  useEffect(() => {
2096
3454
  if (!isActive) return;
2097
3455
  const handlePointerDown = (e) => {
@@ -2110,46 +3468,6 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2110
3468
  document.addEventListener("pointerdown", handlePointerDown, { capture: true });
2111
3469
  return () => document.removeEventListener("pointerdown", handlePointerDown, { capture: true });
2112
3470
  }, [isActive, pendingAnnotation, handleAnnotationCancel]);
2113
- const components = {
2114
- Toolbar: null,
2115
- Overlays: SkemaOverlays,
2116
- // Hide background to make canvas transparent (so website shows through)
2117
- Background: null,
2118
- // Hide UI elements we don't need
2119
- SharePanel: null,
2120
- MenuPanel: null,
2121
- TopPanel: null,
2122
- PageMenu: null,
2123
- NavigationPanel: null,
2124
- HelpMenu: null,
2125
- Minimap: null,
2126
- // Hide "Back to Content" button (HelperButtons contains this)
2127
- HelperButtons: null,
2128
- QuickActions: null,
2129
- ZoomMenu: null,
2130
- ActionsMenu: null,
2131
- DebugPanel: null,
2132
- DebugMenu: null,
2133
- // Hide canvas overlays
2134
- OnTheCanvas: null,
2135
- InFrontOfTheCanvas: null
2136
- };
2137
- const overrides = {
2138
- tools(editor, tools) {
2139
- return {
2140
- ...tools,
2141
- "lasso-select": {
2142
- id: "lasso-select",
2143
- label: "Lasso Select",
2144
- icon: "blob",
2145
- kbd: "l",
2146
- onSelect: () => {
2147
- editor.setCurrentTool("lasso-select");
2148
- }
2149
- }
2150
- };
2151
- }
2152
- };
2153
3471
  if (!isActive) {
2154
3472
  return null;
2155
3473
  }
@@ -2164,14 +3482,66 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2164
3482
  pointerEvents: "none"
2165
3483
  },
2166
3484
  children: [
2167
- /* @__PURE__ */ jsx("style", { children: `
2168
- .tlui-button[data-testid="back-to-content"],
2169
- .tlui-offscreen-indicator,
2170
- [class*="back-to-content"],
2171
- .tl-offscreen-indicator {
2172
- display: none !important;
2173
- }
2174
- ` }),
3485
+ /* @__PURE__ */ jsx("style", { children: skemaHiddenUiStyles }),
3486
+ !isStylePanelOpen && /* @__PURE__ */ jsx("style", { children: `
3487
+ .tlui-style-panel__wrapper,
3488
+ .tlui-style-panel {
3489
+ display: none !important;
3490
+ }
3491
+ ` }),
3492
+ isStylePanelOpen && /* @__PURE__ */ jsx("style", { children: `
3493
+ .tlui-style-panel__wrapper {
3494
+ top: 68px !important;
3495
+ right: 16px !important;
3496
+ }
3497
+ ` }),
3498
+ isToolbarExpanded && /* @__PURE__ */ jsx(
3499
+ "button",
3500
+ {
3501
+ onClick: () => setIsStylePanelOpen((prev) => !prev),
3502
+ title: isStylePanelOpen ? "Hide style settings" : "Show style settings",
3503
+ style: {
3504
+ position: "fixed",
3505
+ top: 16,
3506
+ right: 16,
3507
+ width: 44,
3508
+ height: 44,
3509
+ display: "flex",
3510
+ alignItems: "center",
3511
+ justifyContent: "center",
3512
+ backgroundColor: isStylePanelOpen ? "#FF6800" : "white",
3513
+ border: "none",
3514
+ borderRadius: 12,
3515
+ boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
3516
+ cursor: "pointer",
3517
+ pointerEvents: "auto",
3518
+ zIndex: zIndex + 5,
3519
+ transition: "all 0.2s ease"
3520
+ },
3521
+ children: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3522
+ /* @__PURE__ */ jsx(
3523
+ "path",
3524
+ {
3525
+ d: "M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z",
3526
+ stroke: isStylePanelOpen ? "white" : "#6B7280",
3527
+ strokeWidth: "2",
3528
+ strokeLinecap: "round",
3529
+ strokeLinejoin: "round"
3530
+ }
3531
+ ),
3532
+ /* @__PURE__ */ jsx(
3533
+ "path",
3534
+ {
3535
+ d: "M19.4 15C19.2669 15.3016 19.2272 15.6362 19.286 15.9606C19.3448 16.285 19.4995 16.5843 19.73 16.82L19.79 16.88C19.976 17.0657 20.1235 17.2863 20.2241 17.5291C20.3248 17.7719 20.3766 18.0322 20.3766 18.295C20.3766 18.5578 20.3248 18.8181 20.2241 19.0609C20.1235 19.3037 19.976 19.5243 19.79 19.71C19.6043 19.896 19.3837 20.0435 19.1409 20.1441C18.8981 20.2448 18.6378 20.2966 18.375 20.2966C18.1122 20.2966 17.8519 20.2448 17.6091 20.1441C17.3663 20.0435 17.1457 19.896 16.96 19.71L16.9 19.65C16.6643 19.4195 16.365 19.2648 16.0406 19.206C15.7162 19.1472 15.3816 19.1869 15.08 19.32C14.7842 19.4468 14.532 19.6572 14.3543 19.9255C14.1766 20.1938 14.0813 20.5082 14.08 20.83V21C14.08 21.5304 13.8693 22.0391 13.4942 22.4142C13.1191 22.7893 12.6104 23 12.08 23C11.5496 23 11.0409 22.7893 10.6658 22.4142C10.2907 22.0391 10.08 21.5304 10.08 21V20.91C10.0723 20.579 9.96512 20.258 9.77251 19.9887C9.5799 19.7194 9.31074 19.5143 9 19.4C8.69838 19.2669 8.36381 19.2272 8.03941 19.286C7.71502 19.3448 7.41568 19.4995 7.18 19.73L7.12 19.79C6.93425 19.976 6.71368 20.1235 6.47088 20.2241C6.22808 20.3248 5.96783 20.3766 5.705 20.3766C5.44217 20.3766 5.18192 20.3248 4.93912 20.2241C4.69632 20.1235 4.47575 19.976 4.29 19.79C4.10405 19.6043 3.95653 19.3837 3.85588 19.1409C3.75523 18.8981 3.70343 18.6378 3.70343 18.375C3.70343 18.1122 3.75523 17.8519 3.85588 17.6091C3.95653 17.3663 4.10405 17.1457 4.29 16.96L4.35 16.9C4.58054 16.6643 4.73519 16.365 4.794 16.0406C4.85282 15.7162 4.81312 15.3816 4.68 15.08C4.55324 14.7842 4.34276 14.532 4.07447 14.3543C3.80618 14.1766 3.49179 14.0813 3.17 14.08H3C2.46957 14.08 1.96086 13.8693 1.58579 13.4942C1.21071 13.1191 1 12.6104 1 12.08C1 11.5496 1.21071 11.0409 1.58579 10.6658C1.96086 10.2907 2.46957 10.08 3 10.08H3.09C3.42099 10.0723 3.742 9.96512 4.0113 9.77251C4.28059 9.5799 4.48572 9.31074 4.6 9C4.73312 8.69838 4.77282 8.36381 4.714 8.03941C4.65519 7.71502 4.50054 7.41568 4.27 7.18L4.21 7.12C4.02405 6.93425 3.87653 6.71368 3.77588 6.47088C3.67523 6.22808 3.62343 5.96783 3.62343 5.705C3.62343 5.44217 3.67523 5.18192 3.77588 4.93912C3.87653 4.69632 4.02405 4.47575 4.21 4.29C4.39575 4.10405 4.61632 3.95653 4.85912 3.85588C5.10192 3.75523 5.36217 3.70343 5.625 3.70343C5.88783 3.70343 6.14808 3.75523 6.39088 3.85588C6.63368 3.95653 6.85425 4.10405 7.04 4.29L7.1 4.35C7.33568 4.58054 7.63502 4.73519 7.95941 4.794C8.28381 4.85282 8.61838 4.81312 8.92 4.68H9C9.29577 4.55324 9.54802 4.34276 9.72569 4.07447C9.90337 3.80618 9.99872 3.49179 10 3.17V3C10 2.46957 10.2107 1.96086 10.5858 1.58579C10.9609 1.21071 11.4696 1 12 1C12.5304 1 13.0391 1.21071 13.4142 1.58579C13.7893 1.96086 14 2.46957 14 3V3.09C14.0013 3.41179 14.0966 3.72618 14.2743 3.99447C14.452 4.26276 14.7042 4.47324 15 4.6C15.3016 4.73312 15.6362 4.77282 15.9606 4.714C16.285 4.65519 16.5843 4.50054 16.82 4.27L16.88 4.21C17.0657 4.02405 17.2863 3.87653 17.5291 3.77588C17.7719 3.67523 18.0322 3.62343 18.295 3.62343C18.5578 3.62343 18.8181 3.67523 19.0609 3.77588C19.3037 3.87653 19.5243 4.02405 19.71 4.21C19.896 4.39575 20.0435 4.61632 20.1441 4.85912C20.2448 5.10192 20.2966 5.36217 20.2966 5.625C20.2966 5.88783 20.2448 6.14808 20.1441 6.39088C20.0435 6.63368 19.896 6.85425 19.71 7.04L19.65 7.1C19.4195 7.33568 19.2648 7.63502 19.206 7.95941C19.1472 8.28381 19.1869 8.61838 19.32 8.92V9C19.4468 9.29577 19.6572 9.54802 19.9255 9.72569C20.1938 9.90337 20.5082 9.99872 20.83 10H21C21.5304 10 22.0391 10.2107 22.4142 10.5858C22.7893 10.9609 23 11.4696 23 12C23 12.5304 22.7893 13.0391 22.4142 13.4142C22.0391 13.7893 21.5304 14 21 14H20.91C20.5882 14.0013 20.2738 14.0966 20.0055 14.2743C19.7372 14.452 19.5268 14.7042 19.4 15Z",
3536
+ stroke: isStylePanelOpen ? "white" : "#6B7280",
3537
+ strokeWidth: "2",
3538
+ strokeLinecap: "round",
3539
+ strokeLinejoin: "round"
3540
+ }
3541
+ )
3542
+ ] })
3543
+ }
3544
+ ),
2175
3545
  /* @__PURE__ */ jsx(
2176
3546
  "div",
2177
3547
  {
@@ -2184,21 +3554,31 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2184
3554
  Tldraw,
2185
3555
  {
2186
3556
  tools: [LassoSelectTool],
2187
- components,
2188
- overrides,
3557
+ components: skemaComponents,
3558
+ overrides: skemaOverrides,
2189
3559
  onMount: handleMount,
2190
3560
  hideUi: false,
2191
3561
  inferDarkMode: false,
2192
- options: {
2193
- // Disable camera constraints that would interfere with overlay mode
2194
- maxPages: 1
2195
- },
2196
- children: /* @__PURE__ */ jsx(SkemaToolbar, {})
3562
+ options: { maxPages: 1 },
3563
+ children: /* @__PURE__ */ jsx(
3564
+ SkemaToolbar,
3565
+ {
3566
+ onExpandedChange: setIsToolbarExpanded,
3567
+ onStylePanelChange: setIsStylePanelOpen
3568
+ }
3569
+ )
2197
3570
  }
2198
3571
  )
2199
3572
  }
2200
3573
  ),
2201
3574
  /* @__PURE__ */ jsx(SelectionOverlay, { selections: domSelections }),
3575
+ isProcessing && processingBoundingBox && /* @__PURE__ */ jsx(
3576
+ ProcessingOverlay,
3577
+ {
3578
+ boundingBox: processingBoundingBox,
3579
+ scrollOffset
3580
+ }
3581
+ ),
2202
3582
  /* @__PURE__ */ jsx(
2203
3583
  AnnotationMarkersLayer,
2204
3584
  {
@@ -2243,15 +3623,7 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2243
3623
  isMultiSelect: pendingAnnotation.isMultiSelect,
2244
3624
  accentColor: pendingAnnotation.isMultiSelect ? "#34C759" : "#3b82f6",
2245
3625
  style: {
2246
- // Position popup centered horizontally
2247
- left: Math.max(
2248
- 160,
2249
- Math.min(
2250
- window.innerWidth - 160,
2251
- pendingAnnotation.x / 100 * window.innerWidth
2252
- )
2253
- ),
2254
- // Position above or below based on viewport space
3626
+ left: Math.max(160, Math.min(window.innerWidth - 160, pendingAnnotation.x / 100 * window.innerWidth)),
2255
3627
  ...pendingAnnotation.clientY > window.innerHeight - 250 ? { bottom: window.innerHeight - pendingAnnotation.clientY + 30 } : { top: pendingAnnotation.clientY + 30 },
2256
3628
  zIndex: zIndex + 3,
2257
3629
  pointerEvents: "auto"
@@ -2295,13 +3667,37 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2295
3667
  " to toggle Skema"
2296
3668
  ]
2297
3669
  }
2298
- )
3670
+ ),
3671
+ scribbleToast && /* @__PURE__ */ jsx(
3672
+ "div",
3673
+ {
3674
+ "data-skema": "scribble-toast",
3675
+ style: {
3676
+ position: "fixed",
3677
+ bottom: 60,
3678
+ left: "50%",
3679
+ transform: "translateX(-50%)",
3680
+ padding: "10px 20px",
3681
+ backgroundColor: "rgba(239, 68, 68, 0.95)",
3682
+ color: "white",
3683
+ borderRadius: "8px",
3684
+ fontSize: "14px",
3685
+ fontWeight: 500,
3686
+ pointerEvents: "none",
3687
+ zIndex: zIndex + 10,
3688
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
3689
+ animation: "skema-toast-fade 0.2s ease-out"
3690
+ },
3691
+ children: scribbleToast
3692
+ }
3693
+ ),
3694
+ /* @__PURE__ */ jsx("style", { children: skemaToastStyles })
2299
3695
  ]
2300
3696
  }
2301
3697
  );
2302
3698
  };
2303
3699
  var Skema_default = Skema;
2304
3700
 
2305
- export { AnnotationPopup, Skema, addGridToSvg, bboxCenter, bboxDocumentToViewport, bboxFromPoints, bboxIntersects, bboxViewportToDocument, blobToBase64, createDOMSelection, Skema_default as default, documentToViewport, expandBbox, extractTextFromShapes, generateSelector, getBoundingBox, getElementClasses, getElementPath, getGridCellReference, getViewportInfo, identifyElement, pointInBbox, shouldIgnoreElement, viewportToDocument };
3701
+ export { AnnotationPopup, Skema, addGridToSvg, bboxCenter, bboxDocumentToViewport, bboxFromPoints, bboxIntersects, bboxViewportToDocument, blobToBase64, createDOMSelection, Skema_default as default, documentToViewport, expandBbox, extractTextFromShapes, generateSelector, getBoundingBox, getElementClasses, getElementPath, getGridCellReference, getViewportInfo, identifyElement, pointInBbox, shouldIgnoreElement, useDaemon, viewportToDocument };
2306
3702
  //# sourceMappingURL=index.mjs.map
2307
3703
  //# sourceMappingURL=index.mjs.map