@haklex/rich-ext-tldraw 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1004 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
+ import { createContext, use, useMemo, forwardRef, createElement, useState, useEffect, useRef, useCallback, lazy, Suspense, Component } from "react";
6
+ import { useColorScheme } from "@haklex/rich-editor";
7
+ import { presentDialog, SegmentedControl } from "@haklex/rich-editor-ui";
8
+ import { DecoratorNode, $insertNodes, $getNodeByKey, createCommand, COMMAND_PRIORITY_EDITOR } from "lexical";
9
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
10
+ const TldrawConfigContext = createContext({});
11
+ function TldrawConfigProvider({
12
+ apiUrl,
13
+ saveSnapshot,
14
+ children
15
+ }) {
16
+ const value = useMemo(
17
+ () => ({ apiUrl, saveSnapshot }),
18
+ [apiUrl, saveSnapshot]
19
+ );
20
+ return /* @__PURE__ */ jsx(TldrawConfigContext.Provider, { value, children });
21
+ }
22
+ function useTldrawConfig() {
23
+ return use(TldrawConfigContext);
24
+ }
25
+ const PortalThemeContext = createContext({
26
+ className: ""
27
+ });
28
+ function usePortalTheme() {
29
+ return use(PortalThemeContext);
30
+ }
31
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
32
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
33
+ }).join(" ").trim();
34
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
35
+ const toCamelCase = (string) => string.replace(
36
+ /^([A-Z])|[\s-_]+(\w)/g,
37
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
38
+ );
39
+ const toPascalCase = (string) => {
40
+ const camelCase = toCamelCase(string);
41
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
42
+ };
43
+ var defaultAttributes = {
44
+ xmlns: "http://www.w3.org/2000/svg",
45
+ width: 24,
46
+ height: 24,
47
+ viewBox: "0 0 24 24",
48
+ fill: "none",
49
+ stroke: "currentColor",
50
+ strokeWidth: 2,
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round"
53
+ };
54
+ const hasA11yProp = (props) => {
55
+ for (const prop in props) {
56
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ };
62
+ const Icon = forwardRef(
63
+ ({
64
+ color = "currentColor",
65
+ size = 24,
66
+ strokeWidth = 2,
67
+ absoluteStrokeWidth,
68
+ className = "",
69
+ children,
70
+ iconNode,
71
+ ...rest
72
+ }, ref) => createElement(
73
+ "svg",
74
+ {
75
+ ref,
76
+ ...defaultAttributes,
77
+ width: size,
78
+ height: size,
79
+ stroke: color,
80
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
81
+ className: mergeClasses("lucide", className),
82
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
83
+ ...rest
84
+ },
85
+ [
86
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
87
+ ...Array.isArray(children) ? children : [children]
88
+ ]
89
+ )
90
+ );
91
+ const createLucideIcon = (iconName, iconNode) => {
92
+ const Component2 = forwardRef(
93
+ ({ className, ...props }, ref) => createElement(Icon, {
94
+ ref,
95
+ iconNode,
96
+ className: mergeClasses(
97
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
98
+ `lucide-${iconName}`,
99
+ className
100
+ ),
101
+ ...props
102
+ })
103
+ );
104
+ Component2.displayName = toPascalCase(iconName);
105
+ return Component2;
106
+ };
107
+ const __iconNode$9 = [
108
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1", key: "tgr4d6" }],
109
+ [
110
+ "path",
111
+ {
112
+ d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",
113
+ key: "116196"
114
+ }
115
+ ]
116
+ ];
117
+ const Clipboard = createLucideIcon("clipboard", __iconNode$9);
118
+ const __iconNode$8 = [
119
+ ["path", { d: "M12 15V3", key: "m9g1x1" }],
120
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
121
+ ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
122
+ ];
123
+ const Download = createLucideIcon("download", __iconNode$8);
124
+ const __iconNode$7 = [
125
+ ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
126
+ ["path", { d: "m21 3-7 7", key: "1l2asr" }],
127
+ ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
128
+ ["path", { d: "M9 21H3v-6", key: "wtvkvv" }]
129
+ ];
130
+ const Maximize2 = createLucideIcon("maximize-2", __iconNode$7);
131
+ const __iconNode$6 = [
132
+ [
133
+ "path",
134
+ {
135
+ d: "M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z",
136
+ key: "nt11vn"
137
+ }
138
+ ],
139
+ [
140
+ "path",
141
+ {
142
+ d: "m18 13-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18",
143
+ key: "15qc1e"
144
+ }
145
+ ],
146
+ ["path", { d: "m2.3 2.3 7.286 7.286", key: "1wuzzi" }],
147
+ ["circle", { cx: "11", cy: "11", r: "2", key: "xmgehs" }]
148
+ ];
149
+ const PenTool = createLucideIcon("pen-tool", __iconNode$6);
150
+ const __iconNode$5 = [
151
+ [
152
+ "path",
153
+ {
154
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
155
+ key: "1a8usu"
156
+ }
157
+ ],
158
+ ["path", { d: "m15 5 4 4", key: "1mk7zo" }]
159
+ ];
160
+ const Pencil = createLucideIcon("pencil", __iconNode$5);
161
+ const __iconNode$4 = [
162
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }],
163
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }],
164
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }],
165
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }],
166
+ ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }],
167
+ ["path", { d: "m16 16-1.9-1.9", key: "1dq9hf" }]
168
+ ];
169
+ const ScanSearch = createLucideIcon("scan-search", __iconNode$4);
170
+ const __iconNode$3 = [
171
+ ["path", { d: "M12 3v12", key: "1x0j5s" }],
172
+ ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
173
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
174
+ ];
175
+ const Upload = createLucideIcon("upload", __iconNode$3);
176
+ const __iconNode$2 = [
177
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
178
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
179
+ ];
180
+ const X = createLucideIcon("x", __iconNode$2);
181
+ const __iconNode$1 = [
182
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
183
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }],
184
+ ["line", { x1: "11", x2: "11", y1: "8", y2: "14", key: "1vmskp" }],
185
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]
186
+ ];
187
+ const ZoomIn = createLucideIcon("zoom-in", __iconNode$1);
188
+ const __iconNode = [
189
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
190
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }],
191
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]
192
+ ];
193
+ const ZoomOut = createLucideIcon("zoom-out", __iconNode);
194
+ var tldrawStaticContainer = "_1vqtdre0";
195
+ var tldrawEditorContainer = "_1vqtdre1";
196
+ var tldrawLoading = "_1vqtdre2";
197
+ var tldrawError = "_1vqtdre4";
198
+ var tldrawActionGroup = "_1vqtdre5";
199
+ var tldrawActionButton = "_1vqtdre6";
200
+ var tldrawFullscreenPopup = "_1vqtdre7";
201
+ var tldrawDialogHeader = "_1vqtdre8";
202
+ var tldrawStatusDot = "_1vqtdre9";
203
+ var tldrawDialogTitle = "_1vqtdrea";
204
+ var tldrawDialogMeta = "_1vqtdreb";
205
+ var tldrawHeaderActions = "_1vqtdrec";
206
+ var tldrawHeaderClose = "_1vqtdred";
207
+ var tldrawActionBarBtn = "_1vqtdree";
208
+ var tldrawActionBarSep = "_1vqtdref";
209
+ var tldrawActionBarUrl = "_1vqtdreg";
210
+ var tldrawDialogCanvas = "_1vqtdreh";
211
+ var tldrawConfirmActions = "_1vqtdrei";
212
+ var tldrawConfirmBtn = "_1vqtdrej";
213
+ var tldrawConfirmBtnPrimary = "_1vqtdrek";
214
+ var tldrawConfirmBtnDanger = "_1vqtdrel";
215
+ var tldrawEditOverlay = "_1vqtdrem";
216
+ var tldrawEditLabel = "_1vqtdren";
217
+ function useTldrawData(data) {
218
+ const { apiUrl } = useTldrawConfig();
219
+ const parsed = useMemo(() => {
220
+ if (!data) return { type: "empty" };
221
+ try {
222
+ const json = JSON.parse(data);
223
+ if (json && typeof json === "object") {
224
+ return {
225
+ type: "inline",
226
+ snapshot: json
227
+ };
228
+ }
229
+ } catch {
230
+ }
231
+ const firstLine = data.split("\n")[0].trim();
232
+ let fetchUrl2;
233
+ if (firstLine.startsWith("http")) {
234
+ fetchUrl2 = firstLine;
235
+ } else if (firstLine.startsWith("ref:") && apiUrl) {
236
+ fetchUrl2 = `${apiUrl}/objects/${firstLine.slice(4)}`;
237
+ }
238
+ if (fetchUrl2) {
239
+ return { type: "remote", fetchUrl: fetchUrl2 };
240
+ }
241
+ return {
242
+ type: "error",
243
+ error: firstLine.startsWith("ref:") ? "Missing apiUrl for ref resolution" : "Unrecognized snapshot format"
244
+ };
245
+ }, [data, apiUrl]);
246
+ const fetchUrl = parsed.type === "remote" ? parsed.fetchUrl : void 0;
247
+ const [remoteSnapshot, setRemoteSnapshot] = useState();
248
+ const [loading, setLoading] = useState(!!fetchUrl);
249
+ const [error, setError] = useState("");
250
+ useEffect(() => {
251
+ if (!fetchUrl) return;
252
+ let cancelled = false;
253
+ setLoading(true);
254
+ setError("");
255
+ fetch(fetchUrl).then((res) => {
256
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
257
+ return res.json();
258
+ }).then((json) => {
259
+ if (!cancelled) {
260
+ setRemoteSnapshot(json);
261
+ setLoading(false);
262
+ }
263
+ }).catch((err) => {
264
+ if (!cancelled) {
265
+ setError(err instanceof Error ? err.message : "Failed to load");
266
+ setLoading(false);
267
+ }
268
+ });
269
+ return () => {
270
+ cancelled = true;
271
+ };
272
+ }, [fetchUrl]);
273
+ if (parsed.type === "inline") {
274
+ return { snapshot: parsed.snapshot, loading: false, error: "" };
275
+ }
276
+ if (parsed.type === "remote") {
277
+ return { snapshot: remoteSnapshot, loading, error };
278
+ }
279
+ if (parsed.type === "error") {
280
+ return { snapshot: void 0, loading: false, error: parsed.error };
281
+ }
282
+ return { snapshot: void 0, loading: false, error: "" };
283
+ }
284
+ const SAVE_DEBOUNCE_MS = 2e3;
285
+ const TldrawEditorDialogContent = ({
286
+ dismiss,
287
+ TldrawComponent,
288
+ initialData,
289
+ initialSnapshot,
290
+ theme,
291
+ onSave,
292
+ saveSnapshot,
293
+ onClose
294
+ }) => {
295
+ const { className: portalClassName } = usePortalTheme();
296
+ const editorRef = useRef(null);
297
+ const saveTimerRef = useRef(void 0);
298
+ const [isDirty, setIsDirty] = useState(false);
299
+ const isDirtyRef = useRef(false);
300
+ const [storageMode, setStorageMode] = useState(() => {
301
+ if (!saveSnapshot || !initialSnapshot || !initialSnapshot.trim())
302
+ return "inline";
303
+ try {
304
+ JSON.parse(initialSnapshot);
305
+ return "inline";
306
+ } catch {
307
+ return "remote";
308
+ }
309
+ });
310
+ const storageModeRef = useRef(storageMode);
311
+ const [savedRef, setSavedRef] = useState(() => {
312
+ if (!saveSnapshot || !initialSnapshot || !initialSnapshot.trim()) return "";
313
+ try {
314
+ JSON.parse(initialSnapshot);
315
+ return "";
316
+ } catch {
317
+ return initialSnapshot;
318
+ }
319
+ });
320
+ const [isSaving, setIsSaving] = useState(false);
321
+ useEffect(() => {
322
+ return () => {
323
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
324
+ };
325
+ }, []);
326
+ const performSave = useCallback(() => {
327
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
328
+ const editor = editorRef.current;
329
+ if (!editor) return Promise.resolve();
330
+ try {
331
+ const doc = editor.store.getSnapshot();
332
+ if (storageModeRef.current === "remote" && saveSnapshot) {
333
+ setIsSaving(true);
334
+ return saveSnapshot(doc).then(
335
+ (ref) => {
336
+ onSave(ref);
337
+ isDirtyRef.current = false;
338
+ setIsDirty(false);
339
+ setIsSaving(false);
340
+ setSavedRef(ref);
341
+ },
342
+ () => {
343
+ setIsSaving(false);
344
+ }
345
+ );
346
+ } else {
347
+ onSave(JSON.stringify(doc));
348
+ isDirtyRef.current = false;
349
+ setIsDirty(false);
350
+ }
351
+ } catch {
352
+ }
353
+ return Promise.resolve();
354
+ }, [onSave, saveSnapshot]);
355
+ const handleMount = useCallback(
356
+ (editor) => {
357
+ editorRef.current = editor;
358
+ editor.user.updateUserPreferences({ colorScheme: theme });
359
+ editor.store.listen(
360
+ () => {
361
+ isDirtyRef.current = true;
362
+ setIsDirty(true);
363
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
364
+ saveTimerRef.current = setTimeout(
365
+ () => performSave(),
366
+ SAVE_DEBOUNCE_MS
367
+ );
368
+ },
369
+ { scope: "document" }
370
+ );
371
+ },
372
+ [theme, performSave]
373
+ );
374
+ const handleSaveAndClose = useCallback(() => {
375
+ void performSave().then(() => {
376
+ const editor = editorRef.current;
377
+ onClose(editor?.store.getSnapshot());
378
+ dismiss();
379
+ });
380
+ }, [performSave, onClose, dismiss]);
381
+ const handleDiscard = useCallback(() => {
382
+ if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
383
+ dismiss();
384
+ }, [dismiss]);
385
+ const showConfirmDialog = useCallback(() => {
386
+ presentDialog({
387
+ title: "Unsaved Changes",
388
+ description: "You have unsaved changes that will be lost if you close now.",
389
+ content: ({ dismiss: dismissConfirm }) => /* @__PURE__ */ jsxs("div", { className: tldrawConfirmActions, children: [
390
+ /* @__PURE__ */ jsx(
391
+ "button",
392
+ {
393
+ type: "button",
394
+ className: tldrawConfirmBtn,
395
+ onClick: dismissConfirm,
396
+ children: "Continue Editing"
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx(
400
+ "button",
401
+ {
402
+ type: "button",
403
+ className: tldrawConfirmBtnDanger,
404
+ onClick: () => {
405
+ dismissConfirm();
406
+ handleDiscard();
407
+ },
408
+ children: "Discard"
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsx(
412
+ "button",
413
+ {
414
+ type: "button",
415
+ className: tldrawConfirmBtnPrimary,
416
+ onClick: () => {
417
+ dismissConfirm();
418
+ handleSaveAndClose();
419
+ },
420
+ children: "Save & Close"
421
+ }
422
+ )
423
+ ] }),
424
+ portalClassName,
425
+ showCloseButton: false,
426
+ clickOutsideToDismiss: false
427
+ });
428
+ }, [handleDiscard, handleSaveAndClose, portalClassName]);
429
+ useEffect(() => {
430
+ const handleKeyDown = (e) => {
431
+ if (e.key === "Escape") {
432
+ e.preventDefault();
433
+ e.stopImmediatePropagation();
434
+ if (isDirtyRef.current) {
435
+ showConfirmDialog();
436
+ } else {
437
+ dismiss();
438
+ }
439
+ }
440
+ };
441
+ document.addEventListener("keydown", handleKeyDown, true);
442
+ return () => document.removeEventListener("keydown", handleKeyDown, true);
443
+ }, [dismiss, showConfirmDialog]);
444
+ const attemptClose = useCallback(() => {
445
+ if (isDirtyRef.current) {
446
+ showConfirmDialog();
447
+ } else {
448
+ dismiss();
449
+ }
450
+ }, [dismiss, showConfirmDialog]);
451
+ const handleManualUpload = useCallback(() => {
452
+ performSave();
453
+ }, [performSave]);
454
+ const handleExportJson = useCallback(() => {
455
+ const editor = editorRef.current;
456
+ if (!editor) return;
457
+ const doc = editor.store.getSnapshot();
458
+ const blob = new Blob([JSON.stringify(doc, null, 2)], {
459
+ type: "application/json"
460
+ });
461
+ const url = URL.createObjectURL(blob);
462
+ const a = document.createElement("a");
463
+ a.href = url;
464
+ a.download = "tldraw-snapshot.json";
465
+ a.click();
466
+ URL.revokeObjectURL(url);
467
+ }, []);
468
+ const handleCopyJson = useCallback(() => {
469
+ const editor = editorRef.current;
470
+ if (!editor) return;
471
+ const doc = editor.store.getSnapshot();
472
+ void navigator.clipboard.writeText(JSON.stringify(doc));
473
+ }, []);
474
+ const handleCopyRef = useCallback(() => {
475
+ if (savedRef) void navigator.clipboard.writeText(savedRef);
476
+ }, [savedRef]);
477
+ const handleStorageModeChange = useCallback(
478
+ (newMode) => {
479
+ setStorageMode(newMode);
480
+ storageModeRef.current = newMode;
481
+ if (newMode === "inline") setSavedRef("");
482
+ performSave();
483
+ },
484
+ [performSave]
485
+ );
486
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
487
+ /* @__PURE__ */ jsxs("div", { className: tldrawDialogHeader, children: [
488
+ /* @__PURE__ */ jsx(
489
+ "span",
490
+ {
491
+ className: tldrawStatusDot,
492
+ style: { backgroundColor: isDirty ? "#f59e0b" : "#22c55e" }
493
+ }
494
+ ),
495
+ /* @__PURE__ */ jsx("span", { className: tldrawDialogTitle, children: "Canvas Editor" }),
496
+ /* @__PURE__ */ jsx("span", { className: tldrawDialogMeta, children: "tldraw" }),
497
+ /* @__PURE__ */ jsxs("div", { className: tldrawHeaderActions, children: [
498
+ saveSnapshot && /* @__PURE__ */ jsxs(Fragment, { children: [
499
+ /* @__PURE__ */ jsx(
500
+ SegmentedControl,
501
+ {
502
+ items: [
503
+ { value: "inline", label: "Inline" },
504
+ { value: "remote", label: "Remote" }
505
+ ],
506
+ value: storageMode,
507
+ onChange: handleStorageModeChange
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsx("div", { className: tldrawActionBarSep }),
511
+ /* @__PURE__ */ jsxs(
512
+ "button",
513
+ {
514
+ type: "button",
515
+ className: tldrawActionBarBtn,
516
+ onClick: handleManualUpload,
517
+ disabled: isSaving,
518
+ children: [
519
+ /* @__PURE__ */ jsx(Upload, { size: 14 }),
520
+ /* @__PURE__ */ jsx("span", { children: isSaving ? "Saving..." : "Save" })
521
+ ]
522
+ }
523
+ ),
524
+ /* @__PURE__ */ jsx("div", { className: tldrawActionBarSep })
525
+ ] }),
526
+ /* @__PURE__ */ jsxs(
527
+ "button",
528
+ {
529
+ type: "button",
530
+ className: tldrawActionBarBtn,
531
+ onClick: handleExportJson,
532
+ children: [
533
+ /* @__PURE__ */ jsx(Download, { size: 14 }),
534
+ "JSON"
535
+ ]
536
+ }
537
+ ),
538
+ /* @__PURE__ */ jsxs(
539
+ "button",
540
+ {
541
+ type: "button",
542
+ className: tldrawActionBarBtn,
543
+ onClick: handleCopyJson,
544
+ children: [
545
+ /* @__PURE__ */ jsx(Clipboard, { size: 14 }),
546
+ "Copy"
547
+ ]
548
+ }
549
+ ),
550
+ storageMode === "remote" && savedRef && /* @__PURE__ */ jsxs(Fragment, { children: [
551
+ /* @__PURE__ */ jsx("div", { className: tldrawActionBarSep }),
552
+ /* @__PURE__ */ jsx(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ className: tldrawActionBarUrl,
557
+ onClick: handleCopyRef,
558
+ title: savedRef,
559
+ children: savedRef
560
+ }
561
+ )
562
+ ] })
563
+ ] }),
564
+ /* @__PURE__ */ jsx(
565
+ "button",
566
+ {
567
+ type: "button",
568
+ className: tldrawHeaderClose,
569
+ onClick: attemptClose,
570
+ children: /* @__PURE__ */ jsx(X, { size: 18 })
571
+ }
572
+ )
573
+ ] }),
574
+ /* @__PURE__ */ jsx("div", { className: tldrawDialogCanvas, children: /* @__PURE__ */ jsx(TldrawComponent, { snapshot: initialData, onMount: handleMount }) })
575
+ ] });
576
+ };
577
+ const TldrawEditRenderer = ({
578
+ snapshot,
579
+ onSnapshotChange
580
+ }) => {
581
+ const theme = useColorScheme();
582
+ const { saveSnapshot } = useTldrawConfig();
583
+ const {
584
+ snapshot: initialData,
585
+ loading: dataLoading,
586
+ error: dataError
587
+ } = useTldrawData(snapshot);
588
+ const [TldrawComponent, setTldrawComponent] = useState(null);
589
+ const [libLoading, setLibLoading] = useState(true);
590
+ const previewEditorRef = useRef(null);
591
+ const initialDataRef = useRef(void 0);
592
+ const onSnapshotChangeRef = useRef(onSnapshotChange);
593
+ onSnapshotChangeRef.current = onSnapshotChange;
594
+ const saveSnapshotRef = useRef(saveSnapshot);
595
+ saveSnapshotRef.current = saveSnapshot;
596
+ const snapshotRef = useRef(snapshot);
597
+ snapshotRef.current = snapshot;
598
+ const { className: portalClassName } = usePortalTheme();
599
+ useEffect(() => {
600
+ if (initialData && !initialDataRef.current) {
601
+ initialDataRef.current = initialData;
602
+ }
603
+ }, [initialData]);
604
+ useEffect(() => {
605
+ import("tldraw").then((mod) => {
606
+ setTldrawComponent(() => mod.Tldraw);
607
+ setLibLoading(false);
608
+ }).catch(() => setLibLoading(false));
609
+ }, []);
610
+ useEffect(() => {
611
+ if (previewEditorRef.current?.user?.updateUserPreferences) {
612
+ previewEditorRef.current.user.updateUserPreferences({
613
+ colorScheme: theme
614
+ });
615
+ }
616
+ }, [theme]);
617
+ const handlePreviewMount = useCallback(
618
+ (editor) => {
619
+ previewEditorRef.current = editor;
620
+ editor.updateInstanceState({ isReadonly: true });
621
+ editor.setCurrentTool("hand");
622
+ editor.user.updateUserPreferences({ colorScheme: theme });
623
+ setTimeout(() => editor.zoomToFit({ animate: false }), 100);
624
+ },
625
+ [theme]
626
+ );
627
+ const handleOpenEditor = useCallback(() => {
628
+ if (!TldrawComponent) return;
629
+ presentDialog({
630
+ content: ({ dismiss }) => /* @__PURE__ */ jsx(
631
+ TldrawEditorDialogContent,
632
+ {
633
+ dismiss,
634
+ TldrawComponent,
635
+ initialData: initialDataRef.current,
636
+ initialSnapshot: snapshotRef.current,
637
+ theme,
638
+ onSave: (ref) => onSnapshotChangeRef.current(ref),
639
+ saveSnapshot: saveSnapshotRef.current,
640
+ onClose: (doc) => {
641
+ if (doc) initialDataRef.current = doc;
642
+ }
643
+ }
644
+ ),
645
+ className: tldrawFullscreenPopup,
646
+ portalClassName,
647
+ showCloseButton: false,
648
+ clickOutsideToDismiss: false
649
+ });
650
+ }, [TldrawComponent, theme, portalClassName]);
651
+ const loading = dataLoading || libLoading;
652
+ if (loading || !TldrawComponent) {
653
+ return /* @__PURE__ */ jsx("div", { className: tldrawEditorContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawLoading, children: "Loading tldraw editor..." }) });
654
+ }
655
+ if (dataError) {
656
+ return /* @__PURE__ */ jsx("div", { className: tldrawEditorContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawError, children: dataError }) });
657
+ }
658
+ return /* @__PURE__ */ jsxs("div", { className: tldrawEditorContainer, children: [
659
+ /* @__PURE__ */ jsx(
660
+ TldrawComponent,
661
+ {
662
+ snapshot: initialDataRef.current,
663
+ hideUi: true,
664
+ onMount: handlePreviewMount
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsx(
668
+ "button",
669
+ {
670
+ type: "button",
671
+ className: tldrawEditOverlay,
672
+ onClick: handleOpenEditor,
673
+ children: /* @__PURE__ */ jsxs("span", { className: tldrawEditLabel, children: [
674
+ /* @__PURE__ */ jsx(Pencil, { size: 16 }),
675
+ "Edit Whiteboard"
676
+ ] })
677
+ }
678
+ )
679
+ ] });
680
+ };
681
+ const TldrawEditRenderer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
682
+ __proto__: null,
683
+ TldrawEditRenderer
684
+ }, Symbol.toStringTag, { value: "Module" }));
685
+ const LazyStaticRenderer = lazy(
686
+ () => Promise.resolve().then(() => TldrawStaticRenderer$1).then((m) => ({
687
+ default: m.TldrawStaticRenderer
688
+ }))
689
+ );
690
+ const LazyEditRenderer = lazy(
691
+ () => Promise.resolve().then(() => TldrawEditRenderer$1).then((m) => ({
692
+ default: m.TldrawEditRenderer
693
+ }))
694
+ );
695
+ const _TldrawNode = class _TldrawNode extends DecoratorNode {
696
+ constructor(snapshot, key) {
697
+ super(key);
698
+ __publicField(this, "__snapshot");
699
+ this.__snapshot = snapshot;
700
+ }
701
+ static getType() {
702
+ return "tldraw";
703
+ }
704
+ static clone(node) {
705
+ return new _TldrawNode(node.__snapshot, node.__key);
706
+ }
707
+ createDOM(_config) {
708
+ const div = document.createElement("div");
709
+ div.className = "rich-tldraw-wrapper";
710
+ return div;
711
+ }
712
+ updateDOM() {
713
+ return false;
714
+ }
715
+ isInline() {
716
+ return false;
717
+ }
718
+ static importJSON(serializedNode) {
719
+ return $createTldrawNode(serializedNode.snapshot);
720
+ }
721
+ exportJSON() {
722
+ return {
723
+ ...super.exportJSON(),
724
+ type: "tldraw",
725
+ snapshot: this.__snapshot,
726
+ version: 1
727
+ };
728
+ }
729
+ getSnapshot() {
730
+ return this.__snapshot;
731
+ }
732
+ setSnapshot(snapshot) {
733
+ const writable = this.getWritable();
734
+ writable.__snapshot = snapshot;
735
+ }
736
+ decorate(editor, _config) {
737
+ const nodeKey = this.__key;
738
+ const isEditable = editor.isEditable();
739
+ const fallback = createElement("div", {
740
+ className: "rich-tldraw-loading",
741
+ style: {
742
+ position: "relative",
743
+ width: "100%",
744
+ aspectRatio: "16 / 10",
745
+ margin: "1rem 0"
746
+ }
747
+ });
748
+ if (isEditable) {
749
+ return createElement(
750
+ Suspense,
751
+ { fallback },
752
+ createElement(LazyEditRenderer, {
753
+ snapshot: this.__snapshot,
754
+ onSnapshotChange: (newSnapshot) => {
755
+ editor.update(() => {
756
+ const node = $getNodeByKey(nodeKey);
757
+ if (node) {
758
+ node.setSnapshot(newSnapshot);
759
+ }
760
+ });
761
+ }
762
+ })
763
+ );
764
+ }
765
+ return createElement(
766
+ Suspense,
767
+ { fallback },
768
+ createElement(LazyStaticRenderer, { snapshot: this.__snapshot })
769
+ );
770
+ }
771
+ };
772
+ __publicField(_TldrawNode, "slashMenuItems", [
773
+ {
774
+ title: "Whiteboard",
775
+ icon: createElement(PenTool, { size: 20 }),
776
+ description: "Tldraw whiteboard canvas",
777
+ keywords: ["tldraw", "whiteboard", "draw", "canvas"],
778
+ section: "MEDIA",
779
+ onSelect: (editor) => {
780
+ editor.update(() => {
781
+ $insertNodes([$createTldrawNode("{}")]);
782
+ });
783
+ }
784
+ }
785
+ ]);
786
+ let TldrawNode = _TldrawNode;
787
+ function $createTldrawNode(snapshot) {
788
+ return new TldrawNode(snapshot);
789
+ }
790
+ function $isTldrawNode(node) {
791
+ return node instanceof TldrawNode;
792
+ }
793
+ const INSERT_TLDRAW_COMMAND = createCommand("INSERT_TLDRAW");
794
+ function TldrawPlugin() {
795
+ const [editor] = useLexicalComposerContext();
796
+ useEffect(() => {
797
+ return editor.registerCommand(
798
+ INSERT_TLDRAW_COMMAND,
799
+ (snapshot) => {
800
+ const node = $createTldrawNode(snapshot);
801
+ $insertNodes([node]);
802
+ return true;
803
+ },
804
+ COMMAND_PRIORITY_EDITOR
805
+ );
806
+ }, [editor]);
807
+ return null;
808
+ }
809
+ const TldrawStaticRenderer = ({
810
+ snapshot
811
+ }) => {
812
+ const theme = useColorScheme();
813
+ const containerRef = useRef(null);
814
+ const [hasEnteredView, setHasEnteredView] = useState(false);
815
+ useEffect(() => {
816
+ if (hasEnteredView) return;
817
+ const target = containerRef.current;
818
+ if (!target) return;
819
+ const observer = new IntersectionObserver(
820
+ (entries) => {
821
+ if (entries.some((e) => e.isIntersecting)) {
822
+ setHasEnteredView(true);
823
+ observer.disconnect();
824
+ }
825
+ },
826
+ { rootMargin: "200px 0px", threshold: 0.1 }
827
+ );
828
+ observer.observe(target);
829
+ return () => observer.disconnect();
830
+ }, [hasEnteredView]);
831
+ if (!hasEnteredView) {
832
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: tldrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawLoading, children: "Loading tldraw..." }) });
833
+ }
834
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, children: /* @__PURE__ */ jsx(TldrawStaticCanvas, { snapshot, theme }) });
835
+ };
836
+ const TldrawExpandContent = ({ dismiss, TldrawComponent, data, theme }) => /* @__PURE__ */ jsxs(Fragment, { children: [
837
+ /* @__PURE__ */ jsxs("div", { className: tldrawDialogHeader, children: [
838
+ /* @__PURE__ */ jsx("span", { className: tldrawDialogTitle, children: "Whiteboard" }),
839
+ /* @__PURE__ */ jsx("span", { className: tldrawDialogMeta, children: "tldraw" }),
840
+ /* @__PURE__ */ jsx("button", { type: "button", className: tldrawHeaderClose, onClick: dismiss, children: /* @__PURE__ */ jsx(X, { size: 18 }) })
841
+ ] }),
842
+ /* @__PURE__ */ jsx("div", { className: tldrawDialogCanvas, children: /* @__PURE__ */ jsx(
843
+ TldrawComponent,
844
+ {
845
+ snapshot: data,
846
+ hideUi: true,
847
+ onMount: (editor) => {
848
+ editor.updateInstanceState({ isReadonly: true });
849
+ editor.setCurrentTool("hand");
850
+ editor.user.updateUserPreferences({ colorScheme: theme });
851
+ setTimeout(() => editor.zoomToFit({ animate: false }), 100);
852
+ }
853
+ }
854
+ ) })
855
+ ] });
856
+ const TldrawStaticCanvas = ({ snapshot, theme }) => {
857
+ const {
858
+ snapshot: data,
859
+ loading: dataLoading,
860
+ error: dataError
861
+ } = useTldrawData(snapshot);
862
+ const [TldrawComponent, setTldrawComponent] = useState(null);
863
+ const [libLoading, setLibLoading] = useState(true);
864
+ const editorRef = useRef(null);
865
+ const { className: portalClassName } = usePortalTheme();
866
+ useEffect(() => {
867
+ import("tldraw").then((mod) => {
868
+ setTldrawComponent(() => mod.Tldraw);
869
+ setLibLoading(false);
870
+ }).catch(() => setLibLoading(false));
871
+ }, []);
872
+ useEffect(() => {
873
+ if (editorRef.current?.user?.updateUserPreferences) {
874
+ editorRef.current.user.updateUserPreferences({ colorScheme: theme });
875
+ }
876
+ }, [theme]);
877
+ const handleMount = useCallback(
878
+ (editor) => {
879
+ editorRef.current = editor;
880
+ editor.updateInstanceState({ isReadonly: true });
881
+ editor.setCurrentTool("hand");
882
+ editor.user.updateUserPreferences({ colorScheme: theme });
883
+ setTimeout(() => {
884
+ editor.zoomToFit({ animate: false });
885
+ }, 100);
886
+ },
887
+ [theme]
888
+ );
889
+ const handleExpand = useCallback(() => {
890
+ if (!TldrawComponent || !data) return;
891
+ presentDialog({
892
+ content: ({ dismiss }) => /* @__PURE__ */ jsx(
893
+ TldrawExpandContent,
894
+ {
895
+ dismiss,
896
+ TldrawComponent,
897
+ data,
898
+ theme
899
+ }
900
+ ),
901
+ className: tldrawFullscreenPopup,
902
+ portalClassName,
903
+ showCloseButton: false,
904
+ clickOutsideToDismiss: true
905
+ });
906
+ }, [TldrawComponent, data, theme, portalClassName]);
907
+ const loading = dataLoading || libLoading;
908
+ if (loading) {
909
+ return /* @__PURE__ */ jsx("div", { className: tldrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawLoading, children: "Loading tldraw..." }) });
910
+ }
911
+ if (dataError || !data) {
912
+ return /* @__PURE__ */ jsx("div", { className: tldrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawError, children: dataError || "No data" }) });
913
+ }
914
+ if (!TldrawComponent) {
915
+ return /* @__PURE__ */ jsx("div", { className: tldrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: tldrawError, children: "Failed to load tldraw" }) });
916
+ }
917
+ return /* @__PURE__ */ jsxs(
918
+ "div",
919
+ {
920
+ className: tldrawStaticContainer,
921
+ "data-theme": theme,
922
+ "data-color-scheme": theme,
923
+ children: [
924
+ /* @__PURE__ */ jsx(
925
+ TldrawErrorBoundary,
926
+ {
927
+ fallback: /* @__PURE__ */ jsx("div", { className: tldrawError, children: "Failed to render tldraw" }),
928
+ children: /* @__PURE__ */ jsx(TldrawComponent, { snapshot: data, hideUi: true, onMount: handleMount })
929
+ }
930
+ ),
931
+ /* @__PURE__ */ jsxs("div", { className: tldrawActionGroup, children: [
932
+ /* @__PURE__ */ jsx(
933
+ "button",
934
+ {
935
+ type: "button",
936
+ className: tldrawActionButton,
937
+ title: "Zoom In",
938
+ onClick: () => editorRef.current?.zoomIn(),
939
+ children: /* @__PURE__ */ jsx(ZoomIn, { size: 20 })
940
+ }
941
+ ),
942
+ /* @__PURE__ */ jsx(
943
+ "button",
944
+ {
945
+ type: "button",
946
+ className: tldrawActionButton,
947
+ title: "Zoom Out",
948
+ onClick: () => editorRef.current?.zoomOut(),
949
+ children: /* @__PURE__ */ jsx(ZoomOut, { size: 20 })
950
+ }
951
+ ),
952
+ /* @__PURE__ */ jsx(
953
+ "button",
954
+ {
955
+ type: "button",
956
+ className: tldrawActionButton,
957
+ title: "Fit to Content",
958
+ onClick: () => editorRef.current?.zoomToFit(),
959
+ children: /* @__PURE__ */ jsx(ScanSearch, { size: 20 })
960
+ }
961
+ ),
962
+ /* @__PURE__ */ jsx(
963
+ "button",
964
+ {
965
+ type: "button",
966
+ className: tldrawActionButton,
967
+ title: "Expand",
968
+ onClick: handleExpand,
969
+ children: /* @__PURE__ */ jsx(Maximize2, { size: 20 })
970
+ }
971
+ )
972
+ ] })
973
+ ]
974
+ }
975
+ );
976
+ };
977
+ class TldrawErrorBoundary extends Component {
978
+ constructor() {
979
+ super(...arguments);
980
+ __publicField(this, "state", { hasError: false });
981
+ }
982
+ static getDerivedStateFromError() {
983
+ return { hasError: true };
984
+ }
985
+ render() {
986
+ return this.state.hasError ? this.props.fallback : this.props.children;
987
+ }
988
+ }
989
+ const TldrawStaticRenderer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
990
+ __proto__: null,
991
+ TldrawStaticRenderer
992
+ }, Symbol.toStringTag, { value: "Module" }));
993
+ export {
994
+ $createTldrawNode,
995
+ $isTldrawNode,
996
+ INSERT_TLDRAW_COMMAND,
997
+ TldrawConfigProvider,
998
+ TldrawEditRenderer,
999
+ TldrawNode,
1000
+ TldrawPlugin,
1001
+ TldrawStaticRenderer as TldrawRenderer,
1002
+ TldrawStaticRenderer,
1003
+ useTldrawConfig
1004
+ };