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.js CHANGED
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var react = require('react');
6
6
  var tldraw = require('tldraw');
7
7
  require('tldraw/tldraw.css');
8
+ var framerMotion = require('framer-motion');
8
9
  var jsxRuntime = require('react/jsx-runtime');
9
10
 
10
11
  // src/components/Skema.tsx
@@ -177,6 +178,217 @@ var LassoSelectTool = class extends tldraw.StateNode {
177
178
  LassoSelectTool.id = "lasso-select";
178
179
  LassoSelectTool.initial = "idle";
179
180
  LassoSelectTool.children = () => [IdleState, LassoingState];
181
+ function useDaemon(options = {}) {
182
+ const {
183
+ url = "ws://localhost:9999",
184
+ autoConnect = true,
185
+ autoReconnect = true,
186
+ reconnectDelay = 2e3
187
+ } = options;
188
+ const [state, setState] = react.useState({
189
+ connected: false,
190
+ provider: "gemini",
191
+ availableProviders: [],
192
+ cwd: ""
193
+ });
194
+ const [isGenerating, setIsGenerating] = react.useState(false);
195
+ const [error, setError] = react.useState(null);
196
+ const wsRef = react.useRef(null);
197
+ const pendingRequests = react.useRef(/* @__PURE__ */ new Map());
198
+ const eventCallbacks = react.useRef(/* @__PURE__ */ new Map());
199
+ const reconnectTimeoutRef = react.useRef(null);
200
+ const messageIdRef = react.useRef(0);
201
+ const nextId = react.useCallback(() => {
202
+ messageIdRef.current += 1;
203
+ return `msg-${messageIdRef.current}-${Date.now()}`;
204
+ }, []);
205
+ const sendRequest = react.useCallback((type, payload = {}) => {
206
+ return new Promise((resolve, reject) => {
207
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
208
+ reject(new Error("Not connected to daemon"));
209
+ return;
210
+ }
211
+ const id = nextId();
212
+ pendingRequests.current.set(id, { resolve, reject });
213
+ wsRef.current.send(JSON.stringify({ id, type, ...payload }));
214
+ setTimeout(() => {
215
+ if (pendingRequests.current.has(id)) {
216
+ pendingRequests.current.delete(id);
217
+ reject(new Error("Request timeout"));
218
+ }
219
+ }, 3e4);
220
+ });
221
+ }, [nextId]);
222
+ const handleMessage = react.useCallback((event) => {
223
+ try {
224
+ const msg = JSON.parse(event.data);
225
+ if (msg.type === "connected") {
226
+ setState((prev) => ({
227
+ ...prev,
228
+ connected: true,
229
+ provider: msg.provider || prev.provider,
230
+ availableProviders: msg.availableProviders || prev.availableProviders,
231
+ cwd: msg.cwd || prev.cwd
232
+ }));
233
+ setError(null);
234
+ return;
235
+ }
236
+ if (msg.type === "ai-event" && msg.id) {
237
+ const callback = eventCallbacks.current.get(msg.id);
238
+ if (callback) {
239
+ callback(msg.event);
240
+ }
241
+ return;
242
+ }
243
+ if (msg.type === "generate-complete" && msg.id) {
244
+ const pending = pendingRequests.current.get(msg.id);
245
+ if (pending) {
246
+ pendingRequests.current.delete(msg.id);
247
+ eventCallbacks.current.delete(msg.id);
248
+ pending.resolve({ success: msg.success, annotationId: msg.annotationId });
249
+ }
250
+ setIsGenerating(false);
251
+ return;
252
+ }
253
+ if (msg.type === "provider-changed") {
254
+ setState((prev) => ({ ...prev, provider: msg.provider }));
255
+ }
256
+ if (msg.type === "error" && msg.id) {
257
+ const pending = pendingRequests.current.get(msg.id);
258
+ if (pending) {
259
+ pendingRequests.current.delete(msg.id);
260
+ eventCallbacks.current.delete(msg.id);
261
+ pending.reject(new Error(msg.error));
262
+ }
263
+ setIsGenerating(false);
264
+ return;
265
+ }
266
+ if (msg.id) {
267
+ const pending = pendingRequests.current.get(msg.id);
268
+ if (pending) {
269
+ pendingRequests.current.delete(msg.id);
270
+ pending.resolve(msg);
271
+ }
272
+ }
273
+ } catch (e) {
274
+ console.error("[useDaemon] Failed to parse message:", e);
275
+ }
276
+ }, []);
277
+ const connect = react.useCallback(() => {
278
+ if (wsRef.current?.readyState === WebSocket.OPEN) return;
279
+ if (reconnectTimeoutRef.current) {
280
+ clearTimeout(reconnectTimeoutRef.current);
281
+ reconnectTimeoutRef.current = null;
282
+ }
283
+ try {
284
+ const ws = new WebSocket(url);
285
+ ws.onopen = () => {
286
+ console.log("[useDaemon] Connected to daemon");
287
+ };
288
+ ws.onmessage = handleMessage;
289
+ ws.onclose = () => {
290
+ console.log("[useDaemon] Disconnected from daemon");
291
+ setState((prev) => ({ ...prev, connected: false }));
292
+ wsRef.current = null;
293
+ if (autoReconnect) {
294
+ reconnectTimeoutRef.current = setTimeout(() => {
295
+ console.log("[useDaemon] Attempting to reconnect...");
296
+ connect();
297
+ }, reconnectDelay);
298
+ }
299
+ };
300
+ ws.onerror = (e) => {
301
+ console.error("[useDaemon] WebSocket error:", e);
302
+ setError("Failed to connect to Skema daemon. Is it running?");
303
+ };
304
+ wsRef.current = ws;
305
+ } catch (e) {
306
+ console.error("[useDaemon] Failed to create WebSocket:", e);
307
+ setError("Failed to connect to Skema daemon");
308
+ }
309
+ }, [url, autoReconnect, reconnectDelay, handleMessage]);
310
+ const disconnect = react.useCallback(() => {
311
+ if (reconnectTimeoutRef.current) {
312
+ clearTimeout(reconnectTimeoutRef.current);
313
+ reconnectTimeoutRef.current = null;
314
+ }
315
+ if (wsRef.current) {
316
+ wsRef.current.close();
317
+ wsRef.current = null;
318
+ }
319
+ setState((prev) => ({ ...prev, connected: false }));
320
+ }, []);
321
+ const setProvider = react.useCallback(async (provider) => {
322
+ try {
323
+ const response = await sendRequest("set-provider", { provider });
324
+ return response.type === "provider-changed";
325
+ } catch (e) {
326
+ console.error("[useDaemon] Failed to set provider:", e);
327
+ return false;
328
+ }
329
+ }, [sendRequest]);
330
+ const generate = react.useCallback(async (annotation, onEvent) => {
331
+ const id = nextId();
332
+ if (onEvent) {
333
+ eventCallbacks.current.set(id, onEvent);
334
+ }
335
+ setIsGenerating(true);
336
+ return new Promise((resolve, reject) => {
337
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
338
+ setIsGenerating(false);
339
+ reject(new Error("Not connected to daemon"));
340
+ return;
341
+ }
342
+ pendingRequests.current.set(id, { resolve, reject });
343
+ wsRef.current.send(JSON.stringify({
344
+ id,
345
+ type: "generate",
346
+ annotation
347
+ }));
348
+ });
349
+ }, [nextId]);
350
+ const revert = react.useCallback(async (annotationId) => {
351
+ const response = await sendRequest("revert", { annotationId });
352
+ return { success: response.success, message: response.message || "" };
353
+ }, [sendRequest]);
354
+ const readFile = react.useCallback(async (path) => {
355
+ const response = await sendRequest("read-file", { path });
356
+ return response.content;
357
+ }, [sendRequest]);
358
+ const writeFile = react.useCallback(async (path, content) => {
359
+ const response = await sendRequest("write-file", { path, content });
360
+ return response.type === "write-success";
361
+ }, [sendRequest]);
362
+ const runCommand = react.useCallback(async (command) => {
363
+ const response = await sendRequest("run-command", { command });
364
+ return {
365
+ stdout: response.stdout || "",
366
+ stderr: response.stderr || "",
367
+ exitCode: response.exitCode ?? 0
368
+ };
369
+ }, [sendRequest]);
370
+ react.useEffect(() => {
371
+ if (autoConnect) {
372
+ connect();
373
+ }
374
+ return () => {
375
+ disconnect();
376
+ };
377
+ }, [autoConnect, connect, disconnect]);
378
+ return {
379
+ state,
380
+ isGenerating,
381
+ error,
382
+ connect,
383
+ disconnect,
384
+ setProvider,
385
+ generate,
386
+ revert,
387
+ readFile,
388
+ writeFile,
389
+ runCommand
390
+ };
391
+ }
180
392
 
181
393
  // src/utils/coordinates.ts
182
394
  function getViewportInfo() {
@@ -592,349 +804,777 @@ function findNearbyElementsWithStyles(bounds, maxElements = 5) {
592
804
  }
593
805
  return diverse.map(createNearbyElement);
594
806
  }
