@haklex/rich-ext-excalidraw 0.0.82 → 0.0.84

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.
@@ -0,0 +1,480 @@
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 { useColorScheme } from "@haklex/rich-editor";
6
+ import { presentDialog, ViewportGate } from "@haklex/rich-editor-ui";
7
+ import { usePortalTheme } from "@haklex/rich-style-token";
8
+ import { ZoomIn, ZoomOut, ScanSearch, Maximize2, X } from "lucide-react";
9
+ import { createContext, use, useMemo, useState, useEffect, useRef, useCallback, Component, lazy, Suspense, createElement } from "react";
10
+ import { DecoratorNode } from "lexical";
11
+ const ExcalidrawConfigContext = createContext({});
12
+ function ExcalidrawConfigProvider({
13
+ apiUrl,
14
+ saveSnapshot,
15
+ children
16
+ }) {
17
+ const value = useMemo(
18
+ () => ({ apiUrl, saveSnapshot }),
19
+ [apiUrl, saveSnapshot]
20
+ );
21
+ return /* @__PURE__ */ jsx(ExcalidrawConfigContext.Provider, { value, children });
22
+ }
23
+ function useExcalidrawConfig() {
24
+ return use(ExcalidrawConfigContext);
25
+ }
26
+ const readonlyUIOptions = {
27
+ canvasActions: {
28
+ toggleTheme: false,
29
+ export: false,
30
+ saveAsImage: false,
31
+ loadScene: false,
32
+ changeViewBackgroundColor: false
33
+ }
34
+ };
35
+ var excalidrawStaticContainer = "_1c3wdzl0";
36
+ var excalidrawEditorContainer = "_1c3wdzl1";
37
+ var excalidrawPlaceholder = "_1c3wdzl2";
38
+ var excalidrawLoading = "_1c3wdzl3";
39
+ var excalidrawError = "_1c3wdzl5";
40
+ var excalidrawActionGroup = "_1c3wdzl6";
41
+ var excalidrawActionButton = "_1c3wdzl7";
42
+ var excalidrawFullscreenPopup = "_1c3wdzl8";
43
+ var excalidrawDialogHeader = "_1c3wdzl9";
44
+ var excalidrawDialogHeaderTitle = "_1c3wdzla";
45
+ var excalidrawStatusDot = "_1c3wdzlb";
46
+ var excalidrawDialogTitle = "_1c3wdzlc";
47
+ var excalidrawDialogMeta = "_1c3wdzld";
48
+ var excalidrawHeaderActions = "_1c3wdzle";
49
+ var excalidrawHeaderClose = "_1c3wdzlf";
50
+ var excalidrawActionBarBtn = "_1c3wdzlg";
51
+ var excalidrawActionBarSep = "_1c3wdzlh";
52
+ var excalidrawActionBarUrl = "_1c3wdzli";
53
+ var excalidrawDialogCanvas = "_1c3wdzlj";
54
+ var excalidrawConfirmActions = "_1c3wdzlk";
55
+ var excalidrawConfirmBtn = "_1c3wdzll";
56
+ var excalidrawConfirmBtnPrimary = "_1c3wdzlm";
57
+ var excalidrawConfirmBtnDanger = "_1c3wdzln";
58
+ var excalidrawEditOverlay = "_1c3wdzlo";
59
+ var excalidrawEditLabel = "_1c3wdzlp";
60
+ function parseSnapshot(raw) {
61
+ if (!raw || !raw.trim()) return null;
62
+ try {
63
+ const json = JSON.parse(raw);
64
+ if (json && typeof json === "object") return { type: "inline", data: json };
65
+ } catch {
66
+ }
67
+ const lines = raw.split("\n");
68
+ const firstLine = lines[0].trim();
69
+ if (!firstLine.startsWith("http") && !firstLine.startsWith("blob:") && !firstLine.startsWith("ref:"))
70
+ return null;
71
+ const remaining = lines.slice(1).join("\n").trim();
72
+ if (remaining) {
73
+ try {
74
+ const delta = JSON.parse(remaining);
75
+ if (delta && typeof delta === "object") return { type: "delta", baseUrl: firstLine, delta };
76
+ } catch {
77
+ }
78
+ }
79
+ return { type: "remote", url: firstLine };
80
+ }
81
+ function serializeSnapshot(snapshot) {
82
+ switch (snapshot.type) {
83
+ case "inline": {
84
+ return JSON.stringify(snapshot.data);
85
+ }
86
+ case "remote": {
87
+ return snapshot.url;
88
+ }
89
+ case "delta": {
90
+ return [snapshot.baseUrl, JSON.stringify(snapshot.delta)].join("\n");
91
+ }
92
+ }
93
+ }
94
+ function resolveUrl(url, apiUrl) {
95
+ const refLine = url;
96
+ if (url.startsWith("http") || url.startsWith("blob:")) {
97
+ return { fetchUrl: url, refLine };
98
+ }
99
+ if (url.startsWith("ref:") && apiUrl) {
100
+ return { fetchUrl: `${apiUrl}/objects/${url.slice(4)}`, refLine };
101
+ }
102
+ return {
103
+ error: url.startsWith("ref:") ? "Missing apiUrl for ref resolution" : "Unrecognized snapshot format"
104
+ };
105
+ }
106
+ function typedToParsed(typed, apiUrl) {
107
+ switch (typed.type) {
108
+ case "inline": {
109
+ return { type: "inline", snapshot: typed.data };
110
+ }
111
+ case "remote": {
112
+ const result = resolveUrl(typed.url, apiUrl);
113
+ if ("error" in result) return { type: "error", error: result.error };
114
+ return {
115
+ type: "remote",
116
+ fetchUrl: result.fetchUrl,
117
+ refLine: result.refLine
118
+ };
119
+ }
120
+ case "delta": {
121
+ const result = resolveUrl(typed.baseUrl, apiUrl);
122
+ if ("error" in result) return { type: "error", error: result.error };
123
+ return {
124
+ type: "incremental",
125
+ fetchUrl: result.fetchUrl,
126
+ refLine: result.refLine,
127
+ delta: typed.delta
128
+ };
129
+ }
130
+ }
131
+ }
132
+ function stringToParsed(data, apiUrl) {
133
+ const typed = parseSnapshot(data);
134
+ if (!typed) return { type: "empty" };
135
+ return typedToParsed(typed, apiUrl);
136
+ }
137
+ function useExcalidrawData(data) {
138
+ const { apiUrl } = useExcalidrawConfig();
139
+ const parsed = useMemo(() => {
140
+ if (data === null || data === void 0) return { type: "empty" };
141
+ if (typeof data === "string") return stringToParsed(data, apiUrl);
142
+ return typedToParsed(data, apiUrl);
143
+ }, [data, apiUrl]);
144
+ const [remoteSnapshot, setRemoteSnapshot] = useState();
145
+ const [baseData, setBaseData] = useState();
146
+ const [loading, setLoading] = useState(parsed.type === "remote" || parsed.type === "incremental");
147
+ const [error, setError] = useState("");
148
+ useEffect(() => {
149
+ if (parsed.type !== "remote" && parsed.type !== "incremental") return;
150
+ const { fetchUrl } = parsed;
151
+ const delta = parsed.type === "incremental" ? parsed.delta : void 0;
152
+ let cancelled = false;
153
+ setLoading(true);
154
+ setError("");
155
+ fetch(fetchUrl).then((res) => {
156
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
157
+ return res.json();
158
+ }).then(async (json) => {
159
+ if (cancelled) return;
160
+ setBaseData(json);
161
+ const deltaKeys = delta ? Object.keys(delta) : [];
162
+ if (deltaKeys.length > 0) {
163
+ const { patch } = await import("jsondiffpatch");
164
+ if (cancelled) return;
165
+ const patched = patch(structuredClone(json), delta);
166
+ setRemoteSnapshot(patched);
167
+ } else {
168
+ setRemoteSnapshot(json);
169
+ }
170
+ setLoading(false);
171
+ }).catch((err) => {
172
+ if (!cancelled) {
173
+ setError(err instanceof Error ? err.message : "Failed to load");
174
+ setLoading(false);
175
+ }
176
+ });
177
+ return () => {
178
+ cancelled = true;
179
+ };
180
+ }, [parsed]);
181
+ if (parsed.type === "inline") {
182
+ return { snapshot: parsed.snapshot, loading: false, error: "" };
183
+ }
184
+ if (parsed.type === "remote" || parsed.type === "incremental") {
185
+ return {
186
+ snapshot: remoteSnapshot,
187
+ loading,
188
+ error,
189
+ baseRef: parsed.refLine,
190
+ baseData
191
+ };
192
+ }
193
+ if (parsed.type === "error") {
194
+ return { snapshot: void 0, loading: false, error: parsed.error };
195
+ }
196
+ return { snapshot: void 0, loading: false, error: "" };
197
+ }
198
+ const ExcalidrawDisplayRenderer = ({ snapshot }) => {
199
+ const theme = useColorScheme();
200
+ return /* @__PURE__ */ jsx(ExcalidrawStaticCanvas, { snapshot, theme });
201
+ };
202
+ const ExcalidrawExpandContent = ({ dismiss, ExcalidrawComponent, data, theme }) => {
203
+ const apiRef = useRef(null);
204
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
205
+ /* @__PURE__ */ jsxs("div", { className: excalidrawDialogHeader, children: [
206
+ /* @__PURE__ */ jsxs("div", { className: excalidrawDialogHeaderTitle, children: [
207
+ /* @__PURE__ */ jsx("span", { className: excalidrawDialogTitle, children: "Whiteboard" }),
208
+ /* @__PURE__ */ jsx("span", { className: excalidrawDialogMeta, children: "excalidraw" })
209
+ ] }),
210
+ /* @__PURE__ */ jsx("button", { className: excalidrawHeaderClose, type: "button", onClick: dismiss, children: /* @__PURE__ */ jsx(X, { size: 18 }) })
211
+ ] }),
212
+ /* @__PURE__ */ jsx("div", { className: excalidrawDialogCanvas, children: /* @__PURE__ */ jsx(
213
+ ExcalidrawComponent,
214
+ {
215
+ viewModeEnabled: true,
216
+ zenModeEnabled: true,
217
+ UIOptions: readonlyUIOptions,
218
+ initialData: data,
219
+ theme,
220
+ excalidrawAPI: (api) => {
221
+ apiRef.current = api;
222
+ setTimeout(() => api.scrollToContent(), 100);
223
+ }
224
+ }
225
+ ) })
226
+ ] });
227
+ };
228
+ const ExcalidrawStaticCanvas = ({ snapshot, theme }) => {
229
+ const { snapshot: data, loading: dataLoading, error: dataError } = useExcalidrawData(snapshot);
230
+ const [ExcalidrawComponent, setExcalidrawComponent] = useState(null);
231
+ const [libLoading, setLibLoading] = useState(true);
232
+ const apiRef = useRef(null);
233
+ const { className: portalClassName } = usePortalTheme();
234
+ useEffect(() => {
235
+ import("@excalidraw/excalidraw").then((mod) => {
236
+ const Comp = mod.Excalidraw;
237
+ if (Comp) setExcalidrawComponent(() => Comp);
238
+ setLibLoading(false);
239
+ }).catch((error) => {
240
+ console.error("Error loading excalidraw", error);
241
+ setLibLoading(false);
242
+ });
243
+ }, []);
244
+ const handleExpand = useCallback(() => {
245
+ if (!ExcalidrawComponent || !data) return;
246
+ presentDialog({
247
+ content: ({ dismiss }) => /* @__PURE__ */ jsx(
248
+ ExcalidrawExpandContent,
249
+ {
250
+ ExcalidrawComponent,
251
+ data,
252
+ dismiss,
253
+ theme
254
+ }
255
+ ),
256
+ className: excalidrawFullscreenPopup,
257
+ portalClassName,
258
+ theme,
259
+ showCloseButton: false,
260
+ clickOutsideToDismiss: true
261
+ });
262
+ }, [ExcalidrawComponent, data, theme, portalClassName]);
263
+ const loading = dataLoading || libLoading;
264
+ if (loading) {
265
+ return /* @__PURE__ */ jsx("div", { className: excalidrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: excalidrawLoading, children: "Loading excalidraw..." }) });
266
+ }
267
+ if (dataError || !data) {
268
+ return /* @__PURE__ */ jsx("div", { className: excalidrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: excalidrawError, children: dataError || "No data" }) });
269
+ }
270
+ if (!ExcalidrawComponent) {
271
+ return /* @__PURE__ */ jsx("div", { className: excalidrawStaticContainer, children: /* @__PURE__ */ jsx("div", { className: excalidrawError, children: "Failed to load excalidraw" }) });
272
+ }
273
+ return /* @__PURE__ */ jsxs(
274
+ "div",
275
+ {
276
+ suppressHydrationWarning: true,
277
+ className: excalidrawStaticContainer,
278
+ "data-color-scheme": theme,
279
+ "data-theme": theme,
280
+ children: [
281
+ /* @__PURE__ */ jsx(
282
+ ExcalidrawErrorBoundary,
283
+ {
284
+ fallback: /* @__PURE__ */ jsx("div", { className: excalidrawError, children: "Failed to render excalidraw" }),
285
+ children: /* @__PURE__ */ jsx(
286
+ ExcalidrawComponent,
287
+ {
288
+ viewModeEnabled: true,
289
+ zenModeEnabled: true,
290
+ UIOptions: readonlyUIOptions,
291
+ initialData: data,
292
+ theme,
293
+ excalidrawAPI: (api) => {
294
+ apiRef.current = api;
295
+ setTimeout(() => api.scrollToContent(), 100);
296
+ }
297
+ }
298
+ )
299
+ }
300
+ ),
301
+ /* @__PURE__ */ jsxs("div", { className: excalidrawActionGroup, children: [
302
+ /* @__PURE__ */ jsx(
303
+ "button",
304
+ {
305
+ className: excalidrawActionButton,
306
+ title: "Zoom In",
307
+ type: "button",
308
+ onClick: () => {
309
+ const api = apiRef.current;
310
+ if (!api) return;
311
+ const zoom = api.getAppState().zoom.value;
312
+ api.updateScene({ appState: { zoom: { value: zoom * 1.25 } } });
313
+ },
314
+ children: /* @__PURE__ */ jsx(ZoomIn, { size: 20 })
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsx(
318
+ "button",
319
+ {
320
+ className: excalidrawActionButton,
321
+ title: "Zoom Out",
322
+ type: "button",
323
+ onClick: () => {
324
+ const api = apiRef.current;
325
+ if (!api) return;
326
+ const zoom = api.getAppState().zoom.value;
327
+ api.updateScene({ appState: { zoom: { value: zoom / 1.25 } } });
328
+ },
329
+ children: /* @__PURE__ */ jsx(ZoomOut, { size: 20 })
330
+ }
331
+ ),
332
+ /* @__PURE__ */ jsx(
333
+ "button",
334
+ {
335
+ className: excalidrawActionButton,
336
+ title: "Fit to Content",
337
+ type: "button",
338
+ onClick: () => apiRef.current?.scrollToContent(),
339
+ children: /* @__PURE__ */ jsx(ScanSearch, { size: 20 })
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsx(
343
+ "button",
344
+ {
345
+ className: excalidrawActionButton,
346
+ title: "Expand",
347
+ type: "button",
348
+ onClick: handleExpand,
349
+ children: /* @__PURE__ */ jsx(Maximize2, { size: 20 })
350
+ }
351
+ )
352
+ ] })
353
+ ]
354
+ }
355
+ );
356
+ };
357
+ class ExcalidrawErrorBoundary extends Component {
358
+ constructor() {
359
+ super(...arguments);
360
+ __publicField(this, "state", { hasError: false });
361
+ }
362
+ static getDerivedStateFromError() {
363
+ return { hasError: true };
364
+ }
365
+ render() {
366
+ return this.state.hasError ? this.props.fallback : this.props.children;
367
+ }
368
+ }
369
+ const ExcalidrawDisplayRenderer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
370
+ __proto__: null,
371
+ ExcalidrawDisplayRenderer
372
+ }, Symbol.toStringTag, { value: "Module" }));
373
+ const LazyDisplayRenderer = lazy(
374
+ () => Promise.resolve().then(() => ExcalidrawDisplayRenderer$1).then((m) => ({
375
+ default: m.ExcalidrawDisplayRenderer
376
+ }))
377
+ );
378
+ const ExcalidrawPlaceholder = ({ snapshot }) => {
379
+ let label = "Excalidraw Whiteboard";
380
+ try {
381
+ const data = JSON.parse(snapshot);
382
+ if (data && typeof data === "object") {
383
+ const elementCount = Array.isArray(data.elements) ? data.elements.length : 0;
384
+ if (elementCount > 0) {
385
+ label = `Excalidraw Whiteboard (${elementCount} elements)`;
386
+ }
387
+ }
388
+ } catch {
389
+ }
390
+ return /* @__PURE__ */ jsx("div", { "aria-label": label, className: excalidrawPlaceholder, children: /* @__PURE__ */ jsx("span", { children: label }) });
391
+ };
392
+ const ExcalidrawSSRRenderer = ({ snapshot }) => {
393
+ return /* @__PURE__ */ jsx(ViewportGate, { fallback: /* @__PURE__ */ jsx(ExcalidrawPlaceholder, { snapshot }), children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ExcalidrawPlaceholder, { snapshot }), children: /* @__PURE__ */ jsx(LazyDisplayRenderer, { snapshot }) }) });
394
+ };
395
+ class ExcalidrawNode extends DecoratorNode {
396
+ constructor(snapshot, key) {
397
+ super(key);
398
+ __publicField(this, "__snapshot");
399
+ this.__snapshot = snapshot;
400
+ }
401
+ static getType() {
402
+ return "excalidraw";
403
+ }
404
+ static clone(node) {
405
+ return new ExcalidrawNode(node.__snapshot, node.__key);
406
+ }
407
+ createDOM(_config) {
408
+ const div = document.createElement("div");
409
+ div.className = "rich-excalidraw-wrapper";
410
+ return div;
411
+ }
412
+ updateDOM() {
413
+ return false;
414
+ }
415
+ isInline() {
416
+ return false;
417
+ }
418
+ static importJSON(serializedNode) {
419
+ return $createExcalidrawNode(serializedNode.snapshot);
420
+ }
421
+ exportJSON() {
422
+ return {
423
+ ...super.exportJSON(),
424
+ type: "excalidraw",
425
+ snapshot: this.__snapshot,
426
+ version: 1
427
+ };
428
+ }
429
+ getSnapshot() {
430
+ return this.__snapshot;
431
+ }
432
+ setSnapshot(snapshot) {
433
+ const writable = this.getWritable();
434
+ writable.__snapshot = snapshot;
435
+ }
436
+ decorate(_editor, _config) {
437
+ return createElement(ExcalidrawSSRRenderer, {
438
+ snapshot: this.__snapshot
439
+ });
440
+ }
441
+ }
442
+ function $createExcalidrawNode(snapshot) {
443
+ return new ExcalidrawNode(snapshot);
444
+ }
445
+ function $isExcalidrawNode(node) {
446
+ return node instanceof ExcalidrawNode;
447
+ }
448
+ export {
449
+ $createExcalidrawNode as $,
450
+ excalidrawActionBarUrl as A,
451
+ excalidrawHeaderClose as B,
452
+ excalidrawDialogCanvas as C,
453
+ ExcalidrawConfigProvider as E,
454
+ $isExcalidrawNode as a,
455
+ ExcalidrawDisplayRenderer as b,
456
+ ExcalidrawNode as c,
457
+ ExcalidrawSSRRenderer as d,
458
+ useExcalidrawData as e,
459
+ excalidrawFullscreenPopup as f,
460
+ excalidrawEditorContainer as g,
461
+ excalidrawLoading as h,
462
+ excalidrawError as i,
463
+ excalidrawEditOverlay as j,
464
+ excalidrawEditLabel as k,
465
+ excalidrawConfirmActions as l,
466
+ excalidrawConfirmBtn as m,
467
+ excalidrawConfirmBtnDanger as n,
468
+ excalidrawConfirmBtnPrimary as o,
469
+ parseSnapshot as p,
470
+ excalidrawDialogHeader as q,
471
+ readonlyUIOptions as r,
472
+ serializeSnapshot as s,
473
+ excalidrawStatusDot as t,
474
+ useExcalidrawConfig as u,
475
+ excalidrawDialogTitle as v,
476
+ excalidrawDialogMeta as w,
477
+ excalidrawHeaderActions as x,
478
+ excalidrawActionBarSep as y,
479
+ excalidrawActionBarBtn as z
480
+ };