595
- var styles = {
596
- popup: {
807
+
808
+ // src/lib/utils.ts
809
+ async function blobToBase64(blob) {
810
+ return new Promise((resolve, reject) => {
811
+ const reader = new FileReader();
812
+ reader.onload = () => {
813
+ const result = reader.result;
814
+ resolve(result);
815
+ };
816
+ reader.onerror = () => reject(reader.error);
817
+ reader.readAsDataURL(blob);
818
+ });
819
+ }
820
+ function addGridToSvg(svgString, opts = {}) {
821
+ const { color = "#0066FF", size = 100, labels = true } = opts;
822
+ const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/);
823
+ if (!viewBoxMatch) return svgString;
824
+ const [x, y, w, h] = viewBoxMatch[1].split(" ").map(Number);
825
+ const gridElements = [];
826
+ for (let i = 0; i <= Math.ceil(w / size); i++) {
827
+ const xPos = i * size;
828
+ if (i > 0) {
829
+ gridElements.push(
830
+ `<line x1="${xPos}" y1="0" x2="${xPos}" y2="${h}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
831
+ );
832
+ }
833
+ if (labels) {
834
+ const colLabel = String.fromCharCode(65 + i);
835
+ gridElements.push(
836
+ `<text x="${xPos + size / 2}" y="16" fill="${color}" font-size="12" font-family="sans-serif" text-anchor="middle">${colLabel}</text>`
837
+ );
838
+ }
839
+ }
840
+ for (let i = 0; i <= Math.ceil(h / size); i++) {
841
+ const yPos = i * size;
842
+ gridElements.push(
843
+ `<line x1="0" y1="${yPos}" x2="${w}" y2="${yPos}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
844
+ );
845
+ if (labels && i < Math.ceil(h / size)) {
846
+ gridElements.push(
847
+ `<text x="8" y="${yPos + size / 2 + 4}" fill="${color}" font-size="12" font-family="sans-serif">${i}</text>`
848
+ );
849
+ }
850
+ }
851
+ const gridGroup = `<g id="skema-grid" transform="translate(${x}, ${y})">${gridElements.join("")}</g>`;
852
+ return svgString.replace("</svg>", `${gridGroup}</svg>`);
853
+ }
854
+ function getGridCellReference(x, y, gridSize = 100) {
855
+ const col = Math.floor(x / gridSize);
856
+ const row = Math.floor(y / gridSize);
857
+ const colLabel = String.fromCharCode(65 + col);
858
+ return `${colLabel}${row}`;
859
+ }
860
+ function extractTextFromShapes(shapes) {
861
+ const textContent = [];
862
+ for (const shape of shapes) {
863
+ const s = shape;
864
+ if (s.type === "text" || s.type === "note") {
865
+ if (s.props?.text) {
866
+ textContent.push(s.props.text);
867
+ }
868
+ if (s.props?.richText && typeof s.props.richText === "object") {
869
+ const rt = s.props.richText;
870
+ if (rt.content) {
871
+ for (const block of rt.content) {
872
+ if (block.content) {
873
+ for (const inline of block.content) {
874
+ if (inline.text) {
875
+ textContent.push(inline.text);
876
+ }
877
+ }
878
+ }
879
+ }
880
+ }
881
+ }
882
+ }
883
+ }
884
+ return textContent.filter(Boolean).join("\n");
885
+ }
886
+ var SLogoIcon = ({ size = 32 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 166 161", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
887
+ /* @__PURE__ */ jsxRuntime.jsx(
888
+ "path",
889
+ {
890
+ 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",
891
+ fill: "#FF6800"
892
+ }
893
+ ),
894
+ /* @__PURE__ */ jsxRuntime.jsx(
895
+ "path",
896
+ {
897
+ 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",
898
+ fill: "white"
899
+ }
900
+ )
901
+ ] });
902
+ var ChevronLeftIcon = ({ size = 24 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx(
903
+ "path",
904
+ {
905
+ d: "M15 18L9 12L15 6",
906
+ stroke: "#9CA3AF",
907
+ strokeWidth: "2.5",
908
+ strokeLinecap: "round",
909
+ strokeLinejoin: "round"
910
+ }
911
+ ) });
912
+ var ChevronRightIcon = ({ size = 24 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx(
913
+ "path",
914
+ {
915
+ d: "M9 6L15 12L9 18",
916
+ stroke: "#9CA3AF",
917
+ strokeWidth: "2.5",
918
+ strokeLinecap: "round",
919
+ strokeLinejoin: "round"
920
+ }
921
+ ) });
922
+ var SelectIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
923
+ /* @__PURE__ */ jsxRuntime.jsx(
924
+ "path",
925
+ {
926
+ 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",
927
+ fill: "#F24E1E",
928
+ opacity: isSelected ? 1 : 0.7
929
+ }
930
+ ),
931
+ /* @__PURE__ */ jsxRuntime.jsx(
932
+ "path",
933
+ {
934
+ d: "M9 10L9 18.5L11.5 16L14 20L15.5 19L13 15L16 14.5L9 10Z",
935
+ fill: "white"
936
+ }
937
+ )
938
+ ] });
939
+ var DrawIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
940
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "25", height: "25", rx: "2", fill: isSelected ? "#00C851" : "#00C851", opacity: isSelected ? 1 : 0.7 }),
941
+ /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsxRuntime.jsx(
942
+ "path",
943
+ {
944
+ fillRule: "evenodd",
945
+ clipRule: "evenodd",
946
+ 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",
947
+ fill: "white"
948
+ }
949
+ ) })
950
+ ] });
951
+ var LassoIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
952
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "25", height: "25", rx: "12.5", fill: isSelected ? "#2C7FFF" : "#2C7FFF", opacity: isSelected ? 1 : 0.7 }),
953
+ /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsxRuntime.jsx(
954
+ "path",
955
+ {
956
+ fillRule: "evenodd",
957
+ clipRule: "evenodd",
958
+ 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",
959
+ fill: "white"
960
+ }
961
+ ) })
962
+ ] });
963
+ var EraseIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "36", height: "30", viewBox: "0 0 30 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
964
+ /* @__PURE__ */ jsxRuntime.jsx(
965
+ "path",
966
+ {
967
+ 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",
968
+ fill: "#FFBA00",
969
+ opacity: isSelected ? 1 : 0.7
970
+ }
971
+ ),
972
+ /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(15, 12.5)", children: /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "rotate(-45)", children: [
973
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "-6", y: "-3", width: "12", height: "6", rx: "1", fill: "none", stroke: "white", strokeWidth: "1.5" }),
974
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "-2", y1: "-3", x2: "-2", y2: "3", stroke: "white", strokeWidth: "1.5" })
975
+ ] }) })
976
+ ] });
977
+ var ShapesIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "30", height: "30", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
978
+ /* @__PURE__ */ jsxRuntime.jsx(
979
+ "path",
980
+ {
981
+ 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",
982
+ fill: "#FF6800",
983
+ opacity: isSelected ? 1 : 0.7
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(5.5, 7)", children: [
987
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0", y: "6", width: "4", height: "4", rx: "0.5", fill: "white" }),
988
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "4", r: "3", fill: "white" }),
989
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 11L7.5 6.5L10 11H5Z", fill: "white" })
990
+ ] })
991
+ ] });
992
+ var GEO_SHAPES = [
993
+ "rectangle",
994
+ "ellipse",
995
+ "triangle",
996
+ "diamond",
997
+ "pentagon",
998
+ "hexagon",
999
+ "octagon",
1000
+ "star",
1001
+ "rhombus",
1002
+ "rhombus-2",
1003
+ "oval",
1004
+ "trapezoid",
1005
+ "arrow-right",
1006
+ "arrow-left",
1007
+ "arrow-up",
1008
+ "arrow-down",
1009
+ "x-box",
1010
+ "check-box",
1011
+ "cloud",
1012
+ "heart"
1013
+ ];
1014
+ var ShapeIcon = ({ shape, size = 20 }) => {
1015
+ const stroke = "currentColor";
1016
+ const strokeWidth = 1.5;
1017
+ const fill = "none";
1018
+ switch (shape) {
1019
+ case "rectangle":
1020
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "1" }) });
1021
+ case "ellipse":
1022
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: "12", cy: "12", rx: "9", ry: "7" }) });
1023
+ case "triangle":
1024
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4L21 20H3L12 4Z" }) });
1025
+ case "diamond":
1026
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L22 12L12 22L2 12L12 2Z" }) });
1027
+ case "pentagon":
1028
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L22 9L18 21H6L2 9L12 2Z" }) });
1029
+ case "hexagon":
1030
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L21 7V17L12 22L3 17V7L12 2Z" }) });
1031
+ case "octagon":
1032
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 2H16L22 8V16L16 22H8L2 16V8L8 2Z" }) });
1033
+ case "star":
1034
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L14.5 9H22L16 13.5L18.5 21L12 16.5L5.5 21L8 13.5L2 9H9.5L12 2Z" }) });
1035
+ case "rhombus":
1036
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3L20 12L12 21L4 12L12 3Z" }) });
1037
+ case "rhombus-2":
1038
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 12L12 4L22 12L12 20L2 12Z" }) });
1039
+ case "oval":
1040
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: "12", cy: "12", rx: "6", ry: "9" }) });
1041
+ case "trapezoid":
1042
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 6H18L21 18H3L6 6Z" }) });
1043
+ case "arrow-right":
1044
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 8V16H12V20L20 12L12 4V8H4Z" }) });
1045
+ case "arrow-left":
1046
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 8V16H12V20L4 12L12 4V8H20Z" }) });
1047
+ case "arrow-up":
1048
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 22V10L4 10L12 2L20 10H16V22H8Z" }) });
1049
+ case "arrow-down":
1050
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 2V14L4 14L12 22L20 14H16V2H8Z" }) });
1051
+ case "x-box":
1052
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: [
1053
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }),
1054
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 8L16 16M16 8L8 16" })
1055
+ ] });
1056
+ case "check-box":
1057
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }),
1059
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 12L10 15L17 8" })
1060
+ ] });
1061
+ case "cloud":
1062
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.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" }) });
1063
+ case "heart":
1064
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.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" }) });
1065
+ default:
1066
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill, stroke, strokeWidth, children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "1" }) });
1067
+ }
1068
+ };
1069
+ var ShapePicker = ({ isOpen, onClose, onSelectShape, anchorRef }) => {
1070
+ const editor = tldraw.useEditor();
1071
+ const pickerRef = react.useRef(null);
1072
+ const [selectedShape, setSelectedShape] = react.useState("rectangle");
1073
+ react.useEffect(() => {
1074
+ const currentGeo = editor.getStyleForNextShape(tldraw.GeoShapeGeoStyle);
1075
+ if (currentGeo && GEO_SHAPES.includes(currentGeo)) {
1076
+ setSelectedShape(currentGeo);
1077
+ }
1078
+ }, [editor, isOpen]);
1079
+ react.useEffect(() => {
1080
+ if (!isOpen) return;
1081
+ const handleClickOutside = (e) => {
1082
+ if (pickerRef.current && !pickerRef.current.contains(e.target) && anchorRef.current && !anchorRef.current.contains(e.target)) {
1083
+ onClose();
1084
+ }
1085
+ };
1086
+ document.addEventListener("mousedown", handleClickOutside);
1087
+ return () => document.removeEventListener("mousedown", handleClickOutside);
1088
+ }, [isOpen, onClose, anchorRef]);
1089
+ react.useEffect(() => {
1090
+ if (!isOpen) return;
1091
+ const handleKeyDown = (e) => {
1092
+ if (e.key === "Escape") {
1093
+ onClose();
1094
+ }
1095
+ };
1096
+ document.addEventListener("keydown", handleKeyDown);
1097
+ return () => document.removeEventListener("keydown", handleKeyDown);
1098
+ }, [isOpen, onClose]);
1099
+ if (!isOpen) return null;
1100
+ const handleShapeClick = (e, shape) => {
1101
+ e.preventDefault();
1102
+ e.stopPropagation();
1103
+ setSelectedShape(shape);
1104
+ onSelectShape(shape);
1105
+ };
1106
+ const stopAllEvents = (e) => {
1107
+ e.preventDefault();
1108
+ e.stopPropagation();
1109
+ };
1110
+ const anchorRect = anchorRef.current?.getBoundingClientRect();
1111
+ const pickerStyle = anchorRect ? {
597
1112
  position: "fixed",
598
- transform: "translateX(-50%)",
599
- width: 280,
600
- padding: "12px 16px 14px",
601
- background: "#1a1a1a",
602
- borderRadius: 16,
603
- boxShadow: "0 4px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08)",
604
- cursor: "default",
605
- zIndex: 100001,
606
- fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1113
+ bottom: window.innerHeight - anchorRect.top + 8,
1114
+ left: anchorRect.left + anchorRect.width / 2,
1115
+ transform: "translateX(-50%)"
1116
+ } : {};
1117
+ return /* @__PURE__ */ jsxRuntime.jsx(
1118
+ "div",
1119
+ {
1120
+ ref: pickerRef,
1121
+ style: {
1122
+ ...pickerStyle,
1123
+ backgroundColor: "white",
1124
+ borderRadius: 8,
1125
+ boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
1126
+ padding: 8,
1127
+ zIndex: 1e5,
1128
+ pointerEvents: "auto"
1129
+ },
1130
+ onClick: stopAllEvents,
1131
+ onPointerDown: stopAllEvents,
1132
+ onMouseDown: stopAllEvents,
1133
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1134
+ "div",
1135
+ {
1136
+ style: {
1137
+ display: "grid",
1138
+ gridTemplateColumns: "repeat(4, 1fr)",
1139
+ gap: 2
1140
+ },
1141
+ children: GEO_SHAPES.map((shape) => /* @__PURE__ */ jsxRuntime.jsx(
1142
+ "button",
1143
+ {
1144
+ onClick: (e) => handleShapeClick(e, shape),
1145
+ onPointerDown: stopAllEvents,
1146
+ onMouseDown: stopAllEvents,
1147
+ title: shape.replace(/-/g, " "),
1148
+ style: {
1149
+ display: "flex",
1150
+ alignItems: "center",
1151
+ justifyContent: "center",
1152
+ width: 32,
1153
+ height: 32,
1154
+ border: "none",
1155
+ borderRadius: 4,
1156
+ backgroundColor: selectedShape === shape ? "#e8f4ff" : "transparent",
1157
+ color: selectedShape === shape ? "#2c7fff" : "#333",
1158
+ cursor: "pointer",
1159
+ transition: "background-color 0.1s"
1160
+ },
1161
+ onMouseEnter: (e) => {
1162
+ if (selectedShape !== shape) {
1163
+ e.currentTarget.style.backgroundColor = "#f5f5f5";
1164
+ }
1165
+ },
1166
+ onMouseLeave: (e) => {
1167
+ if (selectedShape !== shape) {
1168
+ e.currentTarget.style.backgroundColor = "transparent";
1169
+ }
1170
+ },
1171
+ children: /* @__PURE__ */ jsxRuntime.jsx(ShapeIcon, { shape, size: 18 })
1172
+ },
1173
+ shape
1174
+ ))
1175
+ }
1176
+ )
1177
+ }
1178
+ );
1179
+ };
1180
+ var TOOL_COUNT = 4;
1181
+ var EXIT_DURATION = 0.12;
1182
+ var EXIT_STAGGER = 0.01;
1183
+ var smoothEasing = [0.4, 0, 0.2, 1];
1184
+ var toolVariants = {
1185
+ hidden: {
607
1186
  opacity: 0,
608
- transition: "opacity 0.2s ease, transform 0.2s ease"
1187
+ scale: 0.8,
1188
+ width: 0,
1189
+ marginRight: -6
609
1190
  },
610
- popupEnter: {
1191
+ visible: (i) => ({
611
1192
  opacity: 1,
612
- transform: "translateX(-50%) scale(1) translateY(0)"
613
- },
614
- popupExit: {
1193
+ scale: 1,
1194
+ width: 40,
1195
+ marginRight: 0,
1196
+ transition: {
1197
+ opacity: { duration: 0.15, delay: i * 0.04 },
1198
+ scale: { type: "spring", stiffness: 400, damping: 25, delay: i * 0.04 },
1199
+ width: { type: "spring", stiffness: 400, damping: 30, delay: i * 0.03 },
1200
+ marginRight: { duration: 0.1, delay: i * 0.03 }
1201
+ }
1202
+ }),
1203
+ exit: (i) => ({
615
1204
  opacity: 0,
616
- transform: "translateX(-50%) scale(0.95) translateY(4px)"
617
- },
618
- header: {
619
- display: "flex",
620
- alignItems: "center",
621
- justifyContent: "space-between",
622
- marginBottom: 9
623
- },
624
- element: {
625
- fontSize: 12,
626
- fontWeight: 400,
627
- color: "rgba(255, 255, 255, 0.5)",
628
- maxWidth: "100%",
629
- overflow: "hidden",
630
- textOverflow: "ellipsis",
631
- whiteSpace: "nowrap",
632
- flex: 1
633
- },
634
- quote: {
635
- fontSize: 12,
636
- fontStyle: "italic",
637
- color: "rgba(255, 255, 255, 0.6)",
638
- marginBottom: 8,
639
- padding: "6px 8px",
640
- background: "rgba(255, 255, 255, 0.05)",
641
- borderRadius: 4,
642
- lineHeight: 1.45
643
- },
644
- textarea: {
645
- width: "100%",
646
- padding: "8px 10px",
647
- fontSize: 13,
648
- fontFamily: "inherit",
649
- background: "rgba(255, 255, 255, 0.05)",
650
- color: "#fff",
651
- border: "1px solid rgba(255, 255, 255, 0.15)",
652
- borderRadius: 8,
653
- resize: "none",
654
- outline: "none",
655
- transition: "border-color 0.15s ease",
656
- boxSizing: "border-box"
657
- },
658
- actions: {
659
- display: "flex",
660
- justifyContent: "flex-end",
661
- gap: 6,
662
- marginTop: 8
663
- },
664
- button: {
665
- padding: "6px 14px",
666
- fontSize: 12,
667
- fontWeight: 500,
668
- borderRadius: 16,
669
- border: "none",
670
- cursor: "pointer",
671
- transition: "background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease"
1205
+ scale: 0.8,
1206
+ width: 0,
1207
+ marginRight: -6,
1208
+ transition: {
1209
+ opacity: { duration: 0.06, ease: "easeOut" },
1210
+ scale: { duration: EXIT_DURATION, ease: smoothEasing },
1211
+ width: { duration: EXIT_DURATION, ease: smoothEasing, delay: EXIT_STAGGER * (TOOL_COUNT - 1 - i) },
1212
+ marginRight: { duration: EXIT_DURATION, ease: smoothEasing, delay: EXIT_STAGGER * (TOOL_COUNT - 1 - i) }
1213
+ }
1214
+ })
1215
+ };
1216
+ var chevronVariants = {
1217
+ hidden: {
1218
+ opacity: 0,
1219
+ scale: 0.8,
1220
+ width: 0
672
1221
  },
673
- cancelButton: {
674
- background: "transparent",
675
- color: "rgba(255, 255, 255, 0.5)"
1222
+ visible: {
1223
+ opacity: 0.6,
1224
+ scale: 1,
1225
+ width: 28,
1226
+ transition: {
1227
+ opacity: { delay: 0.1, duration: 0.1 },
1228
+ scale: { delay: 0.1, type: "spring", stiffness: 500, damping: 30 },
1229
+ width: { delay: 0.08, duration: 0.12, ease: "easeOut" }
1230
+ }
676
1231
  },
677
- submitButton: {
678
- color: "white"
1232
+ exit: {
1233
+ opacity: 0,
1234
+ scale: 0.8,
1235
+ width: 0,
1236
+ transition: {
1237
+ opacity: { duration: 0.06 },
1238
+ scale: { duration: 0.08 },
1239
+ width: { duration: 0.1, ease: smoothEasing }
1240
+ }
679
1241
  }
680
1242
  };
681
- var AnnotationPopup = react.forwardRef(
682
- function AnnotationPopup2({
683
- element,
684
- selectedText,
685
- placeholder = "What should change?",
686
- initialValue = "",
687
- submitLabel = "Add",
688
- onSubmit,
689
- onCancel,
690
- style,
691
- accentColor = "#3c82f7",
692
- isExiting = false,
693
- isMultiSelect = false
694
- }, ref) {
695
- const [text, setText] = react.useState(initialValue);
696
- const [isShaking, setIsShaking] = react.useState(false);
697
- const [animState, setAnimState] = react.useState("initial");
698
- const [isFocused, setIsFocused] = react.useState(false);
699
- const textareaRef = react.useRef(null);
700
- const popupRef = react.useRef(null);
701
- react.useEffect(() => {
702
- if (isExiting && animState !== "exit") {
703
- setAnimState("exit");
704
- }
705
- }, [isExiting, animState]);
706
- react.useEffect(() => {
707
- requestAnimationFrame(() => {
708
- setAnimState("enter");
709
- });
710
- const enterTimer = setTimeout(() => {
711
- setAnimState("entered");
712
- }, 200);
713
- const focusTimer = setTimeout(() => {
714
- const textarea = textareaRef.current;
715
- if (textarea) {
716
- textarea.focus();
717
- textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
718
- textarea.scrollTop = textarea.scrollHeight;
719
- }
720
- }, 50);
721
- return () => {
722
- clearTimeout(enterTimer);
723
- clearTimeout(focusTimer);
724
- };
725
- }, []);
726
- const shake = react.useCallback(() => {
727
- setIsShaking(true);
728
- setTimeout(() => {
729
- setIsShaking(false);
730
- textareaRef.current?.focus();
731
- }, 250);
732
- }, []);
733
- react.useImperativeHandle(ref, () => ({
734
- shake
735
- }), [shake]);
736
- const handleCancel = react.useCallback(() => {
737
- setAnimState("exit");
738
- setTimeout(() => {
739
- onCancel();
740
- }, 150);
741
- }, [onCancel]);
742
- const handleSubmit = react.useCallback(() => {
743
- if (!text.trim()) return;
744
- onSubmit(text.trim());
745
- }, [text, onSubmit]);
746
- const handleKeyDown = react.useCallback(
747
- (e) => {
748
- if (e.nativeEvent.isComposing) return;
749
- if (e.key === "Enter" && !e.shiftKey) {
750
- e.preventDefault();
751
- handleSubmit();
752
- }
753
- if (e.key === "Escape") {
754
- handleCancel();
1243
+ var ToolbarButton = ({
1244
+ onClick,
1245
+ isSelected,
1246
+ icon,
1247
+ label,
1248
+ buttonRef,
1249
+ index = 0
1250
+ }) => {
1251
+ const handleClick = (e) => {
1252
+ e.preventDefault();
1253
+ e.stopPropagation();
1254
+ onClick();
1255
+ };
1256
+ return /* @__PURE__ */ jsxRuntime.jsx(
1257
+ framerMotion.motion.button,
1258
+ {
1259
+ ref: buttonRef,
1260
+ custom: index,
1261
+ variants: toolVariants,
1262
+ initial: "hidden",
1263
+ animate: "visible",
1264
+ exit: "exit",
1265
+ onClick: handleClick,
1266
+ title: label,
1267
+ type: "button",
1268
+ whileHover: { scale: 1.05 },
1269
+ whileTap: { scale: 0.95 },
1270
+ style: {
1271
+ display: "flex",
1272
+ alignItems: "center",
1273
+ justifyContent: "center",
1274
+ height: 40,
1275
+ minWidth: 0,
1276
+ border: "none",
1277
+ borderRadius: 8,
1278
+ backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1279
+ cursor: "pointer",
1280
+ pointerEvents: "auto",
1281
+ overflow: "hidden",
1282
+ flexShrink: 0
1283
+ },
1284
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1285
+ framerMotion.motion.span,
1286
+ {
1287
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
1288
+ initial: { opacity: 0, scale: 0.8 },
1289
+ animate: { opacity: 1, scale: 1 },
1290
+ exit: { opacity: 0, scale: 0.8 },
1291
+ transition: { duration: 0.12 },
1292
+ children: icon
755
1293
  }
1294
+ )
1295
+ }
1296
+ );
1297
+ };
1298
+ var LogoShapesButton = ({
1299
+ isExpanded,
1300
+ isHovered,
1301
+ isSelected,
1302
+ onClick,
1303
+ onShapesClick,
1304
+ onMouseEnter,
1305
+ onMouseLeave,
1306
+ buttonRef
1307
+ }) => {
1308
+ const handleClick = (e) => {
1309
+ e.preventDefault();
1310
+ e.stopPropagation();
1311
+ if (isExpanded) {
1312
+ onShapesClick();
1313
+ } else {
1314
+ onClick();
1315
+ }
1316
+ };
1317
+ return /* @__PURE__ */ jsxRuntime.jsx(
1318
+ framerMotion.motion.button,
1319
+ {
1320
+ ref: buttonRef,
1321
+ onClick: handleClick,
1322
+ onMouseEnter,
1323
+ onMouseLeave,
1324
+ title: isExpanded ? "Shapes (G)" : "Expand toolbar",
1325
+ type: "button",
1326
+ whileHover: { scale: 1.08 },
1327
+ whileTap: { scale: 0.95 },
1328
+ style: {
1329
+ display: "flex",
1330
+ alignItems: "center",
1331
+ justifyContent: "center",
1332
+ width: 40,
1333
+ height: 40,
1334
+ border: "none",
1335
+ borderRadius: 8,
1336
+ backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1337
+ cursor: "pointer",
1338
+ pointerEvents: "auto",
1339
+ position: "relative"
756
1340
  },
757
- [handleSubmit, handleCancel]
758
- );
759
- const popupStyle = {
760
- ...styles.popup,
761
- ...animState === "enter" || animState === "entered" ? styles.popupEnter : {},
762
- ...animState === "exit" ? styles.popupExit : {},
763
- ...isShaking ? {
764
- animation: "skema-shake 0.25s ease-out"
765
- } : {},
766
- ...style
767
- };
768
- const textareaStyle = {
769
- ...styles.textarea,
770
- ...isFocused ? { borderColor: accentColor } : {}
771
- };
772
- const effectiveAccentColor = isMultiSelect ? "#34C759" : accentColor;
773
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
774
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
775
- @keyframes skema-shake {
776
- 0%, 100% { transform: translateX(-50%) scale(1) translateY(0) translateX(0); }
777
- 20% { transform: translateX(-50%) scale(1) translateY(0) translateX(-3px); }
778
- 40% { transform: translateX(-50%) scale(1) translateY(0) translateX(3px); }
779
- 60% { transform: translateX(-50%) scale(1) translateY(0) translateX(-2px); }
780
- 80% { transform: translateX(-50%) scale(1) translateY(0) translateX(2px); }
781
- }
782
- ` }),
783
- /* @__PURE__ */ jsxRuntime.jsxs(
784
- "div",
1341
+ children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "popLayout", children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(
1342
+ framerMotion.motion.div,
785
1343
  {
786
- ref: popupRef,
787
- "data-skema": "annotation-popup",
788
- style: popupStyle,
789
- onClick: (e) => e.stopPropagation(),
790
- onPointerDown: (e) => e.stopPropagation(),
791
- children: [
792
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.header, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.element, children: element }) }),
793
- selectedText && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.quote, children: [
794
- "\u201C",
795
- selectedText.slice(0, 80),
796
- selectedText.length > 80 ? "..." : "",
797
- "\u201D"
798
- ] }),
799
- /* @__PURE__ */ jsxRuntime.jsx(
800
- "textarea",
801
- {
802
- ref: textareaRef,
803
- style: textareaStyle,
804
- placeholder,
805
- value: text,
806
- onChange: (e) => setText(e.target.value),
807
- onFocus: () => setIsFocused(true),
808
- onBlur: () => setIsFocused(false),
809
- rows: 2,
810
- onKeyDown: handleKeyDown
811
- }
812
- ),
813
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
814
- /* @__PURE__ */ jsxRuntime.jsx(
815
- "button",
816
- {
817
- style: { ...styles.button, ...styles.cancelButton },
818
- onClick: handleCancel,
819
- onMouseEnter: (e) => {
820
- e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
821
- e.currentTarget.style.color = "rgba(255, 255, 255, 0.8)";
822
- },
823
- onMouseLeave: (e) => {
824
- e.currentTarget.style.background = "transparent";
825
- e.currentTarget.style.color = "rgba(255, 255, 255, 0.5)";
826
- },
827
- children: "Cancel"
828
- }
829
- ),
830
- /* @__PURE__ */ jsxRuntime.jsx(
831
- "button",
832
- {
833
- style: {
834
- ...styles.button,
835
- ...styles.submitButton,
836
- backgroundColor: effectiveAccentColor,
837
- opacity: text.trim() ? 1 : 0.4
838
- },
839
- onClick: handleSubmit,
840
- disabled: !text.trim(),
841
- onMouseEnter: (e) => {
842
- if (text.trim()) {
843
- e.currentTarget.style.filter = "brightness(0.9)";
844
- }
845
- },
846
- onMouseLeave: (e) => {
847
- e.currentTarget.style.filter = "none";
848
- },
849
- children: submitLabel
850
- }
851
- )
852
- ] })
853
- ]
1344
+ initial: { opacity: 0, scale: 0.8 },
1345
+ animate: { opacity: 1, scale: 1 },
1346
+ exit: { opacity: 0, scale: 0.85 },
1347
+ transition: {
1348
+ type: "spring",
1349
+ stiffness: 500,
1350
+ damping: 30,
1351
+ opacity: { duration: 0.08 }
1352
+ },
1353
+ children: /* @__PURE__ */ jsxRuntime.jsx(ShapesIcon, { isSelected })
1354
+ },
1355
+ "shapes"
1356
+ ) : isHovered ? /* @__PURE__ */ jsxRuntime.jsx(
1357
+ framerMotion.motion.div,
1358
+ {
1359
+ initial: { opacity: 0, x: -3 },
1360
+ animate: { opacity: 1, x: 0 },
1361
+ exit: { opacity: 0, x: 3 },
1362
+ transition: { duration: 0.08, ease: "easeOut" },
1363
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, { size: 20 })
1364
+ },
1365
+ "chevron"
1366
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1367
+ framerMotion.motion.div,
1368
+ {
1369
+ initial: { opacity: 0, scale: 0.9 },
1370
+ animate: { opacity: 1, scale: 1 },
1371
+ exit: { opacity: 0, scale: 0.85 },
1372
+ transition: {
1373
+ type: "spring",
1374
+ stiffness: 500,
1375
+ damping: 30,
1376
+ opacity: { duration: 0.06 }
1377
+ },
1378
+ children: /* @__PURE__ */ jsxRuntime.jsx(SLogoIcon, { size: 32 })
1379
+ },
1380
+ "s"
1381
+ ) })
1382
+ }
1383
+ );
1384
+ };
1385
+ var CollapseButton = ({ onClick }) => {
1386
+ return /* @__PURE__ */ jsxRuntime.jsx(
1387
+ framerMotion.motion.button,
1388
+ {
1389
+ variants: chevronVariants,
1390
+ initial: "hidden",
1391
+ animate: "visible",
1392
+ exit: "exit",
1393
+ onClick: (e) => {
1394
+ e.preventDefault();
1395
+ e.stopPropagation();
1396
+ onClick();
1397
+ },
1398
+ title: "Collapse toolbar",
1399
+ type: "button",
1400
+ whileHover: { opacity: 1, scale: 1.1 },
1401
+ whileTap: { scale: 0.9 },
1402
+ style: {
1403
+ display: "flex",
1404
+ alignItems: "center",
1405
+ justifyContent: "center",
1406
+ height: 40,
1407
+ minWidth: 0,
1408
+ border: "none",
1409
+ borderRadius: 6,
1410
+ backgroundColor: "transparent",
1411
+ cursor: "pointer",
1412
+ pointerEvents: "auto",
1413
+ marginLeft: 2,
1414
+ overflow: "hidden",
1415
+ flexShrink: 0
1416
+ },
1417
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1418
+ framerMotion.motion.span,
1419
+ {
1420
+ style: { display: "flex", alignItems: "center", justifyContent: "center" },
1421
+ initial: { opacity: 0 },
1422
+ animate: { opacity: 1 },
1423
+ exit: { opacity: 0 },
1424
+ transition: { duration: 0.1 },
1425
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, { size: 20 })
854
1426
  }
855
1427
  )
856
- ] });
857
- }
858
- );
859
-
860
- // src/lib/utils.ts
861
- async function blobToBase64(blob) {
862
- return new Promise((resolve, reject) => {
863
- const reader = new FileReader();
864
- reader.onload = () => {
865
- const result = reader.result;
866
- resolve(result);
867
- };
868
- reader.onerror = () => reject(reader.error);
869
- reader.readAsDataURL(blob);
870
- });
871
- }
872
- function addGridToSvg(svgString, opts = {}) {
873
- const { color = "#0066FF", size = 100, labels = true } = opts;
874
- const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/);
875
- if (!viewBoxMatch) return svgString;
876
- const [x, y, w, h] = viewBoxMatch[1].split(" ").map(Number);
877
- const gridElements = [];
878
- for (let i = 0; i <= Math.ceil(w / size); i++) {
879
- const xPos = i * size;
880
- if (i > 0) {
881
- gridElements.push(
882
- `<line x1="${xPos}" y1="0" x2="${xPos}" y2="${h}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
883
- );
884
- }
885
- if (labels) {
886
- const colLabel = String.fromCharCode(65 + i);
887
- gridElements.push(
888
- `<text x="${xPos + size / 2}" y="16" fill="${color}" font-size="12" font-family="sans-serif" text-anchor="middle">${colLabel}</text>`
889
- );
890
1428
  }
891
- }
892
- for (let i = 0; i <= Math.ceil(h / size); i++) {
893
- const yPos = i * size;
894
- gridElements.push(
895
- `<line x1="0" y1="${yPos}" x2="${w}" y2="${yPos}" stroke="${color}" stroke-width="1" stroke-opacity="0.5"/>`
896
- );
897
- if (labels && i < Math.ceil(h / size)) {
898
- gridElements.push(
899
- `<text x="8" y="${yPos + size / 2 + 4}" fill="${color}" font-size="12" font-family="sans-serif">${i}</text>`
900
- );
1429
+ );
1430
+ };
1431
+ var SkemaToolbar = ({ onExpandedChange, onStylePanelChange }) => {
1432
+ const editor = tldraw.useEditor();
1433
+ const tools = tldraw.useTools();
1434
+ const shapesButtonRef = react.useRef(null);
1435
+ const [isExpanded, setIsExpanded] = react.useState(false);
1436
+ const [isLogoHovered, setIsLogoHovered] = react.useState(false);
1437
+ const [isShapePickerOpen, setIsShapePickerOpen] = react.useState(false);
1438
+ const [isStylePanelOpen, setIsStylePanelOpen] = react.useState(false);
1439
+ const isSelectSelected = tldraw.useIsToolSelected(tools["select"]);
1440
+ const isDrawSelected = tldraw.useIsToolSelected(tools["draw"]);
1441
+ const isLassoSelected = tldraw.useIsToolSelected(tools["lasso-select"]);
1442
+ const isEraseSelected = tldraw.useIsToolSelected(tools["eraser"]);
1443
+ const isGeoSelected = tldraw.useIsToolSelected(tools["geo"]);
1444
+ const handleExpand = (expanded) => {
1445
+ setIsExpanded(expanded);
1446
+ onExpandedChange?.(expanded);
1447
+ if (!expanded) {
1448
+ setIsStylePanelOpen(false);
1449
+ onStylePanelChange?.(false);
901
1450
  }
902
- }
903
- const gridGroup = `<g id="skema-grid" transform="translate(${x}, ${y})">${gridElements.join("")}</g>`;
904
- return svgString.replace("</svg>", `${gridGroup}</svg>`);
905
- }
906
- function getGridCellReference(x, y, gridSize = 100) {
907
- const col = Math.floor(x / gridSize);
908
- const row = Math.floor(y / gridSize);
909
- const colLabel = String.fromCharCode(65 + col);
910
- return `${colLabel}${row}`;
911
- }
912
- function extractTextFromShapes(shapes) {
913
- const textContent = [];
914
- for (const shape of shapes) {
915
- const s = shape;
916
- if (s.type === "text" || s.type === "note") {
917
- if (s.props?.text) {
918
- textContent.push(s.props.text);
919
- }
920
- if (s.props?.richText && typeof s.props.richText === "object") {
921
- const rt = s.props.richText;
922
- if (rt.content) {
923
- for (const block of rt.content) {
924
- if (block.content) {
925
- for (const inline of block.content) {
926
- if (inline.text) {
927
- textContent.push(inline.text);
1451
+ };
1452
+ const handleLogoClick = () => {
1453
+ handleExpand(true);
1454
+ };
1455
+ const handleCollapse = () => {
1456
+ handleExpand(false);
1457
+ setIsShapePickerOpen(false);
1458
+ };
1459
+ const handleShapesClick = () => {
1460
+ setIsShapePickerOpen((prev) => !prev);
1461
+ };
1462
+ const handleSelectShape = (shape) => {
1463
+ editor.setStyleForNextShapes(tldraw.GeoShapeGeoStyle, shape);
1464
+ editor.setCurrentTool("geo");
1465
+ setIsShapePickerOpen(false);
1466
+ };
1467
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1468
+ /* @__PURE__ */ jsxRuntime.jsx(
1469
+ "div",
1470
+ {
1471
+ style: {
1472
+ position: "absolute",
1473
+ bottom: 16,
1474
+ left: 0,
1475
+ right: 0,
1476
+ display: "flex",
1477
+ justifyContent: "center",
1478
+ pointerEvents: "none",
1479
+ zIndex: 99999
1480
+ },
1481
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1482
+ framerMotion.motion.div,
1483
+ {
1484
+ "data-skema": "toolbar",
1485
+ style: {
1486
+ display: "flex",
1487
+ alignItems: "center",
1488
+ gap: 6,
1489
+ padding: "6px 12px",
1490
+ backgroundColor: "white",
1491
+ borderRadius: 28,
1492
+ boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
1493
+ pointerEvents: "auto"
1494
+ },
1495
+ children: [
1496
+ /* @__PURE__ */ jsxRuntime.jsx(
1497
+ LogoShapesButton,
1498
+ {
1499
+ isExpanded,
1500
+ isHovered: isLogoHovered,
1501
+ isSelected: isGeoSelected || isShapePickerOpen,
1502
+ onClick: handleLogoClick,
1503
+ onShapesClick: handleShapesClick,
1504
+ onMouseEnter: () => setIsLogoHovered(true),
1505
+ onMouseLeave: () => setIsLogoHovered(false),
1506
+ buttonRef: shapesButtonRef
928
1507
  }
929
- }
930
- }
1508
+ ),
1509
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "popLayout", children: isExpanded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1510
+ /* @__PURE__ */ jsxRuntime.jsx(
1511
+ ToolbarButton,
1512
+ {
1513
+ onClick: () => editor.setCurrentTool("select"),
1514
+ isSelected: isSelectSelected,
1515
+ icon: /* @__PURE__ */ jsxRuntime.jsx(SelectIcon, { isSelected: isSelectSelected }),
1516
+ label: "Select (S)",
1517
+ index: 0
1518
+ },
1519
+ "select"
1520
+ ),
1521
+ /* @__PURE__ */ jsxRuntime.jsx(
1522
+ ToolbarButton,
1523
+ {
1524
+ onClick: () => editor.setCurrentTool("draw"),
1525
+ isSelected: isDrawSelected,
1526
+ icon: /* @__PURE__ */ jsxRuntime.jsx(DrawIcon, { isSelected: isDrawSelected }),
1527
+ label: "Draw (D)",
1528
+ index: 1
1529
+ },
1530
+ "draw"
1531
+ ),
1532
+ /* @__PURE__ */ jsxRuntime.jsx(
1533
+ ToolbarButton,
1534
+ {
1535
+ onClick: () => editor.setCurrentTool("lasso-select"),
1536
+ isSelected: isLassoSelected,
1537
+ icon: /* @__PURE__ */ jsxRuntime.jsx(LassoIcon, { isSelected: isLassoSelected }),
1538
+ label: "Lasso Select (L)",
1539
+ index: 2
1540
+ },
1541
+ "lasso"
1542
+ ),
1543
+ /* @__PURE__ */ jsxRuntime.jsx(
1544
+ ToolbarButton,
1545
+ {
1546
+ onClick: () => editor.setCurrentTool("eraser"),
1547
+ isSelected: isEraseSelected,
1548
+ icon: /* @__PURE__ */ jsxRuntime.jsx(EraseIcon, { isSelected: isEraseSelected }),
1549
+ label: "Eraser (E)",
1550
+ index: 3
1551
+ },
1552
+ "erase"
1553
+ ),
1554
+ /* @__PURE__ */ jsxRuntime.jsx(
1555
+ CollapseButton,
1556
+ {
1557
+ onClick: handleCollapse
1558
+ },
1559
+ "collapse"
1560
+ )
1561
+ ] }) })
1562
+ ]
931
1563
  }
932
- }
1564
+ )
933
1565
  }
934
- }
935
- }
936
- return textContent.filter(Boolean).join("\n");
937
- }
1566
+ ),
1567
+ /* @__PURE__ */ jsxRuntime.jsx(
1568
+ ShapePicker,
1569
+ {
1570
+ isOpen: isShapePickerOpen,
1571
+ onClose: () => setIsShapePickerOpen(false),
1572
+ onSelectShape: handleSelectShape,
1573
+ anchorRef: shapesButtonRef
1574
+ }
1575
+ )
1576
+ ] });
1577
+ };
938
1578
  var AnnotationMarker = ({
939
1579
  annotation,
940
1580
  index,
@@ -990,7 +1630,7 @@ var AnnotationMarker = ({
990
1630
  justifyContent: "center",
991
1631
  fontSize: 11,
992
1632
  fontWeight: 600,
993
- fontFamily: "system-ui, -apple-system, sans-serif",
1633
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
994
1634
  cursor: "pointer",
995
1635
  boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
996
1636
  transition: "all 0.15s ease",
@@ -1046,7 +1686,13 @@ var AnnotationMarker = ({
1046
1686
  }
1047
1687
  );
1048
1688
  };
1049
- var AnnotationMarkersLayer = ({ annotations, scrollOffset, hoveredMarkerId, onHover, onDelete }) => {
1689
+ var AnnotationMarkersLayer = ({
1690
+ annotations,
1691
+ scrollOffset,
1692
+ hoveredMarkerId,
1693
+ onHover,
1694
+ onDelete
1695
+ }) => {
1050
1696
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: annotations.map((annotation, index) => /* @__PURE__ */ jsxRuntime.jsx(
1051
1697
  AnnotationMarker,
1052
1698
  {
@@ -1060,292 +1706,14 @@ var AnnotationMarkersLayer = ({ annotations, scrollOffset, hoveredMarkerId, onHo
1060
1706
  annotation.id
1061
1707
  )) });
1062
1708
  };
1063
- var SelectIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1064
- /* @__PURE__ */ jsxRuntime.jsx(
1065
- "path",
1066
- {
1067
- 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",
1068
- fill: "#F24E1E",
1069
- opacity: isSelected ? 1 : 0.7
1070
- }
1071
- ),
1072
- /* @__PURE__ */ jsxRuntime.jsx(
1073
- "path",
1074
- {
1075
- d: "M9 10L9 18.5L11.5 16L14 20L15.5 19L13 15L16 14.5L9 10Z",
1076
- fill: "white"
1077
- }
1078
- )
1079
- ] });
1080
- var DrawIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1081
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "25", height: "25", rx: "2", fill: isSelected ? "#00C851" : "#00C851", opacity: isSelected ? 1 : 0.7 }),
1082
- /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsxRuntime.jsx(
1083
- "path",
1084
- {
1085
- fillRule: "evenodd",
1086
- clipRule: "evenodd",
1087
- 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",
1088
- fill: "white"
1089
- }
1090
- ) })
1091
- ] });
1092
- var LassoIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1093
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "25", height: "25", rx: "12.5", fill: isSelected ? "#2C7FFF" : "#2C7FFF", opacity: isSelected ? 1 : 0.7 }),
1094
- /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12.5, 12.5) scale(1.4) translate(-12.5, -12.5)", children: /* @__PURE__ */ jsxRuntime.jsx(
1095
- "path",
1096
- {
1097
- fillRule: "evenodd",
1098
- clipRule: "evenodd",
1099
- 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",
1100
- fill: "white"
1101
- }
1102
- ) })
1103
- ] });
1104
- var EraseIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "50", height: "42", viewBox: "0 0 30 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1105
- /* @__PURE__ */ jsxRuntime.jsx(
1106
- "path",
1107
- {
1108
- 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",
1109
- fill: "#FFBA00",
1110
- opacity: isSelected ? 1 : 0.7
1111
- }
1112
- ),
1113
- /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(15, 12.5)", children: /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "rotate(-45)", children: [
1114
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "-6", y: "-3", width: "12", height: "6", rx: "1", fill: "none", stroke: "white", strokeWidth: "1.5" }),
1115
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "-2", y1: "-3", x2: "-2", y2: "3", stroke: "white", strokeWidth: "1.5" })
1116
- ] }) })
1117
- ] });
1118
- var StarIcon = ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "42", height: "42", viewBox: "0 0 25 25", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1119
- /* @__PURE__ */ jsxRuntime.jsx(
1120
- "path",
1121
- {
1122
- 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",
1123
- fill: "#FF6800",
1124
- opacity: isSelected ? 1 : 0.7
1125
- }
1126
- ),
1127
- /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(6.5, 6) scale(0.5)", children: /* @__PURE__ */ jsxRuntime.jsx(
1128
- "path",
1129
- {
1130
- 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",
1131
- fill: "white"
1132
- }
1133
- ) })
1134
- ] });
1135
- var ToolbarButton = ({ onClick, isSelected, icon, label }) => {
1136
- const handleClick = (e) => {
1137
- e.preventDefault();
1138
- e.stopPropagation();
1139
- onClick();
1140
- };
1141
- return /* @__PURE__ */ jsxRuntime.jsx(
1142
- "button",
1143
- {
1144
- onClick: handleClick,
1145
- title: label,
1146
- type: "button",
1147
- style: {
1148
- display: "flex",
1149
- alignItems: "center",
1150
- justifyContent: "center",
1151
- width: 56,
1152
- height: 56,
1153
- border: "none",
1154
- borderRadius: 11,
1155
- backgroundColor: isSelected ? "rgba(255,255,255,0.15)" : "transparent",
1156
- cursor: "pointer",
1157
- transition: "background-color 0.15s ease",
1158
- pointerEvents: "auto"
1159
- },
1160
- children: icon
1161
- }
1162
- );
1163
- };
1164
- var SkemaToolbar = () => {
1165
- const editor = tldraw.useEditor();
1166
- const tools = tldraw.useTools();
1167
- const isSelectSelected = tldraw.useIsToolSelected(tools["select"]);
1168
- const isDrawSelected = tldraw.useIsToolSelected(tools["draw"]);
1169
- const isLassoSelected = tldraw.useIsToolSelected(tools["lasso-select"]);
1170
- const isEraseSelected = tldraw.useIsToolSelected(tools["eraser"]);
1171
- const [isStarSelected] = react.useState(false);
1172
- return /* @__PURE__ */ jsxRuntime.jsxs(
1173
- "div",
1174
- {
1175
- "data-skema": "toolbar",
1176
- style: {
1177
- position: "absolute",
1178
- bottom: 16,
1179
- left: "50%",
1180
- transform: "translateX(-50%)",
1181
- display: "flex",
1182
- alignItems: "center",
1183
- gap: 11,
1184
- padding: "11px 17px",
1185
- backgroundColor: "white",
1186
- borderRadius: 36,
1187
- boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
1188
- pointerEvents: "auto",
1189
- zIndex: 99999
1190
- },
1191
- children: [
1192
- /* @__PURE__ */ jsxRuntime.jsx(
1193
- ToolbarButton,
1194
- {
1195
- onClick: () => editor.setCurrentTool("select"),
1196
- isSelected: isSelectSelected,
1197
- icon: /* @__PURE__ */ jsxRuntime.jsx(SelectIcon, { isSelected: isSelectSelected }),
1198
- label: "Select (V)"
1199
- }
1200
- ),
1201
- /* @__PURE__ */ jsxRuntime.jsx(
1202
- ToolbarButton,
1203
- {
1204
- onClick: () => editor.setCurrentTool("lasso-select"),
1205
- isSelected: isLassoSelected,
1206
- icon: /* @__PURE__ */ jsxRuntime.jsx(LassoIcon, { isSelected: isLassoSelected }),
1207
- label: "Lasso Select (L)"
1208
- }
1209
- ),
1210
- /* @__PURE__ */ jsxRuntime.jsx(
1211
- ToolbarButton,
1212
- {
1213
- onClick: () => editor.setCurrentTool("draw"),
1214
- isSelected: isDrawSelected,
1215
- icon: /* @__PURE__ */ jsxRuntime.jsx(DrawIcon, { isSelected: isDrawSelected }),
1216
- label: "Draw (D)"
1217
- }
1218
- ),
1219
- /* @__PURE__ */ jsxRuntime.jsx(
1220
- ToolbarButton,
1221
- {
1222
- onClick: () => editor.setCurrentTool("eraser"),
1223
- isSelected: isEraseSelected,
1224
- icon: /* @__PURE__ */ jsxRuntime.jsx(EraseIcon, { isSelected: isEraseSelected }),
1225
- label: "Eraser (E)"
1226
- }
1227
- ),
1228
- /* @__PURE__ */ jsxRuntime.jsx(
1229
- "div",
1230
- {
1231
- style: {
1232
- width: 3,
1233
- height: 36,
1234
- backgroundColor: "#C4C2C2",
1235
- borderRadius: 1.5,
1236
- margin: "0 6px"
1237
- }
1238
- }
1239
- ),
1240
- /* @__PURE__ */ jsxRuntime.jsx(
1241
- ToolbarButton,
1242
- {
1243
- onClick: () => {
1244
- console.log("Star button clicked - placeholder for future feature");
1245
- },
1246
- isSelected: isStarSelected,
1247
- icon: /* @__PURE__ */ jsxRuntime.jsx(StarIcon, { isSelected: isStarSelected }),
1248
- label: "Special (Coming Soon)"
1249
- }
1250
- )
1251
- ]
1252
- }
1253
- );
1254
- };
1255
- var LassoOverlay = () => {
1256
- const editor = tldraw.useEditor();
1257
- const lassoPoints = tldraw.useValue(
1258
- "lasso points",
1259
- () => {
1260
- if (!editor.isIn("lasso-select.lassoing")) return [];
1261
- const lassoing = editor.getStateDescendant("lasso-select.lassoing");
1262
- return lassoing?.points?.get() ?? [];
1263
- },
1264
- [editor]
1265
- );
1266
- const svgPath = react.useMemo(() => {
1267
- if (lassoPoints.length < 2) return "";
1268
- let path = `M ${lassoPoints[0].x} ${lassoPoints[0].y}`;
1269
- for (let i = 1; i < lassoPoints.length; i++) {
1270
- path += ` L ${lassoPoints[i].x} ${lassoPoints[i].y}`;
1271
- }
1272
- path += " Z";
1273
- return path;
1274
- }, [lassoPoints]);
1275
- if (lassoPoints.length === 0) return null;
1276
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "tl-overlays__item", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
1277
- "path",
1278
- {
1279
- d: svgPath,
1280
- fill: "none",
1281
- stroke: "rgba(59, 130, 246, 1)",
1282
- strokeWidth: "calc(2px / var(--tl-zoom))",
1283
- strokeLinecap: "round",
1284
- strokeLinejoin: "round",
1285
- strokeDasharray: "4 4"
1286
- }
1287
- ) });
1288
- };
1289
- var SkemaOverlays = () => {
1290
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1291
- /* @__PURE__ */ jsxRuntime.jsx(tldraw.TldrawOverlays, {}),
1292
- /* @__PURE__ */ jsxRuntime.jsx(LassoOverlay, {})
1293
- ] });
1294
- };
1295
- var SelectionOverlay = ({ selections }) => {
1296
- const [scrollPos, setScrollPos] = react.useState({ x: 0, y: 0 });
1297
- react.useEffect(() => {
1298
- const handleScroll = () => {
1299
- setScrollPos({ x: window.scrollX, y: window.scrollY });
1300
- };
1301
- handleScroll();
1302
- window.addEventListener("scroll", handleScroll, { passive: true });
1303
- return () => window.removeEventListener("scroll", handleScroll);
1304
- }, []);
1305
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: selections.map((selection) => {
1306
- const viewportX = selection.boundingBox.x - scrollPos.x;
1307
- const viewportY = selection.boundingBox.y - scrollPos.y;
1308
- return /* @__PURE__ */ jsxRuntime.jsx(
1309
- "div",
1310
- {
1311
- "data-skema": "selection",
1312
- style: {
1313
- position: "fixed",
1314
- left: viewportX,
1315
- top: viewportY,
1316
- width: selection.boundingBox.width,
1317
- height: selection.boundingBox.height,
1318
- border: "2px solid #10b981",
1319
- backgroundColor: "rgba(16, 185, 129, 0.1)",
1320
- pointerEvents: "none",
1321
- zIndex: 999997
1322
- },
1323
- children: /* @__PURE__ */ jsxRuntime.jsx(
1324
- "span",
1325
- {
1326
- style: {
1327
- position: "absolute",
1328
- top: -20,
1329
- left: 0,
1330
- backgroundColor: "#10b981",
1331
- color: "white",
1332
- padding: "2px 6px",
1333
- fontSize: "11px",
1334
- borderRadius: "3px",
1335
- whiteSpace: "nowrap"
1336
- },
1337
- children: selection.tagName
1338
- }
1339
- )
1340
- },
1341
- selection.id
1342
- );
1343
- }) });
1344
- };
1345
- var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1346
- const [isOpen, setIsOpen] = react.useState(false);
1347
- return /* @__PURE__ */ jsxRuntime.jsxs(
1348
- "div",
1709
+ var AnnotationsSidebar = ({
1710
+ annotations,
1711
+ onClear,
1712
+ onExport
1713
+ }) => {
1714
+ const [isOpen, setIsOpen] = react.useState(false);
1715
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1716
+ "div",
1349
1717
  {
1350
1718
  "data-skema": "sidebar",
1351
1719
  style: {
@@ -1447,9 +1815,9 @@ var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1447
1815
  },
1448
1816
  children: [
1449
1817
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontWeight: 500, marginBottom: "4px" }, children: [
1450
- annotation.type === "dom_selection" && `\u{1F3AF} ${annotation.tagName}`,
1451
- annotation.type === "drawing" && `\u270F\uFE0F ${annotation.comment || "Drawing"}`,
1452
- annotation.type === "gesture" && `\u{1F446} ${annotation.gesture}`
1818
+ annotation.type === "dom_selection" && `[DOM] ${annotation.tagName}`,
1819
+ annotation.type === "drawing" && `[Draw] ${annotation.comment || "Drawing"}`,
1820
+ annotation.type === "gesture" && `[Gesture] ${annotation.gesture}`
1453
1821
  ] }),
1454
1822
  annotation.type === "dom_selection" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1455
1823
  annotation.comment && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#374151", fontSize: "12px", marginBottom: "4px" }, children: annotation.comment }),
@@ -1463,37 +1831,556 @@ var AnnotationsSidebar = ({ annotations, onClear, onExport }) => {
1463
1831
  }
1464
1832
  );
1465
1833
  };
1466
- var Skema = ({
1467
- enabled = true,
1468
- onAnnotationsChange,
1469
- onAnnotationSubmit,
1470
- onAnnotationDelete,
1471
- toggleShortcut = "mod+shift+e",
1472
- initialAnnotations = [],
1473
- zIndex = 99999
1474
- }) => {
1475
- const [isActive, setIsActive] = react.useState(enabled);
1476
- const [annotations, setAnnotations] = react.useState(initialAnnotations);
1477
- const [domSelections, setDomSelections] = react.useState([]);
1478
- const [pendingAnnotation, setPendingAnnotation] = react.useState(null);
1479
- const [pendingExiting, setPendingExiting] = react.useState(false);
1480
- const [hoveredMarkerId, setHoveredMarkerId] = react.useState(null);
1481
- const editorRef = react.useRef(null);
1482
- const popupRef = react.useRef(null);
1483
- const lastDoubleClickRef = react.useRef(0);
1484
- react.useRef(false);
1485
- const cleanupRef = react.useRef(null);
1834
+ var SelectionOverlay = ({ selections }) => {
1835
+ const [scrollPos, setScrollPos] = react.useState({ x: 0, y: 0 });
1836
+ react.useEffect(() => {
1837
+ const handleScrollOrResize = () => {
1838
+ setScrollPos({ x: window.scrollX, y: window.scrollY });
1839
+ };
1840
+ handleScrollOrResize();
1841
+ window.addEventListener("scroll", handleScrollOrResize, { passive: true });
1842
+ window.addEventListener("resize", handleScrollOrResize);
1843
+ return () => {
1844
+ window.removeEventListener("scroll", handleScrollOrResize);
1845
+ window.removeEventListener("resize", handleScrollOrResize);
1846
+ };
1847
+ }, []);
1848
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: selections.map((selection) => {
1849
+ const viewportX = selection.boundingBox.x - scrollPos.x;
1850
+ const viewportY = selection.boundingBox.y - scrollPos.y;
1851
+ return /* @__PURE__ */ jsxRuntime.jsx(
1852
+ "div",
1853
+ {
1854
+ "data-skema": "selection",
1855
+ style: {
1856
+ position: "fixed",
1857
+ left: viewportX,
1858
+ top: viewportY,
1859
+ width: selection.boundingBox.width,
1860
+ height: selection.boundingBox.height,
1861
+ border: "2px solid #10b981",
1862
+ backgroundColor: "rgba(16, 185, 129, 0.1)",
1863
+ pointerEvents: "none",
1864
+ zIndex: 999997
1865
+ },
1866
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1867
+ "span",
1868
+ {
1869
+ style: {
1870
+ position: "absolute",
1871
+ top: -20,
1872
+ left: 0,
1873
+ backgroundColor: "#10b981",
1874
+ color: "white",
1875
+ padding: "2px 6px",
1876
+ fontSize: "11px",
1877
+ borderRadius: "3px",
1878
+ whiteSpace: "nowrap"
1879
+ },
1880
+ children: selection.tagName
1881
+ }
1882
+ )
1883
+ },
1884
+ selection.id
1885
+ );
1886
+ }) });
1887
+ };
1888
+ var ShapeLoader = () => {
1889
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1890
+ "div",
1891
+ {
1892
+ style: {
1893
+ position: "relative",
1894
+ width: 28,
1895
+ height: 28,
1896
+ display: "flex",
1897
+ alignItems: "center",
1898
+ justifyContent: "center"
1899
+ },
1900
+ children: [
1901
+ /* @__PURE__ */ jsxRuntime.jsx(
1902
+ "svg",
1903
+ {
1904
+ className: "skema-shape skema-shape-1",
1905
+ viewBox: "0 0 24 24",
1906
+ style: {
1907
+ position: "absolute",
1908
+ width: 24,
1909
+ height: 24
1910
+ },
1911
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1912
+ "polygon",
1913
+ {
1914
+ points: "12,2 15,9 22,9 16,14 18,22 12,17 6,22 8,14 2,9 9,9",
1915
+ fill: "#F97316"
1916
+ }
1917
+ )
1918
+ }
1919
+ ),
1920
+ /* @__PURE__ */ jsxRuntime.jsx(
1921
+ "svg",
1922
+ {
1923
+ className: "skema-shape skema-shape-2",
1924
+ viewBox: "0 0 24 24",
1925
+ style: {
1926
+ position: "absolute",
1927
+ width: 24,
1928
+ height: 24
1929
+ },
1930
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1931
+ "polygon",
1932
+ {
1933
+ points: "6,4 22,4 18,20 2,20",
1934
+ fill: "#FACC15"
1935
+ }
1936
+ )
1937
+ }
1938
+ ),
1939
+ /* @__PURE__ */ jsxRuntime.jsx(
1940
+ "svg",
1941
+ {
1942
+ className: "skema-shape skema-shape-3",
1943
+ viewBox: "0 0 24 24",
1944
+ style: {
1945
+ position: "absolute",
1946
+ width: 24,
1947
+ height: 24
1948
+ },
1949
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1950
+ "polygon",
1951
+ {
1952
+ points: "12,3 22,21 2,21",
1953
+ fill: "#EF4444"
1954
+ }
1955
+ )
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ jsxRuntime.jsx(
1959
+ "svg",
1960
+ {
1961
+ className: "skema-shape skema-shape-4",
1962
+ viewBox: "0 0 24 24",
1963
+ style: {
1964
+ position: "absolute",
1965
+ width: 24,
1966
+ height: 24
1967
+ },
1968
+ children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", fill: "#3B82F6" })
1969
+ }
1970
+ ),
1971
+ /* @__PURE__ */ jsxRuntime.jsx(
1972
+ "svg",
1973
+ {
1974
+ className: "skema-shape skema-shape-5",
1975
+ viewBox: "0 0 24 24",
1976
+ style: {
1977
+ position: "absolute",
1978
+ width: 24,
1979
+ height: 24
1980
+ },
1981
+ children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", fill: "#22C55E" })
1982
+ }
1983
+ )
1984
+ ]
1985
+ }
1986
+ );
1987
+ };
1988
+ var ProcessingOverlayStyles = `
1989
+ @keyframes skema-processing-pulse {
1990
+ 0%, 100% {
1991
+ opacity: 0.7;
1992
+ transform: scale(1);
1993
+ }
1994
+ 50% {
1995
+ opacity: 0.95;
1996
+ transform: scale(1.02);
1997
+ }
1998
+ }
1999
+ @keyframes skema-processing-shimmer {
2000
+ 0% {
2001
+ background-position: -200% 0;
2002
+ }
2003
+ 100% {
2004
+ background-position: 200% 0;
2005
+ }
2006
+ }
2007
+ @keyframes skema-processing-border {
2008
+ 0%, 100% {
2009
+ border-color: rgba(139, 92, 246, 0.85);
2010
+ }
2011
+ 50% {
2012
+ border-color: rgba(139, 92, 246, 1);
2013
+ }
2014
+ }
2015
+
2016
+ /* Shape loader animations */
2017
+ .skema-shape {
2018
+ opacity: 0;
2019
+ transform: scale(0.5) rotate(-180deg);
2020
+ animation: skema-shape-cycle 2.5s ease-in-out infinite;
2021
+ }
2022
+ .skema-shape-1 { animation-delay: 0s; }
2023
+ .skema-shape-2 { animation-delay: 0.5s; }
2024
+ .skema-shape-3 { animation-delay: 1s; }
2025
+ .skema-shape-4 { animation-delay: 1.5s; }
2026
+ .skema-shape-5 { animation-delay: 2s; }
2027
+
2028
+ @keyframes skema-shape-cycle {
2029
+ 0%, 100% {
2030
+ opacity: 0;
2031
+ transform: scale(0.5) rotate(-180deg);
2032
+ }
2033
+ 10%, 30% {
2034
+ opacity: 1;
2035
+ transform: scale(1) rotate(0deg);
2036
+ }
2037
+ 40% {
2038
+ opacity: 0;
2039
+ transform: scale(0.5) rotate(180deg);
2040
+ }
2041
+ }
2042
+ `;
2043
+ var ProcessingOverlay = ({ boundingBox, scrollOffset }) => {
2044
+ const viewportX = boundingBox.x - scrollOffset.x;
2045
+ const viewportY = boundingBox.y - scrollOffset.y;
2046
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2047
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: ProcessingOverlayStyles }),
2048
+ /* @__PURE__ */ jsxRuntime.jsxs(
2049
+ "div",
2050
+ {
2051
+ "data-skema": "processing-overlay",
2052
+ style: {
2053
+ position: "fixed",
2054
+ left: viewportX,
2055
+ top: viewportY,
2056
+ width: boundingBox.width,
2057
+ height: boundingBox.height,
2058
+ border: "3px solid rgba(139, 92, 246, 0.95)",
2059
+ borderRadius: 4,
2060
+ pointerEvents: "none",
2061
+ zIndex: 999998,
2062
+ animation: "skema-processing-pulse 1.5s ease-in-out infinite, skema-processing-border 1.5s ease-in-out infinite",
2063
+ 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%)",
2064
+ backgroundSize: "200% 100%"
2065
+ },
2066
+ children: [
2067
+ /* @__PURE__ */ jsxRuntime.jsx(
2068
+ "div",
2069
+ {
2070
+ style: {
2071
+ position: "absolute",
2072
+ inset: 0,
2073
+ background: "linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.25) 50%, transparent 100%)",
2074
+ backgroundSize: "200% 100%",
2075
+ animation: "skema-processing-shimmer 2s linear infinite",
2076
+ borderRadius: 2
2077
+ }
2078
+ }
2079
+ ),
2080
+ /* @__PURE__ */ jsxRuntime.jsx(
2081
+ "div",
2082
+ {
2083
+ style: {
2084
+ position: "absolute",
2085
+ top: -18,
2086
+ left: "50%",
2087
+ transform: "translateX(-50%)",
2088
+ display: "flex",
2089
+ alignItems: "center",
2090
+ justifyContent: "center",
2091
+ padding: "8px 14px",
2092
+ backgroundColor: "#FFFFFF",
2093
+ borderRadius: 20,
2094
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08)"
2095
+ },
2096
+ children: /* @__PURE__ */ jsxRuntime.jsx(ShapeLoader, {})
2097
+ }
2098
+ )
2099
+ ]
2100
+ }
2101
+ )
2102
+ ] });
2103
+ };
2104
+ var styles = {
2105
+ popup: {
2106
+ position: "fixed",
2107
+ transform: "translateX(-50%)",
2108
+ width: 280,
2109
+ padding: "12px 16px 14px",
2110
+ background: "#1a1a1a",
2111
+ borderRadius: 16,
2112
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08)",
2113
+ cursor: "default",
2114
+ zIndex: 100001,
2115
+ fontFamily: '"Clash Display", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
2116
+ opacity: 0,
2117
+ transition: "opacity 0.2s ease, transform 0.2s ease"
2118
+ },
2119
+ popupEnter: {
2120
+ opacity: 1,
2121
+ transform: "translateX(-50%) scale(1) translateY(0)"
2122
+ },
2123
+ popupExit: {
2124
+ opacity: 0,
2125
+ transform: "translateX(-50%) scale(0.95) translateY(4px)"
2126
+ },
2127
+ header: {
2128
+ display: "flex",
2129
+ alignItems: "center",
2130
+ justifyContent: "space-between",
2131
+ marginBottom: 9
2132
+ },
2133
+ element: {
2134
+ fontSize: 12,
2135
+ fontWeight: 400,
2136
+ color: "rgba(255, 255, 255, 0.5)",
2137
+ maxWidth: "100%",
2138
+ overflow: "hidden",
2139
+ textOverflow: "ellipsis",
2140
+ whiteSpace: "nowrap",
2141
+ flex: 1
2142
+ },
2143
+ quote: {
2144
+ fontSize: 12,
2145
+ fontStyle: "italic",
2146
+ color: "rgba(255, 255, 255, 0.6)",
2147
+ marginBottom: 8,
2148
+ padding: "6px 8px",
2149
+ background: "rgba(255, 255, 255, 0.05)",
2150
+ borderRadius: 4,
2151
+ lineHeight: 1.45
2152
+ },
2153
+ textarea: {
2154
+ width: "100%",
2155
+ padding: "8px 10px",
2156
+ fontSize: 13,
2157
+ fontFamily: "inherit",
2158
+ background: "rgba(255, 255, 255, 0.05)",
2159
+ color: "#fff",
2160
+ border: "1px solid rgba(255, 255, 255, 0.15)",
2161
+ borderRadius: 8,
2162
+ resize: "none",
2163
+ outline: "none",
2164
+ transition: "border-color 0.15s ease",
2165
+ boxSizing: "border-box"
2166
+ },
2167
+ actions: {
2168
+ display: "flex",
2169
+ justifyContent: "flex-end",
2170
+ gap: 6,
2171
+ marginTop: 8
2172
+ },
2173
+ button: {
2174
+ padding: "6px 14px",
2175
+ fontSize: 12,
2176
+ fontWeight: 500,
2177
+ borderRadius: 16,
2178
+ border: "none",
2179
+ cursor: "pointer",
2180
+ transition: "background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease"
2181
+ },
2182
+ cancelButton: {
2183
+ background: "transparent",
2184
+ color: "rgba(255, 255, 255, 0.5)"
2185
+ },
2186
+ submitButton: {
2187
+ color: "white"
2188
+ }
2189
+ };
2190
+ var AnnotationPopup = react.forwardRef(
2191
+ function AnnotationPopup2({
2192
+ element,
2193
+ selectedText,
2194
+ placeholder = "What should change?",
2195
+ initialValue = "",
2196
+ submitLabel = "Add",
2197
+ onSubmit,
2198
+ onCancel,
2199
+ style,
2200
+ accentColor = "#3c82f7",
2201
+ isExiting = false,
2202
+ isMultiSelect = false
2203
+ }, ref) {
2204
+ const [text, setText] = react.useState(initialValue);
2205
+ const [isShaking, setIsShaking] = react.useState(false);
2206
+ const [animState, setAnimState] = react.useState("initial");
2207
+ const [isFocused, setIsFocused] = react.useState(false);
2208
+ const textareaRef = react.useRef(null);
2209
+ const popupRef = react.useRef(null);
2210
+ react.useEffect(() => {
2211
+ if (isExiting && animState !== "exit") {
2212
+ setAnimState("exit");
2213
+ }
2214
+ }, [isExiting, animState]);
2215
+ react.useEffect(() => {
2216
+ requestAnimationFrame(() => {
2217
+ setAnimState("enter");
2218
+ });
2219
+ const enterTimer = setTimeout(() => {
2220
+ setAnimState("entered");
2221
+ }, 200);
2222
+ const focusTimer = setTimeout(() => {
2223
+ const textarea = textareaRef.current;
2224
+ if (textarea) {
2225
+ textarea.focus();
2226
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
2227
+ textarea.scrollTop = textarea.scrollHeight;
2228
+ }
2229
+ }, 50);
2230
+ return () => {
2231
+ clearTimeout(enterTimer);
2232
+ clearTimeout(focusTimer);
2233
+ };
2234
+ }, []);
2235
+ const shake = react.useCallback(() => {
2236
+ setIsShaking(true);
2237
+ setTimeout(() => {
2238
+ setIsShaking(false);
2239
+ textareaRef.current?.focus();
2240
+ }, 250);
2241
+ }, []);
2242
+ react.useImperativeHandle(ref, () => ({
2243
+ shake
2244
+ }), [shake]);
2245
+ const handleCancel = react.useCallback(() => {
2246
+ setAnimState("exit");
2247
+ setTimeout(() => {
2248
+ onCancel();
2249
+ }, 150);
2250
+ }, [onCancel]);
2251
+ const handleSubmit = react.useCallback(() => {
2252
+ if (!text.trim()) return;
2253
+ onSubmit(text.trim());
2254
+ }, [text, onSubmit]);
2255
+ const handleKeyDown = react.useCallback(
2256
+ (e) => {
2257
+ if (e.nativeEvent.isComposing) return;
2258
+ if (e.key === "Enter" && !e.shiftKey) {
2259
+ e.preventDefault();
2260
+ handleSubmit();
2261
+ }
2262
+ if (e.key === "Escape") {
2263
+ handleCancel();
2264
+ }
2265
+ },
2266
+ [handleSubmit, handleCancel]
2267
+ );
2268
+ const popupStyle = {
2269
+ ...styles.popup,
2270
+ ...animState === "enter" || animState === "entered" ? styles.popupEnter : {},
2271
+ ...animState === "exit" ? styles.popupExit : {},
2272
+ ...isShaking ? {
2273
+ animation: "skema-shake 0.25s ease-out"
2274
+ } : {},
2275
+ ...style
2276
+ };
2277
+ const textareaStyle = {
2278
+ ...styles.textarea,
2279
+ ...isFocused ? { borderColor: accentColor } : {}
2280
+ };
2281
+ const effectiveAccentColor = isMultiSelect ? "#34C759" : accentColor;
2282
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2283
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
2284
+ @keyframes skema-shake {
2285
+ 0%, 100% { transform: translateX(-50%) scale(1) translateY(0) translateX(0); }
2286
+ 20% { transform: translateX(-50%) scale(1) translateY(0) translateX(-3px); }
2287
+ 40% { transform: translateX(-50%) scale(1) translateY(0) translateX(3px); }
2288
+ 60% { transform: translateX(-50%) scale(1) translateY(0) translateX(-2px); }
2289
+ 80% { transform: translateX(-50%) scale(1) translateY(0) translateX(2px); }
2290
+ }
2291
+ ` }),
2292
+ /* @__PURE__ */ jsxRuntime.jsxs(
2293
+ "div",
2294
+ {
2295
+ ref: popupRef,
2296
+ "data-skema": "annotation-popup",
2297
+ style: popupStyle,
2298
+ onClick: (e) => e.stopPropagation(),
2299
+ onPointerDown: (e) => e.stopPropagation(),
2300
+ children: [
2301
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.header, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.element, children: element }) }),
2302
+ selectedText && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.quote, children: [
2303
+ "\u201C",
2304
+ selectedText.slice(0, 80),
2305
+ selectedText.length > 80 ? "..." : "",
2306
+ "\u201D"
2307
+ ] }),
2308
+ /* @__PURE__ */ jsxRuntime.jsx(
2309
+ "textarea",
2310
+ {
2311
+ ref: textareaRef,
2312
+ style: textareaStyle,
2313
+ placeholder,
2314
+ value: text,
2315
+ onChange: (e) => setText(e.target.value),
2316
+ onFocus: () => setIsFocused(true),
2317
+ onBlur: () => setIsFocused(false),
2318
+ rows: 2,
2319
+ onKeyDown: handleKeyDown
2320
+ }
2321
+ ),
2322
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
2323
+ /* @__PURE__ */ jsxRuntime.jsx(
2324
+ "button",
2325
+ {
2326
+ style: { ...styles.button, ...styles.cancelButton },
2327
+ onClick: handleCancel,
2328
+ onMouseEnter: (e) => {
2329
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
2330
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.8)";
2331
+ },
2332
+ onMouseLeave: (e) => {
2333
+ e.currentTarget.style.background = "transparent";
2334
+ e.currentTarget.style.color = "rgba(255, 255, 255, 0.5)";
2335
+ },
2336
+ children: "Cancel"
2337
+ }
2338
+ ),
2339
+ /* @__PURE__ */ jsxRuntime.jsx(
2340
+ "button",
2341
+ {
2342
+ style: {
2343
+ ...styles.button,
2344
+ ...styles.submitButton,
2345
+ backgroundColor: effectiveAccentColor,
2346
+ opacity: text.trim() ? 1 : 0.4
2347
+ },
2348
+ onClick: handleSubmit,
2349
+ disabled: !text.trim(),
2350
+ onMouseEnter: (e) => {
2351
+ if (text.trim()) {
2352
+ e.currentTarget.style.filter = "brightness(0.9)";
2353
+ }
2354
+ },
2355
+ onMouseLeave: (e) => {
2356
+ e.currentTarget.style.filter = "none";
2357
+ },
2358
+ children: submitLabel
2359
+ }
2360
+ )
2361
+ ] })
2362
+ ]
2363
+ }
2364
+ )
2365
+ ] });
2366
+ }
2367
+ );
2368
+ function useKeyboardShortcuts({ onToggle, shortcut = "mod+shift+e" }) {
1486
2369
  react.useEffect(() => {
1487
2370
  const handleKeyDown = (e) => {
1488
2371
  const isMod = e.metaKey || e.ctrlKey;
1489
- if (isMod && e.shiftKey && e.key.toLowerCase() === "e") {
1490
- e.preventDefault();
1491
- setIsActive((prev) => !prev);
2372
+ if (shortcut === "mod+shift+e") {
2373
+ if (isMod && e.shiftKey && e.key.toLowerCase() === "e") {
2374
+ e.preventDefault();
2375
+ onToggle();
2376
+ }
1492
2377
  }
1493
2378
  };
1494
2379
  window.addEventListener("keydown", handleKeyDown);
1495
2380
  return () => window.removeEventListener("keydown", handleKeyDown);
1496
- }, []);
2381
+ }, [onToggle, shortcut]);
2382
+ }
2383
+ function useScrollSync(isActive, editorRef) {
1497
2384
  const [scrollOffset, setScrollOffset] = react.useState({ x: 0, y: 0 });
1498
2385
  react.useEffect(() => {
1499
2386
  if (!isActive) return;
@@ -1506,10 +2393,15 @@ var Skema = ({
1506
2393
  };
1507
2394
  syncScroll();
1508
2395
  window.addEventListener("scroll", syncScroll, { passive: true });
2396
+ window.addEventListener("resize", syncScroll);
1509
2397
  return () => {
1510
2398
  window.removeEventListener("scroll", syncScroll);
2399
+ window.removeEventListener("resize", syncScroll);
1511
2400
  };
1512
- }, [isActive]);
2401
+ }, [isActive, editorRef]);
2402
+ return scrollOffset;
2403
+ }
2404
+ function useWheelIntercept(isActive) {
1513
2405
  react.useEffect(() => {
1514
2406
  if (!isActive) return;
1515
2407
  const handleWheel = (e) => {
@@ -1528,9 +2420,586 @@ var Skema = ({
1528
2420
  document.removeEventListener("wheel", handleWheel, { capture: true });
1529
2421
  };
1530
2422
  }, [isActive]);
2423
+ }
2424
+ function useShapePersistence(isActive, editorRef) {
2425
+ const savedShapesRef = react.useRef(null);
2426
+ const wasActiveRef = react.useRef(isActive);
2427
+ react.useEffect(() => {
2428
+ const editor = editorRef.current;
2429
+ if (wasActiveRef.current && !isActive && editor) {
2430
+ const allShapes = editor.getCurrentPageShapes();
2431
+ const drawingShapes = allShapes.filter(
2432
+ (shape) => ["draw", "line", "arrow", "geo", "text", "note", "frame"].includes(shape.type)
2433
+ );
2434
+ if (drawingShapes.length > 0) {
2435
+ const shapeRecords = {};
2436
+ for (const shape of drawingShapes) {
2437
+ shapeRecords[shape.id] = shape;
2438
+ }
2439
+ savedShapesRef.current = shapeRecords;
2440
+ const shapeIds = drawingShapes.map((s) => s.id);
2441
+ editor.deleteShapes(shapeIds);
2442
+ console.log(`[Skema] Hiding: saved ${drawingShapes.length} shape(s) to memory`);
2443
+ }
2444
+ }
2445
+ wasActiveRef.current = isActive;
2446
+ }, [isActive, editorRef]);
2447
+ react.useEffect(() => {
2448
+ if (!isActive) return;
2449
+ const editor = editorRef.current;
2450
+ if (!editor || !savedShapesRef.current) return;
2451
+ const timeoutId = setTimeout(() => {
2452
+ const savedShapes = savedShapesRef.current;
2453
+ const currentEditor = editorRef.current;
2454
+ if (!savedShapes || !currentEditor) return;
2455
+ const shapesToRestore = Object.values(savedShapes);
2456
+ if (shapesToRestore.length > 0) {
2457
+ currentEditor.createShapes(shapesToRestore);
2458
+ console.log(`[Skema] Restoring: loaded ${shapesToRestore.length} shape(s) from memory`);
2459
+ savedShapesRef.current = null;
2460
+ }
2461
+ }, 100);
2462
+ return () => clearTimeout(timeoutId);
2463
+ }, [isActive, editorRef]);
2464
+ }
2465
+
2466
+ // src/utils/gesture-recognizer.ts
2467
+ function angleBetweenVectors(v1, v2) {
2468
+ const dot = v1.x * v2.x + v1.y * v2.y;
2469
+ const mag1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
2470
+ const mag2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
2471
+ if (mag1 === 0 || mag2 === 0) return 0;
2472
+ const cosAngle = Math.max(-1, Math.min(1, dot / (mag1 * mag2)));
2473
+ return Math.acos(cosAngle);
2474
+ }
2475
+ function calculatePathLength(points) {
2476
+ let length = 0;
2477
+ for (let i = 1; i < points.length; i++) {
2478
+ const dx = points[i].x - points[i - 1].x;
2479
+ const dy = points[i].y - points[i - 1].y;
2480
+ length += Math.sqrt(dx * dx + dy * dy);
2481
+ }
2482
+ return length;
2483
+ }
2484
+ function calculateBoundingBox(points) {
2485
+ if (points.length === 0) {
2486
+ return { width: 0, height: 0, diagonal: 0 };
2487
+ }
2488
+ let minX = Infinity, maxX = -Infinity;
2489
+ let minY = Infinity, maxY = -Infinity;
2490
+ for (const point of points) {
2491
+ minX = Math.min(minX, point.x);
2492
+ maxX = Math.max(maxX, point.x);
2493
+ minY = Math.min(minY, point.y);
2494
+ maxY = Math.max(maxY, point.y);
2495
+ }
2496
+ const width = maxX - minX;
2497
+ const height = maxY - minY;
2498
+ const diagonal = Math.sqrt(width * width + height * height);
2499
+ return { width, height, diagonal };
2500
+ }
2501
+ function getPointsBounds(points) {
2502
+ if (points.length === 0) {
2503
+ return null;
2504
+ }
2505
+ let minX = Infinity, maxX = -Infinity;
2506
+ let minY = Infinity, maxY = -Infinity;
2507
+ for (const point of points) {
2508
+ minX = Math.min(minX, point.x);
2509
+ maxX = Math.max(maxX, point.x);
2510
+ minY = Math.min(minY, point.y);
2511
+ maxY = Math.max(maxY, point.y);
2512
+ }
2513
+ return {
2514
+ minX,
2515
+ minY,
2516
+ maxX,
2517
+ maxY,
2518
+ width: maxX - minX,
2519
+ height: maxY - minY
2520
+ };
2521
+ }
2522
+ function countDirectionChanges(points, angleThreshold = Math.PI / 2) {
2523
+ if (points.length < 3) return 0;
2524
+ let changes = 0;
2525
+ const sampleRate = Math.max(1, Math.floor(points.length / 50));
2526
+ const sampledPoints = [];
2527
+ for (let i = 0; i < points.length; i += sampleRate) {
2528
+ sampledPoints.push(points[i]);
2529
+ }
2530
+ if (sampledPoints[sampledPoints.length - 1] !== points[points.length - 1]) {
2531
+ sampledPoints.push(points[points.length - 1]);
2532
+ }
2533
+ for (let i = 1; i < sampledPoints.length - 1; i++) {
2534
+ const v1 = {
2535
+ x: sampledPoints[i].x - sampledPoints[i - 1].x,
2536
+ y: sampledPoints[i].y - sampledPoints[i - 1].y
2537
+ };
2538
+ const v2 = {
2539
+ x: sampledPoints[i + 1].x - sampledPoints[i].x,
2540
+ y: sampledPoints[i + 1].y - sampledPoints[i].y
2541
+ };
2542
+ const angle = angleBetweenVectors(v1, v2);
2543
+ if (angle > angleThreshold) {
2544
+ changes++;
2545
+ }
2546
+ }
2547
+ return changes;
2548
+ }
2549
+ function isRealtimeScribble(points) {
2550
+ if (points.length < 15) {
2551
+ return {
2552
+ isScribble: false,
2553
+ confidence: 0,
2554
+ directionChanges: 0,
2555
+ compactness: 0
2556
+ };
2557
+ }
2558
+ const directionChanges = countDirectionChanges(points, Math.PI / 3);
2559
+ const pathLength = calculatePathLength(points);
2560
+ const { diagonal } = calculateBoundingBox(points);
2561
+ const compactness = diagonal > 0 ? pathLength / diagonal : 0;
2562
+ const minDirectionChanges = 5;
2563
+ const minCompactness = 1.8;
2564
+ const minPathLength = 80;
2565
+ const meetsDirectionCriteria = directionChanges >= minDirectionChanges;
2566
+ const meetsCompactnessCriteria = compactness >= minCompactness;
2567
+ const meetsLengthCriteria = pathLength >= minPathLength;
2568
+ const isScribble = meetsDirectionCriteria && meetsCompactnessCriteria && meetsLengthCriteria;
2569
+ let confidence = 0;
2570
+ if (isScribble) {
2571
+ const directionScore = Math.min(1, (directionChanges - minDirectionChanges) / 4);
2572
+ const compactnessScore = Math.min(1, (compactness - minCompactness) / 2);
2573
+ confidence = (directionScore + compactnessScore) / 2;
2574
+ }
2575
+ return {
2576
+ isScribble,
2577
+ confidence,
2578
+ directionChanges,
2579
+ compactness
2580
+ };
2581
+ }
2582
+ function findOverlappingShapesFromBounds(editor, bounds, excludeIds = []) {
2583
+ const allShapes = editor.getCurrentPageShapes();
2584
+ const overlapping = [];
2585
+ for (const shape of allShapes) {
2586
+ if (excludeIds.includes(shape.id)) continue;
2587
+ if (!["draw", "line", "arrow", "geo", "text", "note", "frame"].includes(shape.type)) {
2588
+ continue;
2589
+ }
2590
+ const shapeBounds = editor.getShapePageBounds(shape.id);
2591
+ if (shapeBounds) {
2592
+ const intersects = !(bounds.maxX < shapeBounds.x || bounds.minX > shapeBounds.x + shapeBounds.w || bounds.maxY < shapeBounds.y || bounds.minY > shapeBounds.y + shapeBounds.h);
2593
+ if (intersects) {
2594
+ overlapping.push(shape.id);
2595
+ }
2596
+ }
2597
+ }
2598
+ return overlapping;
2599
+ }
2600
+
2601
+ // src/hooks/useScribbleDelete.ts
2602
+ function useScribbleDelete({
2603
+ isActive,
2604
+ editorRef,
2605
+ setAnnotations,
2606
+ setScribbleToast
2607
+ }) {
2608
+ const scribblePointsRef = react.useRef([]);
2609
+ const isDrawingRef = react.useRef(false);
2610
+ const scribbleDetectedRef = react.useRef(false);
2611
+ react.useEffect(() => {
2612
+ if (!isActive) return;
2613
+ const handleScribbleDelete = (overlappingIds) => {
2614
+ const editor = editorRef.current;
2615
+ if (!editor || overlappingIds.length === 0) return;
2616
+ editor.cancel();
2617
+ editor.deleteShapes(overlappingIds);
2618
+ setAnnotations((prev) => prev.filter((annotation) => {
2619
+ if (annotation.type === "drawing") {
2620
+ const drawingShapes = annotation.shapes;
2621
+ return !drawingShapes.some(
2622
+ (shapeId) => overlappingIds.includes(shapeId)
2623
+ );
2624
+ }
2625
+ return true;
2626
+ }));
2627
+ const message = overlappingIds.length === 1 ? "Deleted 1 shape" : `Deleted ${overlappingIds.length} shapes`;
2628
+ setScribbleToast(message);
2629
+ setTimeout(() => setScribbleToast(null), 2e3);
2630
+ console.log(`[Skema] Scribble-delete: removed ${overlappingIds.length} shape(s)`);
2631
+ };
2632
+ const handlePointerDown = (e) => {
2633
+ const editor = editorRef.current;
2634
+ if (!editor) return;
2635
+ const currentTool = editor.getCurrentToolId();
2636
+ if (currentTool !== "draw") return;
2637
+ isDrawingRef.current = true;
2638
+ scribbleDetectedRef.current = false;
2639
+ scribblePointsRef.current = [{
2640
+ x: e.clientX + window.scrollX,
2641
+ y: e.clientY + window.scrollY
2642
+ }];
2643
+ };
2644
+ const handlePointerMove = (e) => {
2645
+ const editor = editorRef.current;
2646
+ if (!editor || !isDrawingRef.current || scribbleDetectedRef.current) return;
2647
+ const currentTool = editor.getCurrentToolId();
2648
+ if (currentTool !== "draw") {
2649
+ isDrawingRef.current = false;
2650
+ return;
2651
+ }
2652
+ const newPoint = {
2653
+ x: e.clientX + window.scrollX,
2654
+ y: e.clientY + window.scrollY
2655
+ };
2656
+ const points = scribblePointsRef.current;
2657
+ const lastPoint = points[points.length - 1];
2658
+ if (lastPoint) {
2659
+ const dx = newPoint.x - lastPoint.x;
2660
+ const dy = newPoint.y - lastPoint.y;
2661
+ const dist = Math.sqrt(dx * dx + dy * dy);
2662
+ if (dist < 3) return;
2663
+ }
2664
+ points.push(newPoint);
2665
+ scribblePointsRef.current = points;
2666
+ if (points.length >= 20 && points.length % 5 === 0) {
2667
+ const gestureResult = isRealtimeScribble(points);
2668
+ if (gestureResult.isScribble) {
2669
+ const bounds = getPointsBounds(points);
2670
+ if (bounds) {
2671
+ const overlappingIds = findOverlappingShapesFromBounds(editor, bounds, []);
2672
+ if (overlappingIds.length > 0) {
2673
+ scribbleDetectedRef.current = true;
2674
+ isDrawingRef.current = false;
2675
+ handleScribbleDelete(overlappingIds);
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2680
+ };
2681
+ const handlePointerUp = () => {
2682
+ isDrawingRef.current = false;
2683
+ scribblePointsRef.current = [];
2684
+ scribbleDetectedRef.current = false;
2685
+ };
2686
+ document.addEventListener("pointerdown", handlePointerDown, { capture: true });
2687
+ document.addEventListener("pointermove", handlePointerMove, { capture: true });
2688
+ document.addEventListener("pointerup", handlePointerUp, { capture: true });
2689
+ document.addEventListener("pointercancel", handlePointerUp, { capture: true });
2690
+ return () => {
2691
+ document.removeEventListener("pointerdown", handlePointerDown, { capture: true });
2692
+ document.removeEventListener("pointermove", handlePointerMove, { capture: true });
2693
+ document.removeEventListener("pointerup", handlePointerUp, { capture: true });
2694
+ document.removeEventListener("pointercancel", handlePointerUp, { capture: true });
2695
+ };
2696
+ }, [isActive, editorRef, setAnnotations, setScribbleToast]);
2697
+ }
2698
+ var LassoOverlay = () => {
2699
+ const editor = tldraw.useEditor();
2700
+ const lassoPoints = tldraw.useValue(
2701
+ "lasso points",
2702
+ () => {
2703
+ if (!editor.isIn("lasso-select.lassoing")) return [];
2704
+ const lassoing = editor.getStateDescendant("lasso-select.lassoing");
2705
+ return lassoing?.points?.get() ?? [];
2706
+ },
2707
+ [editor]
2708
+ );
2709
+ const svgPath = react.useMemo(() => {
2710
+ if (lassoPoints.length < 2) return "";
2711
+ let path = `M ${lassoPoints[0].x} ${lassoPoints[0].y}`;
2712
+ for (let i = 1; i < lassoPoints.length; i++) {
2713
+ path += ` L ${lassoPoints[i].x} ${lassoPoints[i].y}`;
2714
+ }
2715
+ path += " Z";
2716
+ return path;
2717
+ }, [lassoPoints]);
2718
+ if (lassoPoints.length === 0) return null;
2719
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "tl-overlays__item", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
2720
+ "path",
2721
+ {
2722
+ d: svgPath,
2723
+ fill: "none",
2724
+ stroke: "rgba(59, 130, 246, 1)",
2725
+ strokeWidth: "calc(2px / var(--tl-zoom))",
2726
+ strokeLinecap: "round",
2727
+ strokeLinejoin: "round",
2728
+ strokeDasharray: "4 4"
2729
+ }
2730
+ ) });
2731
+ };
2732
+ var SkemaOverlays = () => {
2733
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2734
+ /* @__PURE__ */ jsxRuntime.jsx(tldraw.TldrawOverlays, {}),
2735
+ /* @__PURE__ */ jsxRuntime.jsx(LassoOverlay, {})
2736
+ ] });
2737
+ };
2738
+
2739
+ // src/lib/tldrawConfig.ts
2740
+ var skemaComponents = {
2741
+ Toolbar: null,
2742
+ Overlays: SkemaOverlays,
2743
+ // Hide background to make canvas transparent (so website shows through)
2744
+ Background: null,
2745
+ // Hide UI elements we don't need
2746
+ SharePanel: null,
2747
+ MenuPanel: null,
2748
+ TopPanel: null,
2749
+ PageMenu: null,
2750
+ NavigationPanel: null,
2751
+ HelpMenu: null,
2752
+ Minimap: null,
2753
+ // Hide "Back to Content" button (HelperButtons contains this)
2754
+ HelperButtons: null,
2755
+ QuickActions: null,
2756
+ ZoomMenu: null,
2757
+ ActionsMenu: null,
2758
+ DebugPanel: null,
2759
+ DebugMenu: null,
2760
+ // Hide canvas overlays
2761
+ OnTheCanvas: null,
2762
+ InFrontOfTheCanvas: null
2763
+ };
2764
+ var skemaOverrides = {
2765
+ tools(editor, tools) {
2766
+ return {
2767
+ ...tools,
2768
+ "select": {
2769
+ ...tools["select"],
2770
+ kbd: "s"
2771
+ },
2772
+ "lasso-select": {
2773
+ id: "lasso-select",
2774
+ label: "Lasso Select",
2775
+ icon: "blob",
2776
+ kbd: "l",
2777
+ onSelect: () => {
2778
+ editor.setCurrentTool("lasso-select");
2779
+ }
2780
+ }
2781
+ };
2782
+ }
2783
+ };
2784
+ var skemaHiddenUiStyles = `
2785
+ .tlui-button[data-testid="back-to-content"],
2786
+ .tlui-offscreen-indicator,
2787
+ [class*="back-to-content"],
2788
+ .tl-offscreen-indicator {
2789
+ display: none !important;
2790
+ }
2791
+ `;
2792
+ var skemaToastStyles = `
2793
+ @keyframes skema-toast-fade {
2794
+ from {
2795
+ opacity: 0;
2796
+ transform: translateX(-50%) translateY(10px);
2797
+ }
2798
+ to {
2799
+ opacity: 1;
2800
+ transform: translateX(-50%) translateY(0);
2801
+ }
2802
+ }
2803
+ `;
2804
+ var Skema = ({
2805
+ enabled = true,
2806
+ daemonUrl = "ws://localhost:9999",
2807
+ onAnnotationsChange,
2808
+ onAnnotationSubmit: externalOnAnnotationSubmit,
2809
+ onAnnotationDelete: externalOnAnnotationDelete,
2810
+ onProcessingCancel: externalOnProcessingCancel,
2811
+ toggleShortcut = "mod+shift+e",
2812
+ initialAnnotations = [],
2813
+ zIndex = 99999,
2814
+ isProcessing: externalIsProcessing
2815
+ }) => {
2816
+ const [isActive, setIsActive] = react.useState(enabled);
2817
+ const [annotations, setAnnotations] = react.useState(initialAnnotations);
2818
+ const [domSelections, setDomSelections] = react.useState([]);
2819
+ const [pendingAnnotation, setPendingAnnotation] = react.useState(null);
2820
+ const [pendingExiting, setPendingExiting] = react.useState(false);
2821
+ const [hoveredMarkerId, setHoveredMarkerId] = react.useState(null);
2822
+ const [processingBoundingBox, setProcessingBoundingBox] = react.useState(null);
2823
+ const [scribbleToast, setScribbleToast] = react.useState(null);
2824
+ const [internalIsProcessing, setInternalIsProcessing] = react.useState(false);
2825
+ const [isToolbarExpanded, setIsToolbarExpanded] = react.useState(false);
2826
+ const [isStylePanelOpen, setIsStylePanelOpen] = react.useState(false);
2827
+ const annotationChangesRef = react.useRef(/* @__PURE__ */ new Map());
2828
+ const {
2829
+ state: daemonState,
2830
+ isGenerating,
2831
+ generate,
2832
+ revert
2833
+ } = useDaemon({
2834
+ url: daemonUrl || "ws://localhost:9999",
2835
+ autoConnect: daemonUrl !== null,
2836
+ autoReconnect: daemonUrl !== null
2837
+ });
2838
+ const isProcessing = externalIsProcessing !== void 0 ? externalIsProcessing : internalIsProcessing || isGenerating;
2839
+ const internalOnAnnotationSubmit = react.useCallback(async (annotation, comment) => {
2840
+ if (!daemonState.connected) {
2841
+ console.warn("[Skema] Not connected to daemon. Run: npx skema-serve");
2842
+ return;
2843
+ }
2844
+ setInternalIsProcessing(true);
2845
+ try {
2846
+ const result = await generate(
2847
+ { ...annotation, comment },
2848
+ (event) => {
2849
+ if (event.type === "text") {
2850
+ console.log(`%c[Skema ${daemonState.provider}]`, "color: #10b981", event.content);
2851
+ } else if (event.type === "error") {
2852
+ console.error("[Skema Error]", event.content);
2853
+ }
2854
+ }
2855
+ );
2856
+ if (result.annotationId) {
2857
+ annotationChangesRef.current.set(annotation.id, result.annotationId);
2858
+ }
2859
+ } catch (error) {
2860
+ console.error("[Skema] Failed to generate:", error);
2861
+ } finally {
2862
+ setInternalIsProcessing(false);
2863
+ }
2864
+ }, [daemonState.connected, daemonState.provider, generate]);
2865
+ const internalOnAnnotationDelete = react.useCallback(async (annotationId) => {
2866
+ const trackedId = annotationChangesRef.current.get(annotationId);
2867
+ if (!trackedId) {
2868
+ return;
2869
+ }
2870
+ try {
2871
+ await revert(trackedId);
2872
+ annotationChangesRef.current.delete(annotationId);
2873
+ } catch (error) {
2874
+ console.error("[Skema] Failed to revert:", error);
2875
+ }
2876
+ }, [revert]);
2877
+ const internalOnProcessingCancel = react.useCallback(() => {
2878
+ setInternalIsProcessing(false);
2879
+ }, []);
2880
+ const onAnnotationSubmit = externalOnAnnotationSubmit || (daemonUrl !== null ? internalOnAnnotationSubmit : void 0);
2881
+ const onAnnotationDelete = externalOnAnnotationDelete || (daemonUrl !== null ? internalOnAnnotationDelete : void 0);
2882
+ const onProcessingCancel = externalOnProcessingCancel || internalOnProcessingCancel;
2883
+ const editorRef = react.useRef(null);
2884
+ const popupRef = react.useRef(null);
2885
+ const lastDoubleClickRef = react.useRef(0);
2886
+ const cleanupRef = react.useRef(null);
2887
+ useKeyboardShortcuts({
2888
+ onToggle: react.useCallback(() => setIsActive((prev) => !prev), []),
2889
+ shortcut: toggleShortcut
2890
+ });
2891
+ const scrollOffset = useScrollSync(isActive, editorRef);
2892
+ useWheelIntercept(isActive);
2893
+ useShapePersistence(isActive, editorRef);
2894
+ useScribbleDelete({
2895
+ isActive,
2896
+ editorRef,
2897
+ setAnnotations,
2898
+ setScribbleToast
2899
+ });
1531
2900
  react.useEffect(() => {
1532
2901
  onAnnotationsChange?.(annotations);
1533
2902
  }, [annotations, onAnnotationsChange]);
2903
+ react.useEffect(() => {
2904
+ if (!isProcessing) {
2905
+ setProcessingBoundingBox(null);
2906
+ }
2907
+ }, [isProcessing]);
2908
+ react.useEffect(() => {
2909
+ return () => {
2910
+ if (cleanupRef.current) {
2911
+ cleanupRef.current();
2912
+ }
2913
+ };
2914
+ }, []);
2915
+ react.useEffect(() => {
2916
+ if (!isActive) return;
2917
+ const handleResize = () => {
2918
+ setDomSelections((prevSelections) => {
2919
+ if (prevSelections.length === 0) return prevSelections;
2920
+ return prevSelections.map((selection) => {
2921
+ if (selection.isMultiSelect && selection.elements) {
2922
+ const updatedElements = selection.elements.map((el) => {
2923
+ const domElement2 = document.querySelector(el.selector);
2924
+ if (domElement2) {
2925
+ return { ...el, boundingBox: getBoundingBox(domElement2) };
2926
+ }
2927
+ return el;
2928
+ });
2929
+ const validElements = updatedElements.filter((el) => {
2930
+ const domEl = document.querySelector(el.selector);
2931
+ return domEl !== null;
2932
+ });
2933
+ if (validElements.length > 0) {
2934
+ const minX = Math.min(...updatedElements.map((e) => e.boundingBox.x));
2935
+ const minY = Math.min(...updatedElements.map((e) => e.boundingBox.y));
2936
+ const maxX = Math.max(...updatedElements.map((e) => e.boundingBox.x + e.boundingBox.width));
2937
+ const maxY = Math.max(...updatedElements.map((e) => e.boundingBox.y + e.boundingBox.height));
2938
+ return {
2939
+ ...selection,
2940
+ elements: updatedElements,
2941
+ boundingBox: {
2942
+ x: minX,
2943
+ y: minY,
2944
+ width: maxX - minX,
2945
+ height: maxY - minY
2946
+ }
2947
+ };
2948
+ }
2949
+ return selection;
2950
+ }
2951
+ const domElement = document.querySelector(selection.selector);
2952
+ if (domElement) {
2953
+ return { ...selection, boundingBox: getBoundingBox(domElement) };
2954
+ }
2955
+ return selection;
2956
+ });
2957
+ });
2958
+ setAnnotations((prevAnnotations) => {
2959
+ if (prevAnnotations.length === 0) return prevAnnotations;
2960
+ return prevAnnotations.map((annotation) => {
2961
+ if (annotation.type !== "dom_selection") return annotation;
2962
+ if (annotation.isMultiSelect && annotation.elements) {
2963
+ const updatedElements = annotation.elements.map((el) => {
2964
+ const domElement2 = document.querySelector(el.selector);
2965
+ if (domElement2) {
2966
+ return { ...el, boundingBox: getBoundingBox(domElement2) };
2967
+ }
2968
+ return el;
2969
+ });
2970
+ const validElements = updatedElements.filter((el) => {
2971
+ const domEl = document.querySelector(el.selector);
2972
+ return domEl !== null;
2973
+ });
2974
+ if (validElements.length > 0) {
2975
+ const minX = Math.min(...updatedElements.map((e) => e.boundingBox.x));
2976
+ const minY = Math.min(...updatedElements.map((e) => e.boundingBox.y));
2977
+ const maxX = Math.max(...updatedElements.map((e) => e.boundingBox.x + e.boundingBox.width));
2978
+ const maxY = Math.max(...updatedElements.map((e) => e.boundingBox.y + e.boundingBox.height));
2979
+ return {
2980
+ ...annotation,
2981
+ elements: updatedElements,
2982
+ boundingBox: {
2983
+ x: minX,
2984
+ y: minY,
2985
+ width: maxX - minX,
2986
+ height: maxY - minY
2987
+ }
2988
+ };
2989
+ }
2990
+ return annotation;
2991
+ }
2992
+ const domElement = document.querySelector(annotation.selector);
2993
+ if (domElement) {
2994
+ return { ...annotation, boundingBox: getBoundingBox(domElement) };
2995
+ }
2996
+ return annotation;
2997
+ });
2998
+ });
2999
+ };
3000
+ window.addEventListener("resize", handleResize);
3001
+ return () => window.removeEventListener("resize", handleResize);
3002
+ }, [isActive]);
1534
3003
  const getSelectedDrawings = react.useCallback(() => {
1535
3004
  if (!editorRef.current) return [];
1536
3005
  const editor = editorRef.current;
@@ -1540,6 +3009,32 @@ var Skema = ({
1540
3009
  (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
1541
3010
  );
1542
3011
  }, []);
3012
+ const findDOMElementsInBounds = react.useCallback((bounds) => {
3013
+ const elements = [];
3014
+ const allElements = document.querySelectorAll("*");
3015
+ allElements.forEach((el) => {
3016
+ if (!(el instanceof HTMLElement)) return;
3017
+ if (shouldIgnoreElement(el)) return;
3018
+ const rect = el.getBoundingClientRect();
3019
+ const elBounds = {
3020
+ x: rect.left,
3021
+ y: rect.top,
3022
+ width: rect.width,
3023
+ height: rect.height
3024
+ };
3025
+ if (elBounds.width < 10 || elBounds.height < 10) return;
3026
+ if (bboxIntersects(bounds, elBounds)) {
3027
+ const isParent = elements.some((existing) => el.contains(existing));
3028
+ if (!isParent) {
3029
+ const filtered = elements.filter((existing) => !existing.contains(el) && !el.contains(existing));
3030
+ filtered.push(el);
3031
+ elements.length = 0;
3032
+ elements.push(...filtered);
3033
+ }
3034
+ }
3035
+ });
3036
+ return elements;
3037
+ }, []);
1543
3038
  const handleDOMSelect = react.useCallback((selection) => {
1544
3039
  const selectedDrawings = getSelectedDrawings();
1545
3040
  const hasDrawings = selectedDrawings.length > 0;
@@ -1602,25 +3097,83 @@ var Skema = ({
1602
3097
  shapeIds: hasDrawings ? editorRef.current?.getSelectedShapeIds() : void 0
1603
3098
  });
1604
3099
  }, [getSelectedDrawings]);
3100
+ const handleDrawingAnnotation = react.useCallback((selectedIds, skipDomElements = false) => {
3101
+ if (!editorRef.current || selectedIds.length === 0) return;
3102
+ if (pendingAnnotation) return;
3103
+ const editor = editorRef.current;
3104
+ const selectedShapes = selectedIds.map((id) => editor.getShape(id)).filter(Boolean);
3105
+ if (selectedShapes.length === 0) return;
3106
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
3107
+ for (const shape of selectedShapes) {
3108
+ if (!shape) continue;
3109
+ const bounds = editor.getShapePageBounds(shape.id);
3110
+ if (bounds) {
3111
+ minX = Math.min(minX, bounds.x);
3112
+ minY = Math.min(minY, bounds.y);
3113
+ maxX = Math.max(maxX, bounds.x + bounds.width);
3114
+ maxY = Math.max(maxY, bounds.y + bounds.height);
3115
+ }
3116
+ }
3117
+ if (minX === Infinity) return;
3118
+ const selectionBounds = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
3119
+ const drawingShapes = selectedShapes.filter(
3120
+ (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
3121
+ );
3122
+ if (drawingShapes.length === 0) return;
3123
+ let domElements = [];
3124
+ if (!skipDomElements) {
3125
+ const viewportBounds = {
3126
+ x: selectionBounds.x - window.scrollX,
3127
+ y: selectionBounds.y - window.scrollY,
3128
+ width: selectionBounds.width,
3129
+ height: selectionBounds.height
3130
+ };
3131
+ domElements = findDOMElementsInBounds(viewportBounds);
3132
+ }
3133
+ const centerX = selectionBounds.x + selectionBounds.width / 2;
3134
+ const centerY = selectionBounds.y + selectionBounds.height / 2;
3135
+ const x = (centerX - window.scrollX) / window.innerWidth * 100;
3136
+ const clientY = centerY - window.scrollY;
3137
+ let elementDesc = `Drawing (${drawingShapes.length} shape${drawingShapes.length > 1 ? "s" : ""})`;
3138
+ if (domElements.length > 0) {
3139
+ const domNames = domElements.slice(0, 2).map((el) => el.tagName.toLowerCase()).join(", ");
3140
+ const domSuffix = domElements.length > 2 ? ` +${domElements.length - 2} more` : "";
3141
+ elementDesc += ` + ${domNames}${domSuffix}`;
3142
+ }
3143
+ const newDomSelections = domElements.map((el) => createDOMSelection(el));
3144
+ setPendingAnnotation({
3145
+ x,
3146
+ y: centerY,
3147
+ clientY,
3148
+ element: elementDesc,
3149
+ elementPath: "drawing",
3150
+ boundingBox: selectionBounds,
3151
+ isMultiSelect: drawingShapes.length > 1 || domElements.length > 0,
3152
+ annotationType: "drawing",
3153
+ shapeIds: selectedIds,
3154
+ selections: newDomSelections.length > 0 ? newDomSelections : void 0
3155
+ });
3156
+ }, [pendingAnnotation, findDOMElementsInBounds]);
1605
3157
  const handleAnnotationSubmit = react.useCallback(async (comment) => {
1606
3158
  if (!pendingAnnotation) return;
3159
+ const now = Date.now();
3160
+ let submittedAnnotation;
1607
3161
  if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1608
3162
  const selections = pendingAnnotation.selections;
1609
3163
  if (selections.length === 1) {
1610
3164
  const selection = { ...selections[0], comment };
1611
3165
  setDomSelections((prev) => [...prev, selection]);
1612
3166
  setAnnotations((prev) => [...prev, { type: "dom_selection", ...selection }]);
3167
+ submittedAnnotation = { type: "dom_selection", ...selections[0], comment };
1613
3168
  } else {
1614
3169
  const groupedSelection = {
1615
- id: `group-${Date.now()}`,
3170
+ id: `group-${now}`,
1616
3171
  selector: selections.map((s) => s.selector).join(", "),
1617
3172
  tagName: pendingAnnotation.element,
1618
- // Already formatted as "3 elements: div, span, p"
1619
3173
  elementPath: selections[0].elementPath,
1620
- // Use first element's path as reference
1621
3174
  text: selections.map((s) => s.text).filter(Boolean).join(" | ").slice(0, 200),
1622
3175
  boundingBox: pendingAnnotation.boundingBox,
1623
- timestamp: Date.now(),
3176
+ timestamp: now,
1624
3177
  pathname: selections[0].pathname,
1625
3178
  comment,
1626
3179
  isMultiSelect: true,
@@ -1636,174 +3189,64 @@ var Skema = ({
1636
3189
  };
1637
3190
  setDomSelections((prev) => [...prev, groupedSelection]);
1638
3191
  setAnnotations((prev) => [...prev, { type: "dom_selection", ...groupedSelection }]);
3192
+ submittedAnnotation = { type: "dom_selection", ...groupedSelection };
1639
3193
  }
1640
3194
  } else if (pendingAnnotation.annotationType === "drawing" && pendingAnnotation.shapeIds) {
1641
- const bbox = pendingAnnotation.boundingBox;
1642
- const nearbyElements = pendingAnnotation.selections?.map((s) => ({
1643
- selector: s.selector,
1644
- tagName: s.tagName,
1645
- text: s.text?.slice(0, 100)
1646
- })) || [];
3195
+ const editor = editorRef.current;
3196
+ let drawingSvg;
3197
+ let drawingImage;
3198
+ let extractedText;
3199
+ const gridConfig = { color: "#0066FF", size: 100, labels: true };
3200
+ if (editor && pendingAnnotation.shapeIds && pendingAnnotation.shapeIds.length > 0) {
3201
+ const shapeIds = pendingAnnotation.shapeIds;
3202
+ try {
3203
+ const svgResult = await editor.getSvgString(shapeIds, { padding: 20, background: false });
3204
+ if (svgResult?.svg) {
3205
+ drawingSvg = addGridToSvg(svgResult.svg, gridConfig);
3206
+ }
3207
+ } catch (e) {
3208
+ console.warn("[Skema] Failed to export drawing SVG:", e);
3209
+ }
3210
+ try {
3211
+ const imageResult = await editor.toImage(shapeIds, { format: "png", padding: 20, background: true });
3212
+ if (imageResult?.blob) {
3213
+ drawingImage = await blobToBase64(imageResult.blob);
3214
+ }
3215
+ } catch (e) {
3216
+ console.warn("[Skema] Failed to export drawing image:", e);
3217
+ }
3218
+ try {
3219
+ const shapes = shapeIds.map((id) => editor.getShape(id)).filter(Boolean);
3220
+ extractedText = extractTextFromShapes(shapes);
3221
+ } catch (e) {
3222
+ console.warn("[Skema] Failed to extract text from shapes:", e);
3223
+ }
3224
+ }
3225
+ const nearbyElements = pendingAnnotation.boundingBox ? findNearbyElementsWithStyles(pendingAnnotation.boundingBox, 5) : [];
3226
+ const projectStyles = extractProjectStyleContext();
3227
+ const viewport = getViewportInfo();
1647
3228
  const drawingAnnotation = {
1648
- id: `drawing-${Date.now()}`,
3229
+ id: `drawing-${now}`,
1649
3230
  type: "drawing",
1650
3231
  tool: "draw",
1651
3232
  shapes: pendingAnnotation.shapeIds,
1652
- boundingBox: bbox,
1653
- timestamp: Date.now(),
3233
+ boundingBox: pendingAnnotation.boundingBox,
3234
+ timestamp: now,
1654
3235
  comment,
1655
- nearbyElements
3236
+ drawingSvg,
3237
+ drawingImage,
3238
+ extractedText: extractedText || void 0,
3239
+ gridConfig,
3240
+ nearbyElements,
3241
+ viewport,
3242
+ projectStyles
1656
3243
  };
1657
3244
  setAnnotations((prev) => [...prev, drawingAnnotation]);
1658
- const drawingLog = `
1659
- ### ${annotations.length + 1}. Drawing (${pendingAnnotation.shapeIds?.length || 0} shapes)
1660
- **Position:** x:${Math.round(bbox.x)}, y:${Math.round(bbox.y)} (${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px)
1661
- **Annotation at:** ${((bbox.x + bbox.width / 2) / window.innerWidth * 100).toFixed(1)}% from left, ${Math.round(bbox.y + bbox.height / 2)}px from top
1662
- **Shape IDs:** ${pendingAnnotation.shapeIds?.join(", ") || "none"}
1663
- **Nearby Elements:** ${nearbyElements.map((e) => e.tagName).join(", ") || "none"}
1664
- **Feedback:** ${comment}
1665
- `;
1666
- console.log(drawingLog);
3245
+ submittedAnnotation = drawingAnnotation;
1667
3246
  }
1668
- if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1669
- const selections = pendingAnnotation.selections;
1670
- const annotationIndex = annotations.length + 1;
1671
- const elementDescs = selections.map((s) => {
1672
- const textPreview = s.text?.slice(0, 50) || "";
1673
- return `${s.tagName.toLowerCase()}${textPreview ? `: "${textPreview}..."` : ""}`;
1674
- }).join(", ");
1675
- const firstSelection = selections[0];
1676
- const firstElement = document.querySelector(firstSelection.selector);
1677
- let computedStylesStr = "N/A";
1678
- let nearbyElements = "N/A";
1679
- if (firstElement) {
1680
- const styles2 = window.getComputedStyle(firstElement);
1681
- computedStylesStr = [
1682
- `color: ${styles2.color}`,
1683
- `border-color: ${styles2.borderColor}`,
1684
- `font-size: ${styles2.fontSize}`,
1685
- `font-weight: ${styles2.fontWeight}`,
1686
- `font-family: ${styles2.fontFamily}`,
1687
- `line-height: ${styles2.lineHeight}`,
1688
- `letter-spacing: ${styles2.letterSpacing}`,
1689
- `text-align: ${styles2.textAlign}`,
1690
- `width: ${styles2.width}`,
1691
- `height: ${styles2.height}`,
1692
- `border: ${styles2.border}`,
1693
- `display: ${styles2.display}`,
1694
- `flex-direction: ${styles2.flexDirection}`,
1695
- `opacity: ${styles2.opacity}`
1696
- ].join("; ");
1697
- const parent = firstElement.parentElement;
1698
- if (parent) {
1699
- const siblings = Array.from(parent.children).filter((el) => el !== firstElement).slice(0, 3).map((el) => el.tagName.toLowerCase());
1700
- nearbyElements = siblings.length > 0 ? siblings.join(", ") : "none";
1701
- }
1702
- }
1703
- const bbox = pendingAnnotation.boundingBox;
1704
- const forensicLog = `
1705
- ### ${annotationIndex}. ${selections.length > 1 ? `${selections.length} elements: ` : ""}${elementDescs}
1706
- ${selections.length > 1 ? "*Forensic data shown for first element of selection*\n" : ""}**Full DOM Path:** ${firstSelection.elementPath}
1707
- **Position:** x:${Math.round(bbox.x)}, y:${Math.round(bbox.y)} (${Math.round(bbox.width)}\xD7${Math.round(bbox.height)}px)
1708
- **Annotation at:** ${((bbox.x + bbox.width / 2) / window.innerWidth * 100).toFixed(1)}% from left, ${Math.round(bbox.y + bbox.height / 2)}px from top
1709
- **Computed Styles:** ${computedStylesStr}
1710
- **Nearby Elements:** ${nearbyElements}
1711
- **Feedback:** ${comment}
1712
- `;
1713
- console.log(forensicLog);
1714
- }
1715
- if (onAnnotationSubmit) {
1716
- let submittedAnnotation;
1717
- if (pendingAnnotation.annotationType === "dom_selection" && pendingAnnotation.selections) {
1718
- const selections = pendingAnnotation.selections;
1719
- if (selections.length === 1) {
1720
- submittedAnnotation = { type: "dom_selection", ...selections[0], comment };
1721
- } else {
1722
- submittedAnnotation = {
1723
- type: "dom_selection",
1724
- id: `group-${Date.now()}`,
1725
- selector: selections.map((s) => s.selector).join(", "),
1726
- tagName: pendingAnnotation.element,
1727
- elementPath: selections[0].elementPath,
1728
- text: selections.map((s) => s.text).filter(Boolean).join(" | ").slice(0, 200),
1729
- boundingBox: pendingAnnotation.boundingBox,
1730
- timestamp: Date.now(),
1731
- pathname: selections[0].pathname,
1732
- comment,
1733
- isMultiSelect: true,
1734
- elements: selections.map((s) => ({
1735
- selector: s.selector,
1736
- tagName: s.tagName,
1737
- elementPath: s.elementPath,
1738
- text: s.text,
1739
- boundingBox: s.boundingBox,
1740
- cssClasses: s.cssClasses,
1741
- attributes: s.attributes
1742
- }))
1743
- };
1744
- }
1745
- } else {
1746
- const editor = editorRef.current;
1747
- let drawingSvg;
1748
- let drawingImage;
1749
- let extractedText;
1750
- const gridConfig = { color: "#0066FF", size: 100, labels: true };
1751
- console.log("[Skema] Drawing annotation - shapeIds:", pendingAnnotation.shapeIds);
1752
- if (editor && pendingAnnotation.shapeIds && pendingAnnotation.shapeIds.length > 0) {
1753
- const shapeIds = pendingAnnotation.shapeIds;
1754
- console.log("[Skema] Exporting", shapeIds.length, "shapes for image...");
1755
- try {
1756
- const svgResult = await editor.getSvgString(shapeIds, {
1757
- padding: 20,
1758
- background: false
1759
- });
1760
- if (svgResult?.svg) {
1761
- drawingSvg = addGridToSvg(svgResult.svg, gridConfig);
1762
- console.log("[Skema] SVG export successful");
1763
- }
1764
- } catch (e) {
1765
- console.warn("[Skema] Failed to export drawing SVG:", e);
1766
- }
1767
- try {
1768
- const imageResult = await editor.toImage(shapeIds, {
1769
- format: "png",
1770
- padding: 20,
1771
- background: true
1772
- });
1773
- console.log("[Skema] toImage result:", imageResult ? "got result" : "null", imageResult?.blob ? "has blob" : "no blob");
1774
- if (imageResult?.blob) {
1775
- drawingImage = await blobToBase64(imageResult.blob);
1776
- console.log("[Skema] Image export successful, base64 length:", drawingImage?.length);
1777
- }
1778
- } catch (e) {
1779
- console.warn("[Skema] Failed to export drawing image:", e);
1780
- }
1781
- try {
1782
- const shapes = shapeIds.map((id) => editor.getShape(id)).filter(Boolean);
1783
- extractedText = extractTextFromShapes(shapes);
1784
- } catch (e) {
1785
- console.warn("[Skema] Failed to extract text from shapes:", e);
1786
- }
1787
- }
1788
- const nearbyElements = pendingAnnotation.boundingBox ? findNearbyElementsWithStyles(pendingAnnotation.boundingBox, 5) : [];
1789
- const projectStyles = extractProjectStyleContext();
1790
- const viewport = getViewportInfo();
1791
- submittedAnnotation = {
1792
- id: `drawing-${Date.now()}`,
1793
- type: "drawing",
1794
- tool: "draw",
1795
- shapes: pendingAnnotation.shapeIds || [],
1796
- boundingBox: pendingAnnotation.boundingBox,
1797
- timestamp: Date.now(),
1798
- comment,
1799
- drawingSvg,
1800
- drawingImage,
1801
- extractedText: extractedText || void 0,
1802
- gridConfig,
1803
- nearbyElements,
1804
- viewport,
1805
- projectStyles
1806
- };
3247
+ if (onAnnotationSubmit && submittedAnnotation) {
3248
+ if (pendingAnnotation.boundingBox) {
3249
+ setProcessingBoundingBox(pendingAnnotation.boundingBox);
1807
3250
  }
1808
3251
  onAnnotationSubmit(submittedAnnotation, comment);
1809
3252
  }
@@ -1815,19 +3258,27 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1815
3258
  }, [pendingAnnotation, onAnnotationSubmit]);
1816
3259
  const handleAnnotationCancel = react.useCallback(() => {
1817
3260
  setPendingExiting(true);
3261
+ if (isProcessing) {
3262
+ onProcessingCancel?.();
3263
+ }
3264
+ setProcessingBoundingBox(null);
1818
3265
  setTimeout(() => {
1819
3266
  setPendingAnnotation(null);
1820
3267
  setPendingExiting(false);
1821
3268
  }, 150);
1822
- }, []);
3269
+ }, [isProcessing, onProcessingCancel]);
1823
3270
  const handleDeleteAnnotation = react.useCallback((annotation) => {
1824
3271
  setAnnotations((prev) => prev.filter((a) => a.id !== annotation.id));
1825
3272
  if (annotation.type === "dom_selection") {
1826
3273
  setDomSelections((prev) => prev.filter((s) => s.id !== annotation.id));
1827
3274
  }
1828
3275
  setHoveredMarkerId(null);
3276
+ if (isProcessing) {
3277
+ onProcessingCancel?.();
3278
+ setProcessingBoundingBox(null);
3279
+ }
1829
3280
  onAnnotationDelete?.(annotation.id);
1830
- }, [onAnnotationDelete]);
3281
+ }, [onAnnotationDelete, isProcessing, onProcessingCancel]);
1831
3282
  const handleClear = react.useCallback(() => {
1832
3283
  setAnnotations([]);
1833
3284
  setDomSelections([]);
@@ -1848,97 +3299,14 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1848
3299
  console.log("[Skema] Exported annotations:", exportData);
1849
3300
  alert("Annotations copied to clipboard!");
1850
3301
  }, [annotations]);
1851
- const findDOMElementsInBounds = react.useCallback((bounds) => {
1852
- const elements = [];
1853
- const allElements = document.querySelectorAll("*");
1854
- allElements.forEach((el) => {
1855
- if (!(el instanceof HTMLElement)) return;
1856
- if (shouldIgnoreElement(el)) return;
1857
- const rect = el.getBoundingClientRect();
1858
- const elBounds = {
1859
- x: rect.left,
1860
- y: rect.top,
1861
- width: rect.width,
1862
- height: rect.height
1863
- };
1864
- if (elBounds.width < 10 || elBounds.height < 10) return;
1865
- if (bboxIntersects(bounds, elBounds)) {
1866
- const isParent = elements.some((existing) => el.contains(existing));
1867
- if (!isParent) {
1868
- const filtered = elements.filter((existing) => !existing.contains(el) && !el.contains(existing));
1869
- filtered.push(el);
1870
- elements.length = 0;
1871
- elements.push(...filtered);
1872
- }
1873
- }
1874
- });
1875
- return elements;
1876
- }, []);
1877
- const handleDrawingAnnotation = react.useCallback((selectedIds) => {
1878
- if (!editorRef.current || selectedIds.length === 0) return;
1879
- if (pendingAnnotation) return;
1880
- const editor = editorRef.current;
1881
- const selectedShapes = selectedIds.map((id) => editor.getShape(id)).filter(Boolean);
1882
- if (selectedShapes.length === 0) return;
1883
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1884
- for (const shape of selectedShapes) {
1885
- if (!shape) continue;
1886
- const bounds = editor.getShapePageBounds(shape.id);
1887
- if (bounds) {
1888
- minX = Math.min(minX, bounds.x);
1889
- minY = Math.min(minY, bounds.y);
1890
- maxX = Math.max(maxX, bounds.x + bounds.width);
1891
- maxY = Math.max(maxY, bounds.y + bounds.height);
3302
+ const handleBrushSelection = react.useCallback((brushBounds) => {
3303
+ if (editorRef.current) {
3304
+ const selectedShapeIds = editorRef.current.getSelectedShapeIds();
3305
+ if (selectedShapeIds.length > 0) {
3306
+ handleDrawingAnnotation(selectedShapeIds, true);
3307
+ return;
1892
3308
  }
1893
3309
  }
1894
- if (minX === Infinity) return;
1895
- const selectionBounds = {
1896
- x: minX,
1897
- y: minY,
1898
- width: maxX - minX,
1899
- height: maxY - minY
1900
- };
1901
- const drawingShapes = selectedShapes.filter(
1902
- (shape) => shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)
1903
- );
1904
- if (drawingShapes.length === 0) return;
1905
- const viewportBounds = {
1906
- x: selectionBounds.x - window.scrollX,
1907
- y: selectionBounds.y - window.scrollY,
1908
- width: selectionBounds.width,
1909
- height: selectionBounds.height
1910
- };
1911
- const domElements = findDOMElementsInBounds(viewportBounds);
1912
- const centerX = selectionBounds.x + selectionBounds.width / 2;
1913
- const centerY = selectionBounds.y + selectionBounds.height / 2;
1914
- const x = (centerX - window.scrollX) / window.innerWidth * 100;
1915
- const clientY = centerY - window.scrollY;
1916
- let elementDesc = `Drawing (${drawingShapes.length} shape${drawingShapes.length > 1 ? "s" : ""})`;
1917
- if (domElements.length > 0) {
1918
- const domNames = domElements.slice(0, 2).map((el) => el.tagName.toLowerCase()).join(", ");
1919
- const domSuffix = domElements.length > 2 ? ` +${domElements.length - 2} more` : "";
1920
- elementDesc += ` + ${domNames}${domSuffix}`;
1921
- }
1922
- const newDomSelections = domElements.map((el) => createDOMSelection(el));
1923
- setPendingAnnotation({
1924
- x,
1925
- y: centerY,
1926
- clientY,
1927
- element: elementDesc,
1928
- elementPath: "drawing",
1929
- boundingBox: {
1930
- x: selectionBounds.x,
1931
- y: selectionBounds.y,
1932
- width: selectionBounds.width,
1933
- height: selectionBounds.height
1934
- },
1935
- isMultiSelect: drawingShapes.length > 1 || domElements.length > 0,
1936
- annotationType: "drawing",
1937
- shapeIds: selectedIds,
1938
- selections: newDomSelections.length > 0 ? newDomSelections : void 0
1939
- });
1940
- }, [pendingAnnotation, findDOMElementsInBounds]);
1941
- const handleBrushSelection = react.useCallback((brushBounds) => {
1942
3310
  const viewportBounds = {
1943
3311
  x: brushBounds.x - window.scrollX,
1944
3312
  y: brushBounds.y - window.scrollY,
@@ -1959,24 +3327,16 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
1959
3327
  } else {
1960
3328
  handleMultiDOMSelect(selections);
1961
3329
  }
1962
- }, [findDOMElementsInBounds, domSelections, handleDOMSelect, handleMultiDOMSelect]);
1963
- const isPointInPolygon = react.useCallback((point, polygon) => {
1964
- let inside = false;
1965
- const n = polygon.length;
1966
- for (let i = 0, j = n - 1; i < n; j = i++) {
1967
- const xi = polygon[i].x;
1968
- const yi = polygon[i].y;
1969
- const xj = polygon[j].x;
1970
- const yj = polygon[j].y;
1971
- const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi;
1972
- if (intersect) {
1973
- inside = !inside;
1974
- }
1975
- }
1976
- return inside;
1977
- }, []);
3330
+ }, [findDOMElementsInBounds, domSelections, handleDOMSelect, handleMultiDOMSelect, handleDrawingAnnotation]);
1978
3331
  const handleLassoSelection = react.useCallback((lassoPoints) => {
1979
3332
  if (lassoPoints.length < 3) return;
3333
+ if (editorRef.current) {
3334
+ const selectedShapeIds = editorRef.current.getSelectedShapeIds();
3335
+ if (selectedShapeIds.length > 0) {
3336
+ handleDrawingAnnotation(selectedShapeIds, true);
3337
+ return;
3338
+ }
3339
+ }
1980
3340
  const viewportPoints = lassoPoints.map((p) => ({
1981
3341
  x: p.x - window.scrollX,
1982
3342
  y: p.y - window.scrollY
@@ -2020,16 +3380,16 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2020
3380
  } else {
2021
3381
  handleMultiDOMSelect(selections);
2022
3382
  }
2023
- }, [isPointInPolygon, domSelections, handleDOMSelect, handleMultiDOMSelect]);
3383
+ }, [domSelections, handleDOMSelect, handleMultiDOMSelect, handleDrawingAnnotation]);
2024
3384
  const handleMount = react.useCallback((editor) => {
2025
3385
  editorRef.current = editor;
2026
3386
  editor.setStyleForNextShapes(tldraw.ArrowShapeKindStyle, "arc");
2027
3387
  try {
2028
3388
  const selectIdleState = editor.getStateDescendant("select.idle");
2029
3389
  if (selectIdleState) {
2030
- selectIdleState.handleDoubleClickOnCanvas = (info) => {
3390
+ selectIdleState.handleDoubleClickOnCanvas = (_info) => {
2031
3391
  lastDoubleClickRef.current = Date.now();
2032
- const point = editor.pageToViewport(info.point);
3392
+ const point = editor.inputs.currentScreenPoint;
2033
3393
  const elements = document.elementsFromPoint(point.x, point.y);
2034
3394
  const target = elements.find(
2035
3395
  (el) => el instanceof HTMLElement && !shouldIgnoreElement(el)
@@ -2053,6 +3413,14 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2053
3413
  });
2054
3414
  }
2055
3415
  };
3416
+ selectIdleState.handleDoubleClickOnShape = (_info, shape) => {
3417
+ lastDoubleClickRef.current = Date.now();
3418
+ if (shape && ["draw", "line", "arrow", "geo", "text", "note"].includes(shape.type)) {
3419
+ const selectedIds = editor.getSelectedShapeIds();
3420
+ const shapeIds = selectedIds.length > 0 ? selectedIds : [shape.id];
3421
+ handleDrawingAnnotation(shapeIds, true);
3422
+ }
3423
+ };
2056
3424
  }
2057
3425
  } catch (e) {
2058
3426
  console.warn("Failed to override double click behavior", e);
@@ -2065,12 +3433,10 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2065
3433
  }
2066
3434
  });
2067
3435
  const lassoSelectTool = editor.root.children?.["lasso-select"];
2068
- if (lassoSelectTool) {
2069
- if ("setOnLassoComplete" in lassoSelectTool) {
2070
- lassoSelectTool.setOnLassoComplete((points) => {
2071
- handleLassoSelection(points);
2072
- });
2073
- }
3436
+ if (lassoSelectTool && "setOnLassoComplete" in lassoSelectTool) {
3437
+ lassoSelectTool.setOnLassoComplete((points) => {
3438
+ handleLassoSelection(points);
3439
+ });
2074
3440
  }
2075
3441
  let lastBrush = null;
2076
3442
  editor.sideEffects.registerAfterChangeHandler("instance", (prev, next) => {
@@ -2086,16 +3452,8 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2086
3452
  } else if (next.brush) {
2087
3453
  lastBrush = next.brush;
2088
3454
  }
2089
- return;
2090
3455
  });
2091
3456
  }, [handleDOMSelect, handleBrushSelection, handleLassoSelection, handleMultiDOMSelect, handleDrawingAnnotation]);
2092
- react.useEffect(() => {
2093
- return () => {
2094
- if (cleanupRef.current) {
2095
- cleanupRef.current();
2096
- }
2097
- };
2098
- }, []);
2099
3457
  react.useEffect(() => {
2100
3458
  if (!isActive) return;
2101
3459
  const handlePointerDown = (e) => {
@@ -2114,46 +3472,6 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2114
3472
  document.addEventListener("pointerdown", handlePointerDown, { capture: true });
2115
3473
  return () => document.removeEventListener("pointerdown", handlePointerDown, { capture: true });
2116
3474
  }, [isActive, pendingAnnotation, handleAnnotationCancel]);
2117
- const components = {
2118
- Toolbar: null,
2119
- Overlays: SkemaOverlays,
2120
- // Hide background to make canvas transparent (so website shows through)
2121
- Background: null,
2122
- // Hide UI elements we don't need
2123
- SharePanel: null,
2124
- MenuPanel: null,
2125
- TopPanel: null,
2126
- PageMenu: null,
2127
- NavigationPanel: null,
2128
- HelpMenu: null,
2129
- Minimap: null,
2130
- // Hide "Back to Content" button (HelperButtons contains this)
2131
- HelperButtons: null,
2132
- QuickActions: null,
2133
- ZoomMenu: null,
2134
- ActionsMenu: null,
2135
- DebugPanel: null,
2136
- DebugMenu: null,
2137
- // Hide canvas overlays
2138
- OnTheCanvas: null,
2139
- InFrontOfTheCanvas: null
2140
- };
2141
- const overrides = {
2142
- tools(editor, tools) {
2143
- return {
2144
- ...tools,
2145
- "lasso-select": {
2146
- id: "lasso-select",
2147
- label: "Lasso Select",
2148
- icon: "blob",
2149
- kbd: "l",
2150
- onSelect: () => {
2151
- editor.setCurrentTool("lasso-select");
2152
- }
2153
- }
2154
- };
2155
- }
2156
- };
2157
3475
  if (!isActive) {
2158
3476
  return null;
2159
3477
  }
@@ -2168,14 +3486,66 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2168
3486
  pointerEvents: "none"
2169
3487
  },
2170
3488
  children: [
2171
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
2172
- .tlui-button[data-testid="back-to-content"],
2173
- .tlui-offscreen-indicator,
2174
- [class*="back-to-content"],
2175
- .tl-offscreen-indicator {
2176
- display: none !important;
2177
- }
2178
- ` }),
3489
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: skemaHiddenUiStyles }),
3490
+ !isStylePanelOpen && /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
3491
+ .tlui-style-panel__wrapper,
3492
+ .tlui-style-panel {
3493
+ display: none !important;
3494
+ }
3495
+ ` }),
3496
+ isStylePanelOpen && /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
3497
+ .tlui-style-panel__wrapper {
3498
+ top: 68px !important;
3499
+ right: 16px !important;
3500
+ }
3501
+ ` }),
3502
+ isToolbarExpanded && /* @__PURE__ */ jsxRuntime.jsx(
3503
+ "button",
3504
+ {
3505
+ onClick: () => setIsStylePanelOpen((prev) => !prev),
3506
+ title: isStylePanelOpen ? "Hide style settings" : "Show style settings",
3507
+ style: {
3508
+ position: "fixed",
3509
+ top: 16,
3510
+ right: 16,
3511
+ width: 44,
3512
+ height: 44,
3513
+ display: "flex",
3514
+ alignItems: "center",
3515
+ justifyContent: "center",
3516
+ backgroundColor: isStylePanelOpen ? "#FF6800" : "white",
3517
+ border: "none",
3518
+ borderRadius: 12,
3519
+ boxShadow: "0 2px 10px rgba(0,0,0,0.15)",
3520
+ cursor: "pointer",
3521
+ pointerEvents: "auto",
3522
+ zIndex: zIndex + 5,
3523
+ transition: "all 0.2s ease"
3524
+ },
3525
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
3526
+ /* @__PURE__ */ jsxRuntime.jsx(
3527
+ "path",
3528
+ {
3529
+ 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",
3530
+ stroke: isStylePanelOpen ? "white" : "#6B7280",
3531
+ strokeWidth: "2",
3532
+ strokeLinecap: "round",
3533
+ strokeLinejoin: "round"
3534
+ }
3535
+ ),
3536
+ /* @__PURE__ */ jsxRuntime.jsx(
3537
+ "path",
3538
+ {
3539
+ 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",
3540
+ stroke: isStylePanelOpen ? "white" : "#6B7280",
3541
+ strokeWidth: "2",
3542
+ strokeLinecap: "round",
3543
+ strokeLinejoin: "round"
3544
+ }
3545
+ )
3546
+ ] })
3547
+ }
3548
+ ),
2179
3549
  /* @__PURE__ */ jsxRuntime.jsx(
2180
3550
  "div",
2181
3551
  {
@@ -2188,21 +3558,31 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2188
3558
  tldraw.Tldraw,
2189
3559
  {
2190
3560
  tools: [LassoSelectTool],
2191
- components,
2192
- overrides,
3561
+ components: skemaComponents,
3562
+ overrides: skemaOverrides,
2193
3563
  onMount: handleMount,
2194
3564
  hideUi: false,
2195
3565
  inferDarkMode: false,
2196
- options: {
2197
- // Disable camera constraints that would interfere with overlay mode
2198
- maxPages: 1
2199
- },
2200
- children: /* @__PURE__ */ jsxRuntime.jsx(SkemaToolbar, {})
3566
+ options: { maxPages: 1 },
3567
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3568
+ SkemaToolbar,
3569
+ {
3570
+ onExpandedChange: setIsToolbarExpanded,
3571
+ onStylePanelChange: setIsStylePanelOpen
3572
+ }
3573
+ )
2201
3574
  }
2202
3575
  )
2203
3576
  }
2204
3577
  ),
2205
3578
  /* @__PURE__ */ jsxRuntime.jsx(SelectionOverlay, { selections: domSelections }),
3579
+ isProcessing && processingBoundingBox && /* @__PURE__ */ jsxRuntime.jsx(
3580
+ ProcessingOverlay,
3581
+ {
3582
+ boundingBox: processingBoundingBox,
3583
+ scrollOffset
3584
+ }
3585
+ ),
2206
3586
  /* @__PURE__ */ jsxRuntime.jsx(
2207
3587
  AnnotationMarkersLayer,
2208
3588
  {
@@ -2247,15 +3627,7 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2247
3627
  isMultiSelect: pendingAnnotation.isMultiSelect,
2248
3628
  accentColor: pendingAnnotation.isMultiSelect ? "#34C759" : "#3b82f6",
2249
3629
  style: {
2250
- // Position popup centered horizontally
2251
- left: Math.max(
2252
- 160,
2253
- Math.min(
2254
- window.innerWidth - 160,
2255
- pendingAnnotation.x / 100 * window.innerWidth
2256
- )
2257
- ),
2258
- // Position above or below based on viewport space
3630
+ left: Math.max(160, Math.min(window.innerWidth - 160, pendingAnnotation.x / 100 * window.innerWidth)),
2259
3631
  ...pendingAnnotation.clientY > window.innerHeight - 250 ? { bottom: window.innerHeight - pendingAnnotation.clientY + 30 } : { top: pendingAnnotation.clientY + 30 },
2260
3632
  zIndex: zIndex + 3,
2261
3633
  pointerEvents: "auto"
@@ -2299,7 +3671,31 @@ ${selections.length > 1 ? "*Forensic data shown for first element of selection*\
2299
3671
  " to toggle Skema"
2300
3672
  ]
2301
3673
  }
2302
- )
3674
+ ),
3675
+ scribbleToast && /* @__PURE__ */ jsxRuntime.jsx(
3676
+ "div",
3677
+ {
3678
+ "data-skema": "scribble-toast",
3679
+ style: {
3680
+ position: "fixed",
3681
+ bottom: 60,
3682
+ left: "50%",
3683
+ transform: "translateX(-50%)",
3684
+ padding: "10px 20px",
3685
+ backgroundColor: "rgba(239, 68, 68, 0.95)",
3686
+ color: "white",
3687
+ borderRadius: "8px",
3688
+ fontSize: "14px",
3689
+ fontWeight: 500,
3690
+ pointerEvents: "none",
3691
+ zIndex: zIndex + 10,
3692
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
3693
+ animation: "skema-toast-fade 0.2s ease-out"
3694
+ },
3695
+ children: scribbleToast
3696
+ }
3697
+ ),
3698
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: skemaToastStyles })
2303
3699
  ]
2304
3700
  }
2305
3701
  );
@@ -2329,6 +3725,7 @@ exports.getViewportInfo = getViewportInfo;
2329
3725
  exports.identifyElement = identifyElement;
2330
3726
  exports.pointInBbox = pointInBbox;
2331
3727
  exports.shouldIgnoreElement = shouldIgnoreElement;
3728
+ exports.useDaemon = useDaemon;
2332
3729
  exports.viewportToDocument = viewportToDocument;
2333
3730
  //# sourceMappingURL=index.js.map
2334
3731
  //# sourceMappingURL=index.js.map