canvu-react 0.3.5

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/camera-BwQjm5oh.d.cts +50 -0
  4. package/dist/camera-KwCYYPhm.d.ts +50 -0
  5. package/dist/chatbot.cjs +221 -0
  6. package/dist/chatbot.cjs.map +1 -0
  7. package/dist/chatbot.d.cts +36 -0
  8. package/dist/chatbot.d.ts +36 -0
  9. package/dist/chatbot.js +218 -0
  10. package/dist/chatbot.js.map +1 -0
  11. package/dist/index.cjs +1920 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +276 -0
  14. package/dist/index.d.ts +276 -0
  15. package/dist/index.js +1867 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/native.cjs +2572 -0
  18. package/dist/native.cjs.map +1 -0
  19. package/dist/native.d.cts +217 -0
  20. package/dist/native.d.ts +217 -0
  21. package/dist/native.js +2562 -0
  22. package/dist/native.js.map +1 -0
  23. package/dist/react.cjs +8540 -0
  24. package/dist/react.cjs.map +1 -0
  25. package/dist/react.d.cts +481 -0
  26. package/dist/react.d.ts +481 -0
  27. package/dist/react.js +8492 -0
  28. package/dist/react.js.map +1 -0
  29. package/dist/realtime.cjs +2338 -0
  30. package/dist/realtime.cjs.map +1 -0
  31. package/dist/realtime.d.cts +309 -0
  32. package/dist/realtime.d.ts +309 -0
  33. package/dist/realtime.js +2317 -0
  34. package/dist/realtime.js.map +1 -0
  35. package/dist/shape-builders-DTYvub8W.d.ts +93 -0
  36. package/dist/shape-builders-DxPoOecg.d.cts +93 -0
  37. package/dist/tldraw.cjs +1948 -0
  38. package/dist/tldraw.cjs.map +1 -0
  39. package/dist/tldraw.d.cts +98 -0
  40. package/dist/tldraw.d.ts +98 -0
  41. package/dist/tldraw.js +1941 -0
  42. package/dist/tldraw.js.map +1 -0
  43. package/dist/types--ALu1mF-.d.ts +356 -0
  44. package/dist/types-B58i5k-u.d.cts +35 -0
  45. package/dist/types-CB0TZZuk.d.cts +157 -0
  46. package/dist/types-CB0TZZuk.d.ts +157 -0
  47. package/dist/types-D1ftVsOQ.d.cts +356 -0
  48. package/dist/types-DgEArHkA.d.ts +35 -0
  49. package/package.json +103 -0
@@ -0,0 +1,2338 @@
1
+ 'use strict';
2
+
3
+ var lucideReact = require('lucide-react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+
7
+ // src/react/presence/map-placement-preview.ts
8
+ function remoteMarkupStrokeFromPlacementPreview(preview) {
9
+ if (!preview || preview.kind !== "stroke" || preview.points.length === 0) {
10
+ return null;
11
+ }
12
+ const tool = preview.tool;
13
+ const mapped = tool === "laser" || tool === "marker" || tool === "draw" ? tool : "draw";
14
+ return {
15
+ points: preview.points,
16
+ tool: mapped
17
+ };
18
+ }
19
+
20
+ // src/math/rect.ts
21
+ function normalizeRect(r) {
22
+ const x0 = r.width >= 0 ? r.x : r.x + r.width;
23
+ const y0 = r.height >= 0 ? r.y : r.y + r.height;
24
+ return {
25
+ x: x0,
26
+ y: y0,
27
+ width: Math.abs(r.width),
28
+ height: Math.abs(r.height)
29
+ };
30
+ }
31
+
32
+ // src/renderer/svg-vector-renderer.ts
33
+ function formatCameraTransform(camera) {
34
+ const z = camera.zoom;
35
+ return `matrix(${z}, 0, 0, ${z}, ${camera.x}, ${camera.y})`;
36
+ }
37
+
38
+ // src/scene/freehand-path.ts
39
+ function smoothFreehandPointsToPathD(points) {
40
+ const n = points.length;
41
+ if (n === 0) return "";
42
+ if (n === 1) {
43
+ const p = points[0];
44
+ if (!p) return "";
45
+ return `M ${p.x} ${p.y}`;
46
+ }
47
+ if (n === 2) {
48
+ const a = points[0];
49
+ const b = points[1];
50
+ if (!a || !b) return "";
51
+ return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
52
+ }
53
+ const p0 = points[0];
54
+ if (!p0) return "";
55
+ let d = `M ${p0.x} ${p0.y}`;
56
+ let i = 1;
57
+ for (; i < n - 2; i++) {
58
+ const pi = points[i];
59
+ const pi1 = points[i + 1];
60
+ if (!pi || !pi1) continue;
61
+ const xc = (pi.x + pi1.x) / 2;
62
+ const yc = (pi.y + pi1.y) / 2;
63
+ d += ` Q ${pi.x} ${pi.y} ${xc} ${yc}`;
64
+ }
65
+ const pLast = points[i];
66
+ const pEnd = points[i + 1];
67
+ if (!pLast || !pEnd) return d;
68
+ d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
69
+ return d;
70
+ }
71
+
72
+ // src/react/presence/peer-color.ts
73
+ function defaultPresenceColorForId(id) {
74
+ let h = 2166136261;
75
+ for (let i = 0; i < id.length; i++) {
76
+ h ^= id.charCodeAt(i);
77
+ h = Math.imul(h, 16777619);
78
+ }
79
+ const hue = (h >>> 0) % 360;
80
+ return `hsl(${hue} 72% 42%)`;
81
+ }
82
+ function strokePaint(tool, fallback) {
83
+ switch (tool) {
84
+ case "laser":
85
+ return { stroke: "#f43f5e", strokeOpacity: 0.92, widthWorld: 4 };
86
+ case "marker":
87
+ return { stroke: fallback, strokeOpacity: 0.45, widthWorld: 14 };
88
+ case "brush":
89
+ return { stroke: fallback, strokeOpacity: 0.85, widthWorld: 5 };
90
+ case "pencil":
91
+ return { stroke: fallback, strokeOpacity: 0.9, widthWorld: 2.5 };
92
+ default:
93
+ return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
94
+ }
95
+ }
96
+ function PresenceRemoteLayer({
97
+ camera,
98
+ cameraVersion: _cameraVersion,
99
+ peers
100
+ }) {
101
+ const z = camera.zoom;
102
+ const rootTransform = formatCameraTransform(camera);
103
+ const overlayStrokePx = 1.25;
104
+ const LUCIDE_POINTER_VIEWBOX = 24;
105
+ const remoteCursorScreenPx = 15;
106
+ const iconWorldScale = remoteCursorScreenPx / (LUCIDE_POINTER_VIEWBOX * z);
107
+ return /* @__PURE__ */ jsxRuntime.jsx(
108
+ "svg",
109
+ {
110
+ style: {
111
+ position: "absolute",
112
+ top: 0,
113
+ left: 0,
114
+ right: 0,
115
+ bottom: 0,
116
+ zIndex: 9,
117
+ width: "100%",
118
+ height: "100%",
119
+ touchAction: "none",
120
+ pointerEvents: "none"
121
+ },
122
+ role: "presentation",
123
+ "aria-hidden": true,
124
+ width: "100%",
125
+ height: "100%",
126
+ children: /* @__PURE__ */ jsxRuntime.jsx("g", { transform: rootTransform, children: peers.map((peer) => {
127
+ const color = peer.color ?? defaultPresenceColorForId(peer.id);
128
+ const markup = peer.markupStroke;
129
+ let strokeNode = null;
130
+ if (markup && markup.points.length > 0) {
131
+ const paint = strokePaint(markup.tool, color);
132
+ const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
133
+ if (d) {
134
+ strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
135
+ "path",
136
+ {
137
+ d,
138
+ fill: "none",
139
+ stroke: paint.stroke,
140
+ strokeOpacity: paint.strokeOpacity,
141
+ strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
142
+ strokeLinecap: "round",
143
+ strokeLinejoin: "round",
144
+ shapeRendering: "geometricPrecision",
145
+ vectorEffect: "non-scaling-stroke"
146
+ }
147
+ );
148
+ } else {
149
+ const p0 = markup.points[0];
150
+ if (p0) {
151
+ strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
152
+ "circle",
153
+ {
154
+ cx: p0.x,
155
+ cy: p0.y,
156
+ r: Math.max(3 / z, 2),
157
+ fill: paint.stroke,
158
+ fillOpacity: paint.strokeOpacity,
159
+ vectorEffect: "non-scaling-stroke"
160
+ }
161
+ );
162
+ }
163
+ }
164
+ }
165
+ const cur = peer.cursor;
166
+ let cursorNode = null;
167
+ if (cur) {
168
+ const displayName = peer.displayName;
169
+ const labelOffsetX = 10 / z;
170
+ const labelOffsetY = 10 / z;
171
+ const labelFont = 10 / z;
172
+ cursorNode = /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(
174
+ "g",
175
+ {
176
+ transform: `translate(${cur.x}, ${cur.y}) scale(${iconWorldScale}) translate(${ -4.037}, ${ -4.688})`,
177
+ children: /* @__PURE__ */ jsxRuntime.jsx(
178
+ lucideReact.MousePointer2,
179
+ {
180
+ size: LUCIDE_POINTER_VIEWBOX,
181
+ color,
182
+ fill: color,
183
+ stroke: "#ffffff",
184
+ strokeWidth: 1.25,
185
+ absoluteStrokeWidth: true,
186
+ "aria-hidden": true
187
+ }
188
+ )
189
+ }
190
+ ),
191
+ displayName ? /* @__PURE__ */ jsxRuntime.jsx(
192
+ "text",
193
+ {
194
+ x: cur.x + labelOffsetX,
195
+ y: cur.y + labelOffsetY,
196
+ fill: color,
197
+ stroke: "#ffffff",
198
+ strokeWidth: 2.5 / z,
199
+ paintOrder: "stroke",
200
+ style: {
201
+ fontSize: labelFont,
202
+ fontFamily: "system-ui, sans-serif",
203
+ fontWeight: 600
204
+ },
205
+ vectorEffect: "non-scaling-stroke",
206
+ children: displayName
207
+ }
208
+ ) : null
209
+ ] });
210
+ }
211
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
212
+ strokeNode,
213
+ cursorNode
214
+ ] }, peer.id);
215
+ }) })
216
+ }
217
+ );
218
+ }
219
+
220
+ // src/react/plugins/realtime/protocol.ts
221
+ function isRecord(value) {
222
+ return typeof value === "object" && value !== null && !Array.isArray(value);
223
+ }
224
+ function getString(value) {
225
+ return typeof value === "string" ? value : void 0;
226
+ }
227
+ function getNumber(value) {
228
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
229
+ }
230
+ function parseCursor(value) {
231
+ if (value === null) return null;
232
+ if (!isRecord(value)) return void 0;
233
+ const x = getNumber(value.x);
234
+ const y = getNumber(value.y);
235
+ if (x == null || y == null) return void 0;
236
+ return { x, y };
237
+ }
238
+ function parseMarkupStroke(value) {
239
+ if (value === null) return null;
240
+ if (!isRecord(value) || !Array.isArray(value.points)) return void 0;
241
+ const tool = getString(value.tool);
242
+ if (tool !== "draw" && tool !== "pencil" && tool !== "brush" && tool !== "marker" && tool !== "laser") {
243
+ return void 0;
244
+ }
245
+ const points = value.points.map((point) => {
246
+ if (!isRecord(point)) return null;
247
+ const x = getNumber(point.x);
248
+ const y = getNumber(point.y);
249
+ if (x == null || y == null) return null;
250
+ return { x, y };
251
+ }).filter((point) => point != null);
252
+ return { points, tool };
253
+ }
254
+ function parsePresencePayload(value) {
255
+ if (!isRecord(value)) return void 0;
256
+ const cursor = parseCursor(value.cursor);
257
+ if (cursor === void 0) return void 0;
258
+ const markupStroke = parseMarkupStroke(value.markupStroke);
259
+ if (markupStroke === void 0 && value.markupStroke !== void 0)
260
+ return void 0;
261
+ return {
262
+ cursor,
263
+ ...markupStroke !== void 0 ? { markupStroke } : {},
264
+ ...getString(value.activeTool) ? { activeTool: getString(value.activeTool) } : {}
265
+ };
266
+ }
267
+ function parseItems(value) {
268
+ return Array.isArray(value) ? value : void 0;
269
+ }
270
+ function parseDocumentSnapshot(value) {
271
+ if (!isRecord(value)) return void 0;
272
+ const revision = getNumber(value.revision);
273
+ const updatedAt = getNumber(value.updatedAt);
274
+ const items = parseItems(value.items);
275
+ if (revision == null || updatedAt == null || items == null) return void 0;
276
+ return {
277
+ revision,
278
+ updatedAt,
279
+ items,
280
+ ...getString(value.updatedByClientId) ? { updatedByClientId: getString(value.updatedByClientId) } : {}
281
+ };
282
+ }
283
+ function parseRealtimeSessionPeer(value) {
284
+ if (!isRecord(value)) return void 0;
285
+ const clientId = getString(value.clientId);
286
+ const peerId = getString(value.peerId);
287
+ const roomId = getString(value.roomId);
288
+ const joinedAt = getNumber(value.joinedAt);
289
+ const lastSeenAt = getNumber(value.lastSeenAt);
290
+ const cursor = parseCursor(value.cursor);
291
+ if (clientId == null || peerId == null || roomId == null || joinedAt == null || lastSeenAt == null || cursor === void 0) {
292
+ return void 0;
293
+ }
294
+ const markupStroke = parseMarkupStroke(value.markupStroke);
295
+ if (markupStroke === void 0 && value.markupStroke !== void 0)
296
+ return void 0;
297
+ const isSelf = value.isSelf === true;
298
+ const connectionState = getString(value.connectionState);
299
+ return {
300
+ id: clientId,
301
+ clientId,
302
+ peerId,
303
+ roomId,
304
+ joinedAt,
305
+ lastSeenAt,
306
+ isSelf,
307
+ cursor,
308
+ ...getString(value.displayName) ? { displayName: getString(value.displayName) } : {},
309
+ ...getString(value.color) ? { color: getString(value.color) } : {},
310
+ ...getString(value.image) ? { image: getString(value.image) } : {},
311
+ ...markupStroke !== void 0 ? { markupStroke } : {},
312
+ ...getString(value.activeTool) ? { activeTool: getString(value.activeTool) } : {},
313
+ ...connectionState ? { connectionState } : {}
314
+ };
315
+ }
316
+ function parsePeers(value) {
317
+ if (!Array.isArray(value)) return void 0;
318
+ const peers = value.map((peer) => parseRealtimeSessionPeer(peer)).filter((peer) => peer != null);
319
+ return peers.length === value.length ? peers : void 0;
320
+ }
321
+ function parseRealtimeClientMessage(value) {
322
+ if (!isRecord(value)) return void 0;
323
+ const type = getString(value.type);
324
+ if (type === "session:join") {
325
+ const roomId = getString(value.roomId);
326
+ const peer = isRecord(value.peer) ? value.peer : void 0;
327
+ const clientId = peer ? getString(peer.clientId) : void 0;
328
+ const peerId = peer ? getString(peer.peerId) : void 0;
329
+ if (!roomId || !peer || !clientId || !peerId) return void 0;
330
+ return {
331
+ type,
332
+ roomId,
333
+ peer: {
334
+ clientId,
335
+ peerId,
336
+ ...getString(peer.displayName) ? { displayName: getString(peer.displayName) } : {},
337
+ ...getString(peer.color) ? { color: getString(peer.color) } : {},
338
+ ...getString(peer.image) ? { image: getString(peer.image) } : {}
339
+ }
340
+ };
341
+ }
342
+ if (type === "session:leave" || type === "session:ping") {
343
+ const roomId = getString(value.roomId);
344
+ const clientId = getString(value.clientId);
345
+ if (!roomId || !clientId) return void 0;
346
+ if (type === "session:leave") {
347
+ return { type, roomId, clientId };
348
+ }
349
+ const sentAt = getNumber(value.sentAt);
350
+ if (sentAt == null) return void 0;
351
+ return { type, roomId, clientId, sentAt };
352
+ }
353
+ if (type === "presence:update") {
354
+ const roomId = getString(value.roomId);
355
+ const clientId = getString(value.clientId);
356
+ const presence = parsePresencePayload(value.presence);
357
+ if (!roomId || !clientId || !presence) return void 0;
358
+ return { type, roomId, clientId, presence };
359
+ }
360
+ if (type === "document:update") {
361
+ const roomId = getString(value.roomId);
362
+ const clientId = getString(value.clientId);
363
+ const baseRevision = getNumber(value.baseRevision);
364
+ const items = parseItems(value.items);
365
+ if (!roomId || !clientId || baseRevision == null || !items) return void 0;
366
+ return { type, roomId, clientId, baseRevision, items };
367
+ }
368
+ return void 0;
369
+ }
370
+ function parseRealtimeServerMessage(value) {
371
+ if (!isRecord(value)) return void 0;
372
+ const type = getString(value.type);
373
+ if (type === "session:welcome") {
374
+ const roomId = getString(value.roomId);
375
+ const clientId = getString(value.clientId);
376
+ const serverTime = getNumber(value.serverTime);
377
+ const document2 = parseDocumentSnapshot(value.document);
378
+ const peers = parsePeers(value.peers);
379
+ if (!roomId || !clientId || serverTime == null || !document2 || !peers) {
380
+ return void 0;
381
+ }
382
+ return { type, roomId, clientId, serverTime, document: document2, peers };
383
+ }
384
+ if (type === "session:peer-joined") {
385
+ const roomId = getString(value.roomId);
386
+ const peer = parseRealtimeSessionPeer(value.peer);
387
+ if (!roomId || !peer) return void 0;
388
+ return { type, roomId, peer };
389
+ }
390
+ if (type === "session:peer-left") {
391
+ const roomId = getString(value.roomId);
392
+ const clientId = getString(value.clientId);
393
+ if (!roomId || !clientId) return void 0;
394
+ return { type, roomId, clientId };
395
+ }
396
+ if (type === "session:pong") {
397
+ const roomId = getString(value.roomId);
398
+ const clientId = getString(value.clientId);
399
+ const sentAt = getNumber(value.sentAt);
400
+ const serverTime = getNumber(value.serverTime);
401
+ if (!roomId || !clientId || sentAt == null || serverTime == null) {
402
+ return void 0;
403
+ }
404
+ return { type, roomId, clientId, sentAt, serverTime };
405
+ }
406
+ if (type === "session:error") {
407
+ const code = getString(value.code);
408
+ const message = getString(value.message);
409
+ if (!code || !message) return void 0;
410
+ return {
411
+ type,
412
+ code,
413
+ message,
414
+ ...getString(value.roomId) ? { roomId: getString(value.roomId) } : {}
415
+ };
416
+ }
417
+ if (type === "presence:sync") {
418
+ const roomId = getString(value.roomId);
419
+ const peers = parsePeers(value.peers);
420
+ const serverTime = getNumber(value.serverTime);
421
+ if (!roomId || !peers || serverTime == null) return void 0;
422
+ return { type, roomId, peers, serverTime };
423
+ }
424
+ if (type === "document:sync" || type === "document:resync-required") {
425
+ const roomId = getString(value.roomId);
426
+ const document2 = parseDocumentSnapshot(value.document);
427
+ if (!roomId || !document2) return void 0;
428
+ if (type === "document:sync") {
429
+ return { type, roomId, document: document2 };
430
+ }
431
+ const reason = getString(value.reason);
432
+ if (!reason) return void 0;
433
+ return { type, roomId, reason, document: document2 };
434
+ }
435
+ return void 0;
436
+ }
437
+ var shell = {
438
+ position: "absolute",
439
+ top: 12,
440
+ left: "50%",
441
+ transform: "translateX(-50%)",
442
+ display: "flex",
443
+ flexDirection: "row",
444
+ alignItems: "center",
445
+ flexWrap: "wrap",
446
+ justifyContent: "center",
447
+ gap: "0 14px",
448
+ maxWidth: "min(100% - 24px, 520px)",
449
+ padding: "6px 14px",
450
+ borderRadius: 9999,
451
+ border: "1px solid rgba(15, 23, 42, 0.08)",
452
+ background: "rgba(255, 255, 255, 0.78)",
453
+ backdropFilter: "blur(12px)",
454
+ WebkitBackdropFilter: "blur(12px)",
455
+ boxShadow: "0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 16px rgba(15, 23, 42, 0.06)",
456
+ pointerEvents: "auto",
457
+ userSelect: "none",
458
+ WebkitUserSelect: "none",
459
+ WebkitTouchCallout: "none",
460
+ fontSize: "0.6875rem",
461
+ fontWeight: 500,
462
+ letterSpacing: "0.01em",
463
+ color: "#334155"
464
+ };
465
+ var titleStyle = {
466
+ fontWeight: 600,
467
+ color: "#64748b",
468
+ letterSpacing: "0.06em",
469
+ textTransform: "uppercase",
470
+ fontSize: "0.625rem",
471
+ whiteSpace: "nowrap"
472
+ };
473
+ var statusRow = {
474
+ display: "inline-flex",
475
+ alignItems: "center",
476
+ gap: 6,
477
+ flexShrink: 0
478
+ };
479
+ var peerList = {
480
+ display: "flex",
481
+ alignItems: "center",
482
+ flexWrap: "wrap",
483
+ gap: "4px 10px",
484
+ margin: 0,
485
+ padding: 0,
486
+ listStyle: "none",
487
+ justifyContent: "center"
488
+ };
489
+ var peerItem = {
490
+ display: "inline-flex",
491
+ alignItems: "center",
492
+ gap: 5,
493
+ maxWidth: 140
494
+ };
495
+ var dot = {
496
+ width: 6,
497
+ height: 6,
498
+ borderRadius: 999,
499
+ flexShrink: 0
500
+ };
501
+ var avatarStyle = {
502
+ width: 18,
503
+ height: 18,
504
+ borderRadius: 999,
505
+ objectFit: "cover",
506
+ flexShrink: 0,
507
+ border: "1px solid rgba(15, 23, 42, 0.08)"
508
+ };
509
+ var nameStyle = {
510
+ overflow: "hidden",
511
+ textOverflow: "ellipsis",
512
+ whiteSpace: "nowrap"
513
+ };
514
+ function connectionAppearance(connectionState) {
515
+ if (connectionState === "connected") {
516
+ return {
517
+ color: "#16a34a",
518
+ label: "Live",
519
+ shadow: "0 0 0 2px rgba(22, 163, 74, 0.25)"
520
+ };
521
+ }
522
+ if (connectionState === "connecting") {
523
+ return {
524
+ color: "#2563eb",
525
+ label: "Conectando",
526
+ shadow: "0 0 0 2px rgba(37, 99, 235, 0.18)"
527
+ };
528
+ }
529
+ if (connectionState === "reconnecting") {
530
+ return {
531
+ color: "#d97706",
532
+ label: "Reconectando",
533
+ shadow: "0 0 0 2px rgba(217, 119, 6, 0.2)"
534
+ };
535
+ }
536
+ if (connectionState === "error") {
537
+ return {
538
+ color: "#dc2626",
539
+ label: "Erro",
540
+ shadow: "none"
541
+ };
542
+ }
543
+ return {
544
+ color: "#64748b",
545
+ label: "Offline",
546
+ shadow: "none"
547
+ };
548
+ }
549
+ function RealtimeSessionPanel({
550
+ title = "Session",
551
+ peers,
552
+ connected = true,
553
+ connectionState,
554
+ roomId,
555
+ children
556
+ }) {
557
+ const resolvedConnectionState = connectionState ?? (connected ? "connected" : "reconnecting");
558
+ const status = connectionAppearance(resolvedConnectionState);
559
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shell, "data-slot": "realtime-session-panel", children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: titleStyle, children: title }),
561
+ roomId ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
562
+ /* @__PURE__ */ jsxRuntime.jsx(
563
+ "span",
564
+ {
565
+ style: {
566
+ width: 1,
567
+ height: 14,
568
+ background: "rgba(15, 23, 42, 0.12)",
569
+ flexShrink: 0
570
+ },
571
+ "aria-hidden": true
572
+ }
573
+ ),
574
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...titleStyle, textTransform: "none", letterSpacing: 0 }, children: [
575
+ "Sala ",
576
+ roomId
577
+ ] })
578
+ ] }) : null,
579
+ /* @__PURE__ */ jsxRuntime.jsx(
580
+ "span",
581
+ {
582
+ style: {
583
+ width: 1,
584
+ height: 14,
585
+ background: "rgba(15, 23, 42, 0.12)",
586
+ flexShrink: 0
587
+ },
588
+ "aria-hidden": true
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: statusRow, children: [
592
+ /* @__PURE__ */ jsxRuntime.jsx(
593
+ "span",
594
+ {
595
+ style: {
596
+ ...dot,
597
+ background: status.color,
598
+ boxShadow: status.shadow
599
+ },
600
+ "aria-hidden": true
601
+ }
602
+ ),
603
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: status.color, fontWeight: 600 }, children: status.label })
604
+ ] }),
605
+ peers.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
606
+ /* @__PURE__ */ jsxRuntime.jsx(
607
+ "span",
608
+ {
609
+ style: {
610
+ width: 1,
611
+ height: 14,
612
+ background: "rgba(15, 23, 42, 0.12)",
613
+ flexShrink: 0
614
+ },
615
+ "aria-hidden": true
616
+ }
617
+ ),
618
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { style: peerList, children: peers.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: peerItem, children: [
619
+ "image" in p && p.image ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: p.image, alt: "", "aria-hidden": true, style: avatarStyle }) : /* @__PURE__ */ jsxRuntime.jsx(
620
+ "span",
621
+ {
622
+ "aria-hidden": true,
623
+ style: {
624
+ ...dot,
625
+ background: p.color ?? "#94a3b8"
626
+ }
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: nameStyle, children: [
630
+ p.displayName ?? p.id,
631
+ "isSelf" in p && p.isSelf ? " (voc\xEA)" : ""
632
+ ] })
633
+ ] }, p.id)) })
634
+ ] }) : null,
635
+ children
636
+ ] });
637
+ }
638
+ var CanvuPluginContext = react.createContext(
639
+ null
640
+ );
641
+ function createCanvuPlugin(plugin) {
642
+ return plugin;
643
+ }
644
+ function useCanvuPluginContext() {
645
+ const ctx = react.useContext(CanvuPluginContext);
646
+ if (!ctx) {
647
+ throw new Error(
648
+ "useCanvuPluginContext must be used inside a VectorViewport plugin runtime."
649
+ );
650
+ }
651
+ return ctx;
652
+ }
653
+ function useCanvuViewportContext() {
654
+ const { viewportRef, viewport } = useCanvuPluginContext();
655
+ return { viewportRef, viewport };
656
+ }
657
+ function useCanvuDocumentContext() {
658
+ const { viewport } = useCanvuPluginContext();
659
+ return {
660
+ items: viewport.items,
661
+ onItemsChange: viewport.onItemsChange
662
+ };
663
+ }
664
+ function useCanvuPluginContribution(pluginId, contribution) {
665
+ const { registerContribution, unregisterContribution } = useCanvuPluginContext();
666
+ react.useLayoutEffect(() => {
667
+ registerContribution(pluginId, contribution);
668
+ return () => unregisterContribution(pluginId);
669
+ }, [contribution, pluginId, registerContribution, unregisterContribution]);
670
+ }
671
+ var base = {
672
+ width: 20,
673
+ height: 20,
674
+ viewBox: "0 0 24 24",
675
+ fill: "none",
676
+ stroke: "currentColor",
677
+ strokeWidth: 2,
678
+ strokeLinecap: "round",
679
+ strokeLinejoin: "round"
680
+ };
681
+ function IconLaser(props) {
682
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...props, "aria-hidden": true, children: [
683
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "2.5" }),
684
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4v4" }),
685
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 16v4" }),
686
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 12h4" }),
687
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 12h4" })
688
+ ] });
689
+ }
690
+ var ic = { size: 20, strokeWidth: 2 };
691
+ var DEFAULT_VECTOR_TOOLS = [
692
+ {
693
+ id: "hand",
694
+ label: "Hand",
695
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Hand, { ...ic, "aria-hidden": true }),
696
+ shortcutHint: "H"
697
+ },
698
+ {
699
+ id: "select",
700
+ label: "Select",
701
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer2, { ...ic, "aria-hidden": true }),
702
+ shortcutHint: "V"
703
+ },
704
+ {
705
+ id: "rect",
706
+ label: "Rectangle",
707
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { ...ic, "aria-hidden": true }),
708
+ shortcutHint: "R"
709
+ },
710
+ {
711
+ id: "ellipse",
712
+ label: "Ellipse",
713
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { ...ic, "aria-hidden": true }),
714
+ shortcutHint: "O"
715
+ },
716
+ {
717
+ id: "line",
718
+ label: "Line",
719
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { ...ic, "aria-hidden": true }),
720
+ shortcutHint: "L"
721
+ },
722
+ {
723
+ id: "arrow",
724
+ label: "Arrow",
725
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpRight, { ...ic, "aria-hidden": true }),
726
+ shortcutHint: "A"
727
+ },
728
+ {
729
+ id: "draw",
730
+ label: "Desenhar",
731
+ tooltipLabel: "Draw",
732
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PenLine, { ...ic, "aria-hidden": true }),
733
+ shortcutHint: "D"
734
+ },
735
+ {
736
+ id: "marker",
737
+ label: "Realce",
738
+ tooltipLabel: "Highlighter",
739
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Highlighter, { ...ic, "aria-hidden": true }),
740
+ shortcutHint: "M"
741
+ },
742
+ {
743
+ id: "laser",
744
+ label: "Laser",
745
+ icon: /* @__PURE__ */ jsxRuntime.jsx(IconLaser, { "aria-hidden": true }),
746
+ shortcutHint: "K"
747
+ },
748
+ {
749
+ id: "eraser",
750
+ label: "Borracha",
751
+ tooltipLabel: "Eraser",
752
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eraser, { ...ic, "aria-hidden": true }),
753
+ shortcutHint: "E"
754
+ },
755
+ {
756
+ id: "text",
757
+ label: "Text",
758
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Type, { ...ic, "aria-hidden": true }),
759
+ shortcutHint: "T"
760
+ },
761
+ {
762
+ id: "image",
763
+ label: "File",
764
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Image, { ...ic, "aria-hidden": true }),
765
+ shortcutHint: "I"
766
+ }
767
+ ];
768
+
769
+ // src/scene/custom-shape.ts
770
+ function expandCustomShapeTemplate(template, width, height) {
771
+ return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
772
+ }
773
+ function resolveCustomInner(content, size) {
774
+ if ("render" in content) {
775
+ return content.render(size);
776
+ }
777
+ return expandCustomShapeTemplate(content.svg, size.width, size.height);
778
+ }
779
+ function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
780
+ const b = normalizeRect(bounds);
781
+ const sx = b.width / intrinsic.width;
782
+ const sy = b.height / intrinsic.height;
783
+ return `<g transform="scale(${sx},${sy})">${inner}</g>`;
784
+ }
785
+ function createCustomShapeItem(id, bounds, content) {
786
+ const r = normalizeRect(bounds);
787
+ const intrinsic = { width: r.width, height: r.height };
788
+ const inner = resolveCustomInner(content, intrinsic);
789
+ return {
790
+ id,
791
+ x: r.x,
792
+ y: r.y,
793
+ bounds: { ...r },
794
+ toolKind: "custom",
795
+ customIntrinsicSize: intrinsic,
796
+ customInnerSvg: inner,
797
+ childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
798
+ };
799
+ }
800
+ var iconProps = { size: 20, strokeWidth: 2 };
801
+ var COMMENT_PLUGIN_DATA_KEY = "realtimeComment";
802
+ var REALTIME_COMMENT_TOOL = {
803
+ id: "comment",
804
+ label: "Coment\xE1rio",
805
+ tooltipLabel: "Comment",
806
+ ariaLabel: "Adicionar coment\xE1rio colaborativo",
807
+ shortcutHint: "C",
808
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { ...iconProps, "aria-hidden": true })
809
+ };
810
+ function isRecord2(value) {
811
+ return typeof value === "object" && value !== null && !Array.isArray(value);
812
+ }
813
+ function getString2(value) {
814
+ return typeof value === "string" ? value : void 0;
815
+ }
816
+ function getNumber2(value) {
817
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
818
+ }
819
+ function initialsFromDisplayName(displayName) {
820
+ const words = displayName.trim().split(/\s+/).filter(Boolean).slice(0, 2);
821
+ if (words.length === 0) return "?";
822
+ return words.map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
823
+ }
824
+ function escapeSvgText(value) {
825
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
826
+ }
827
+ function createRealtimeCommentAvatarDataUrl(displayName, color) {
828
+ const initials = initialsFromDisplayName(displayName);
829
+ const svg = `
830
+ <svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
831
+ <defs>
832
+ <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
833
+ <stop offset="0%" stop-color="${color}" />
834
+ <stop offset="100%" stop-color="#0f172a" />
835
+ </linearGradient>
836
+ <radialGradient id="shine" cx="30%" cy="24%" r="70%">
837
+ <stop offset="0%" stop-color="#ffffff" stop-opacity="0.48" />
838
+ <stop offset="100%" stop-color="#ffffff" stop-opacity="0" />
839
+ </radialGradient>
840
+ </defs>
841
+ <rect width="96" height="96" rx="28" fill="url(#bg)" />
842
+ <rect width="96" height="96" rx="28" fill="url(#shine)" />
843
+ <circle cx="48" cy="38" r="18" fill="rgba(255,255,255,0.2)" />
844
+ <path d="M20 84c5-16 17-24 28-24s23 8 28 24" fill="rgba(255,255,255,0.16)" />
845
+ <text x="48" y="56" text-anchor="middle" font-size="28" font-weight="700" font-family="system-ui, sans-serif" fill="#ffffff">${escapeSvgText(initials)}</text>
846
+ </svg>
847
+ `;
848
+ return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
849
+ }
850
+ function commentPlaceholderInnerSvg(color) {
851
+ return `
852
+ <circle cx="9" cy="9" r="7" fill="rgba(255,255,255,0.94)" stroke="${color}" stroke-width="1.2" stroke-dasharray="2 1.5" />
853
+ <circle cx="9" cy="9" r="3.1" fill="${color}" fill-opacity="0.18" stroke="${color}" stroke-width="1" />
854
+ <path d="M7.1 8.1h3.8c.8 0 1.4.5 1.4 1.2v1.1c0 .7-.6 1.2-1.4 1.2H9.3l-1.2 1v-1H7.1c-.8 0-1.4-.5-1.4-1.2V9.3c0-.7.6-1.2 1.4-1.2Z" fill="#ffffff" stroke="${color}" stroke-width="0.9" />
855
+ `;
856
+ }
857
+ function commentAnchorInnerSvg(authorColor) {
858
+ return `
859
+ <circle cx="9" cy="9" r="7.2" fill="rgba(255,255,255,0.96)" stroke="rgba(15,23,42,0.1)" stroke-width="0.9" />
860
+ <circle cx="9" cy="9" r="4.1" fill="${authorColor}" />
861
+ <circle cx="13.2" cy="13.2" r="3.2" fill="#ffffff" stroke="rgba(15,23,42,0.12)" stroke-width="0.9" />
862
+ <path d="M11.7 12.1h2.7c.6 0 1 .4 1 .9v.9c0 .5-.4.9-1 .9h-1.1l-1 .8v-.8h-.4c-.6 0-1-.4-1-.9V13c0-.5.4-.9 1-.9Z" fill="#111827" fill-opacity="0.08" stroke="#111827" stroke-width="0.75" />
863
+ `;
864
+ }
865
+ function createRealtimeCommentDraftItem(id, bounds, seedColor = "#7c3aed") {
866
+ const base2 = createCustomShapeItem(id, bounds, {
867
+ render: () => commentPlaceholderInnerSvg(seedColor)
868
+ });
869
+ return {
870
+ ...base2,
871
+ pluginData: {
872
+ ...base2.pluginData ?? {},
873
+ [COMMENT_PLUGIN_DATA_KEY]: {
874
+ kind: "draft",
875
+ seedColor
876
+ }
877
+ }
878
+ };
879
+ }
880
+ function createRealtimeCommentItem(id, bounds, comment) {
881
+ const base2 = createCustomShapeItem(id, bounds, {
882
+ render: () => commentAnchorInnerSvg(comment.authorColor)
883
+ });
884
+ return {
885
+ ...base2,
886
+ pluginData: {
887
+ ...base2.pluginData ?? {},
888
+ [COMMENT_PLUGIN_DATA_KEY]: {
889
+ kind: "comment",
890
+ ...comment
891
+ }
892
+ }
893
+ };
894
+ }
895
+ function getStoredRealtimeCommentData(item) {
896
+ const pluginData = item.pluginData?.[COMMENT_PLUGIN_DATA_KEY];
897
+ if (!isRecord2(pluginData)) return null;
898
+ const kind = getString2(pluginData.kind);
899
+ if (kind === "draft") {
900
+ return {
901
+ kind,
902
+ ...getString2(pluginData.seedColor) ? { seedColor: getString2(pluginData.seedColor) } : {}
903
+ };
904
+ }
905
+ if (kind !== "comment") return null;
906
+ const body = getString2(pluginData.body);
907
+ const createdAt = getNumber2(pluginData.createdAt);
908
+ const authorPeerId = getString2(pluginData.authorPeerId);
909
+ const authorDisplayName = getString2(pluginData.authorDisplayName);
910
+ const authorColor = getString2(pluginData.authorColor);
911
+ const authorImage = getString2(pluginData.authorImage);
912
+ if (!body || createdAt == null || !authorPeerId || !authorDisplayName || !authorColor || !authorImage) {
913
+ return null;
914
+ }
915
+ return {
916
+ kind,
917
+ body,
918
+ createdAt,
919
+ authorPeerId,
920
+ authorDisplayName,
921
+ authorColor,
922
+ authorImage
923
+ };
924
+ }
925
+ function isRealtimeCommentDraftItem(item) {
926
+ return getStoredRealtimeCommentData(item)?.kind === "draft";
927
+ }
928
+ function isRealtimeCommentItem(item) {
929
+ return getStoredRealtimeCommentData(item)?.kind === "comment";
930
+ }
931
+ function getRealtimeCommentData(item) {
932
+ const data = getStoredRealtimeCommentData(item);
933
+ return data?.kind === "comment" ? data : null;
934
+ }
935
+ function withRealtimeCommentTool(tools) {
936
+ if (tools.some((tool) => tool.id === REALTIME_COMMENT_TOOL.id)) {
937
+ return tools.map(
938
+ (tool) => tool.id === REALTIME_COMMENT_TOOL.id ? { ...tool, ...REALTIME_COMMENT_TOOL } : tool
939
+ );
940
+ }
941
+ const textIndex = tools.findIndex((tool) => tool.id === "text");
942
+ if (textIndex < 0) return [...tools, REALTIME_COMMENT_TOOL];
943
+ return [
944
+ ...tools.slice(0, textIndex + 1),
945
+ REALTIME_COMMENT_TOOL,
946
+ ...tools.slice(textIndex + 1)
947
+ ];
948
+ }
949
+ var overlayShell = {
950
+ position: "absolute",
951
+ inset: 0,
952
+ pointerEvents: "none",
953
+ zIndex: 26,
954
+ overflow: "hidden"
955
+ };
956
+ function formatCommentTimestamp(timestamp) {
957
+ return new Intl.DateTimeFormat("pt-BR", {
958
+ dateStyle: "short",
959
+ timeStyle: "short"
960
+ }).format(new Date(timestamp));
961
+ }
962
+ function RealtimeCommentsOverlay({
963
+ items,
964
+ onItemsChange,
965
+ isDragEnabled = false,
966
+ cameraVersion,
967
+ viewportRef
968
+ }) {
969
+ const [hoveredId, setHoveredId] = react.useState(null);
970
+ const [draggedId, setDraggedId] = react.useState(null);
971
+ const dragStateRef = react.useRef(null);
972
+ const itemsRef = react.useRef(items);
973
+ react.useEffect(() => {
974
+ itemsRef.current = items;
975
+ }, [items]);
976
+ const camera = viewportRef?.current?.getCamera() ?? null;
977
+ if (!camera) return null;
978
+ function moveCommentItem(item, deltaX, deltaY) {
979
+ return {
980
+ ...item,
981
+ x: item.x + deltaX,
982
+ y: item.y + deltaY,
983
+ bounds: {
984
+ ...item.bounds,
985
+ x: item.bounds.x + deltaX,
986
+ y: item.bounds.y + deltaY
987
+ }
988
+ };
989
+ }
990
+ function handleCommentPointerDown(itemId, event) {
991
+ if (event.button !== 0 || !onItemsChange || !isDragEnabled) return;
992
+ event.preventDefault();
993
+ event.stopPropagation();
994
+ const currentCamera = viewportRef?.current?.getCamera();
995
+ if (!currentCamera) return;
996
+ const start = currentCamera.screenToWorld(event.clientX, event.clientY);
997
+ dragStateRef.current = {
998
+ itemId,
999
+ lastWorldX: start.worldX,
1000
+ lastWorldY: start.worldY
1001
+ };
1002
+ setDraggedId(itemId);
1003
+ setHoveredId((current) => current === itemId ? null : current);
1004
+ const onPointerMove = (moveEvent) => {
1005
+ const drag = dragStateRef.current;
1006
+ const liveCamera = viewportRef?.current?.getCamera();
1007
+ if (!drag || !liveCamera) return;
1008
+ const next = liveCamera.screenToWorld(moveEvent.clientX, moveEvent.clientY);
1009
+ const deltaX = next.worldX - drag.lastWorldX;
1010
+ const deltaY = next.worldY - drag.lastWorldY;
1011
+ if (deltaX === 0 && deltaY === 0) return;
1012
+ drag.lastWorldX = next.worldX;
1013
+ drag.lastWorldY = next.worldY;
1014
+ const nextItems = itemsRef.current.map(
1015
+ (item) => item.id === drag.itemId ? moveCommentItem(item, deltaX, deltaY) : item
1016
+ );
1017
+ itemsRef.current = nextItems;
1018
+ onItemsChange(nextItems);
1019
+ };
1020
+ const onPointerUp = () => {
1021
+ dragStateRef.current = null;
1022
+ setDraggedId((current) => current === itemId ? null : current);
1023
+ window.removeEventListener("pointermove", onPointerMove);
1024
+ window.removeEventListener("pointerup", onPointerUp);
1025
+ window.removeEventListener("pointercancel", onPointerUp);
1026
+ };
1027
+ window.addEventListener("pointermove", onPointerMove);
1028
+ window.addEventListener("pointerup", onPointerUp);
1029
+ window.addEventListener("pointercancel", onPointerUp);
1030
+ }
1031
+ const comments = items.map((item) => ({ item, data: getRealtimeCommentData(item) })).filter(
1032
+ (entry) => entry.data != null
1033
+ );
1034
+ if (comments.length === 0) return null;
1035
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlayShell, "data-slot": "realtime-comments-overlay", children: comments.map(({ item, data }) => {
1036
+ const anchor = camera.worldToScreen(
1037
+ item.bounds.x + item.bounds.width / 2,
1038
+ item.bounds.y + item.bounds.height / 2
1039
+ );
1040
+ const dragging = draggedId === item.id;
1041
+ const expanded = hoveredId === item.id && !dragging;
1042
+ return /* @__PURE__ */ jsxRuntime.jsx(
1043
+ "button",
1044
+ {
1045
+ type: "button",
1046
+ "aria-label": `Comment by ${data.authorDisplayName}`,
1047
+ onMouseEnter: () => setHoveredId(item.id),
1048
+ onMouseLeave: () => setHoveredId((current) => current === item.id ? null : current),
1049
+ onPointerDown: (event) => handleCommentPointerDown(item.id, event),
1050
+ style: {
1051
+ background: "transparent",
1052
+ border: 0,
1053
+ padding: 0,
1054
+ position: "absolute",
1055
+ left: anchor.screenX,
1056
+ top: anchor.screenY,
1057
+ transform: "translate(-50%, -50%)",
1058
+ pointerEvents: "auto",
1059
+ cursor: onItemsChange && isDragEnabled ? dragging ? "grabbing" : "grab" : "default"
1060
+ },
1061
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1062
+ "div",
1063
+ {
1064
+ style: {
1065
+ display: "flex",
1066
+ alignItems: expanded ? "flex-start" : "center",
1067
+ gap: expanded ? 9 : 0,
1068
+ width: expanded ? 228 : 44,
1069
+ minHeight: 44,
1070
+ padding: expanded ? "8px 10px 8px 8px" : 3,
1071
+ borderRadius: expanded ? 18 : 999,
1072
+ background: expanded ? "rgba(255,255,255,0.97)" : "transparent",
1073
+ border: expanded ? "1px solid rgba(148,163,184,0.28)" : "1px solid transparent",
1074
+ boxShadow: expanded ? "0 14px 32px rgba(15,23,42,0.18)" : "none",
1075
+ backdropFilter: "blur(10px)",
1076
+ transition: "width 160ms ease, border-radius 160ms ease, transform 160ms ease, box-shadow 160ms ease, padding 160ms ease",
1077
+ transform: dragging ? "scale(0.98)" : expanded ? "translateY(-2px)" : "translateY(0)",
1078
+ overflow: "hidden"
1079
+ },
1080
+ children: [
1081
+ /* @__PURE__ */ jsxRuntime.jsxs(
1082
+ "div",
1083
+ {
1084
+ style: {
1085
+ position: "relative",
1086
+ width: 36,
1087
+ height: 36,
1088
+ borderRadius: 999,
1089
+ flexShrink: 0,
1090
+ boxShadow: expanded ? `0 0 0 2px ${data.authorColor}22` : "0 10px 18px rgba(15,23,42,0.14)",
1091
+ border: expanded ? "none" : "2px solid rgba(255,255,255,0.92)",
1092
+ overflow: "hidden"
1093
+ },
1094
+ children: [
1095
+ /* @__PURE__ */ jsxRuntime.jsx(
1096
+ "img",
1097
+ {
1098
+ src: data.authorImage,
1099
+ alt: data.authorDisplayName,
1100
+ style: { width: "100%", height: "100%", objectFit: "cover" }
1101
+ }
1102
+ ),
1103
+ /* @__PURE__ */ jsxRuntime.jsx(
1104
+ "div",
1105
+ {
1106
+ style: {
1107
+ position: "absolute",
1108
+ right: expanded ? -1 : -2,
1109
+ bottom: expanded ? -1 : -2,
1110
+ width: 14,
1111
+ height: 14,
1112
+ borderRadius: 999,
1113
+ background: "#111827",
1114
+ color: "#fff",
1115
+ display: "grid",
1116
+ placeItems: "center",
1117
+ border: "1.5px solid rgba(255,255,255,0.98)",
1118
+ boxShadow: "0 4px 10px rgba(15,23,42,0.16)"
1119
+ },
1120
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { size: 8, strokeWidth: 2.2, "aria-hidden": true })
1121
+ }
1122
+ )
1123
+ ]
1124
+ }
1125
+ ),
1126
+ expanded ? /* @__PURE__ */ jsxRuntime.jsxs(
1127
+ "div",
1128
+ {
1129
+ style: {
1130
+ display: "grid",
1131
+ gap: 3,
1132
+ minWidth: 0,
1133
+ paddingTop: 1
1134
+ },
1135
+ children: [
1136
+ /* @__PURE__ */ jsxRuntime.jsxs(
1137
+ "div",
1138
+ {
1139
+ style: {
1140
+ display: "flex",
1141
+ justifyContent: "space-between",
1142
+ gap: 8,
1143
+ alignItems: "baseline"
1144
+ },
1145
+ children: [
1146
+ /* @__PURE__ */ jsxRuntime.jsx(
1147
+ "div",
1148
+ {
1149
+ style: {
1150
+ fontSize: 12,
1151
+ fontWeight: 700,
1152
+ color: "#0f172a",
1153
+ overflow: "hidden",
1154
+ textOverflow: "ellipsis",
1155
+ whiteSpace: "nowrap"
1156
+ },
1157
+ children: data.authorDisplayName
1158
+ }
1159
+ ),
1160
+ /* @__PURE__ */ jsxRuntime.jsx(
1161
+ "div",
1162
+ {
1163
+ style: {
1164
+ fontSize: 10,
1165
+ color: "#64748b",
1166
+ whiteSpace: "nowrap"
1167
+ },
1168
+ children: formatCommentTimestamp(data.createdAt)
1169
+ }
1170
+ )
1171
+ ]
1172
+ }
1173
+ ),
1174
+ /* @__PURE__ */ jsxRuntime.jsx(
1175
+ "div",
1176
+ {
1177
+ style: {
1178
+ fontSize: 11,
1179
+ lineHeight: 1.4,
1180
+ color: "#334155",
1181
+ display: "-webkit-box",
1182
+ WebkitLineClamp: 3,
1183
+ WebkitBoxOrient: "vertical",
1184
+ overflow: "hidden"
1185
+ },
1186
+ children: data.body
1187
+ }
1188
+ )
1189
+ ]
1190
+ }
1191
+ ) : null
1192
+ ]
1193
+ }
1194
+ )
1195
+ },
1196
+ item.id
1197
+ );
1198
+ }) });
1199
+ }
1200
+ function realtimeCommentsPlugin(options) {
1201
+ return {
1202
+ id: "trazo.plugin.realtime-comments",
1203
+ render: (ctx) => /* @__PURE__ */ jsxRuntime.jsx(RealtimeCommentsOverlay, { ...options, viewportRef: ctx.viewportRef })
1204
+ };
1205
+ }
1206
+ var COMMENT_BUBBLE_WORLD_SIZE = 18;
1207
+ function clamp(value, min, max) {
1208
+ return Math.min(max, Math.max(min, value));
1209
+ }
1210
+ function createCommentId() {
1211
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1212
+ return crypto.randomUUID();
1213
+ }
1214
+ return `comment-${Math.random().toString(36).slice(2, 10)}`;
1215
+ }
1216
+ function RealtimeCommentComposer({
1217
+ viewportRef,
1218
+ cameraVersion,
1219
+ composer,
1220
+ author,
1221
+ authorImage,
1222
+ roomId,
1223
+ title,
1224
+ description,
1225
+ placeholder,
1226
+ cancelLabel,
1227
+ submitLabel,
1228
+ submitHint,
1229
+ onBodyChange,
1230
+ onCommit,
1231
+ onCancel
1232
+ }) {
1233
+ const commentInputRef = react.useRef(null);
1234
+ const composerRef = react.useRef(null);
1235
+ const [composerSize, setComposerSize] = react.useState({ width: 360, height: 320 });
1236
+ react.useEffect(() => {
1237
+ if (!composer) return;
1238
+ commentInputRef.current?.focus();
1239
+ }, [composer]);
1240
+ const camera = viewportRef?.current?.getCamera();
1241
+ const viewportSize = viewportRef?.current?.getViewportSize();
1242
+ react.useLayoutEffect(() => {
1243
+ if (!composer || !viewportSize) return;
1244
+ const el = composerRef.current;
1245
+ if (!el) return;
1246
+ const updateSize = () => {
1247
+ const next = {
1248
+ width: el.offsetWidth,
1249
+ height: el.offsetHeight
1250
+ };
1251
+ setComposerSize(
1252
+ (current) => current.width === next.width && current.height === next.height ? current : next
1253
+ );
1254
+ };
1255
+ updateSize();
1256
+ if (typeof ResizeObserver === "undefined") {
1257
+ return;
1258
+ }
1259
+ const observer = new ResizeObserver(updateSize);
1260
+ observer.observe(el);
1261
+ return () => observer.disconnect();
1262
+ }, [composer, viewportSize]);
1263
+ if (!composer) return null;
1264
+ if (!camera || !viewportSize) return null;
1265
+ const anchor = camera.worldToScreen(composer.worldX, composer.worldY);
1266
+ const composerWidth = Math.min(360, Math.max(220, viewportSize.width - 32));
1267
+ const screenPosition = {
1268
+ left: clamp(
1269
+ anchor.screenX + 20,
1270
+ 16,
1271
+ Math.max(16, viewportSize.width - composerSize.width - 16)
1272
+ ),
1273
+ top: clamp(
1274
+ anchor.screenY - 22,
1275
+ 16,
1276
+ Math.max(16, viewportSize.height - composerSize.height - 16)
1277
+ )
1278
+ };
1279
+ function handleKeyDown(event) {
1280
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1281
+ event.preventDefault();
1282
+ onCommit();
1283
+ return;
1284
+ }
1285
+ if (event.key === "Escape") {
1286
+ event.preventDefault();
1287
+ onCancel();
1288
+ }
1289
+ }
1290
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1291
+ "div",
1292
+ {
1293
+ ref: composerRef,
1294
+ style: {
1295
+ position: "absolute",
1296
+ left: screenPosition.left,
1297
+ top: screenPosition.top,
1298
+ zIndex: 44,
1299
+ width: composerWidth,
1300
+ maxWidth: "calc(100% - 32px)",
1301
+ maxHeight: "calc(100% - 32px)",
1302
+ padding: 18,
1303
+ borderRadius: 22,
1304
+ background: "rgba(255,255,255,0.98)",
1305
+ border: "1px solid rgba(148,163,184,0.28)",
1306
+ boxShadow: "0 22px 48px rgba(15,23,42,0.18)",
1307
+ backdropFilter: "blur(12px)",
1308
+ pointerEvents: "auto",
1309
+ boxSizing: "border-box",
1310
+ overflow: "auto"
1311
+ },
1312
+ children: [
1313
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700, color: "#0f172a" }, children: title }),
1314
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 6, color: "#64748b", fontSize: 13, lineHeight: 1.5 }, children: description }),
1315
+ /* @__PURE__ */ jsxRuntime.jsxs(
1316
+ "div",
1317
+ {
1318
+ style: {
1319
+ display: "flex",
1320
+ alignItems: "center",
1321
+ gap: 10,
1322
+ marginTop: 14,
1323
+ marginBottom: 12
1324
+ },
1325
+ children: [
1326
+ /* @__PURE__ */ jsxRuntime.jsx(
1327
+ "img",
1328
+ {
1329
+ src: authorImage,
1330
+ alt: author.displayName,
1331
+ style: {
1332
+ width: 38,
1333
+ height: 38,
1334
+ borderRadius: 999,
1335
+ objectFit: "cover",
1336
+ boxShadow: `0 0 0 2px ${author.color}22`,
1337
+ flexShrink: 0
1338
+ }
1339
+ }
1340
+ ),
1341
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0 }, children: [
1342
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 700, fontSize: 14, color: "#0f172a" }, children: author.displayName }),
1343
+ roomId ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#64748b", fontSize: 12.5 }, children: [
1344
+ "Sala ",
1345
+ roomId
1346
+ ] }) : null
1347
+ ] })
1348
+ ]
1349
+ }
1350
+ ),
1351
+ /* @__PURE__ */ jsxRuntime.jsx(
1352
+ "textarea",
1353
+ {
1354
+ ref: commentInputRef,
1355
+ value: composer.body,
1356
+ onChange: (event) => onBodyChange(event.target.value),
1357
+ onKeyDown: handleKeyDown,
1358
+ placeholder,
1359
+ style: {
1360
+ width: "100%",
1361
+ minHeight: 116,
1362
+ padding: "12px 14px",
1363
+ borderRadius: 14,
1364
+ border: "1px solid #cbd5e1",
1365
+ boxSizing: "border-box",
1366
+ fontFamily: "inherit",
1367
+ fontSize: 15,
1368
+ lineHeight: 1.5,
1369
+ color: "#0f172a",
1370
+ resize: "vertical"
1371
+ }
1372
+ }
1373
+ ),
1374
+ /* @__PURE__ */ jsxRuntime.jsxs(
1375
+ "div",
1376
+ {
1377
+ style: {
1378
+ display: "flex",
1379
+ justifyContent: "space-between",
1380
+ alignItems: "flex-end",
1381
+ flexWrap: "wrap",
1382
+ gap: 10,
1383
+ marginTop: 12
1384
+ },
1385
+ children: [
1386
+ /* @__PURE__ */ jsxRuntime.jsx(
1387
+ "div",
1388
+ {
1389
+ style: {
1390
+ color: "#64748b",
1391
+ fontSize: 11.5,
1392
+ lineHeight: 1.4,
1393
+ flex: "1 1 120px"
1394
+ },
1395
+ children: submitHint
1396
+ }
1397
+ ),
1398
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, flexShrink: 0 }, children: [
1399
+ /* @__PURE__ */ jsxRuntime.jsx(
1400
+ "button",
1401
+ {
1402
+ type: "button",
1403
+ onClick: onCancel,
1404
+ style: {
1405
+ padding: "10px 14px",
1406
+ borderRadius: 12,
1407
+ border: "1px solid #cbd5e1",
1408
+ background: "#fff",
1409
+ fontFamily: "inherit",
1410
+ fontSize: 14,
1411
+ fontWeight: 600,
1412
+ whiteSpace: "nowrap",
1413
+ cursor: "pointer"
1414
+ },
1415
+ children: cancelLabel
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ jsxRuntime.jsx(
1419
+ "button",
1420
+ {
1421
+ type: "button",
1422
+ onClick: onCommit,
1423
+ disabled: !composer.body.trim(),
1424
+ style: {
1425
+ padding: "10px 14px",
1426
+ borderRadius: 12,
1427
+ border: "1px solid #6d28d9",
1428
+ background: composer.body.trim() ? "#7c3aed" : "#c4b5fd",
1429
+ color: "#fff",
1430
+ fontFamily: "inherit",
1431
+ fontSize: 14,
1432
+ fontWeight: 600,
1433
+ whiteSpace: "nowrap",
1434
+ cursor: composer.body.trim() ? "pointer" : "not-allowed",
1435
+ opacity: composer.body.trim() ? 1 : 0.72
1436
+ },
1437
+ children: submitLabel
1438
+ }
1439
+ )
1440
+ ] })
1441
+ ]
1442
+ }
1443
+ )
1444
+ ]
1445
+ }
1446
+ );
1447
+ }
1448
+ var overlayShell2 = {
1449
+ position: "absolute",
1450
+ inset: 0,
1451
+ pointerEvents: "none",
1452
+ zIndex: 26,
1453
+ overflow: "hidden"
1454
+ };
1455
+ function renderRealtimeCommentsOverlayNode(options, viewportRef) {
1456
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: overlayShell2, children: [
1457
+ /* @__PURE__ */ jsxRuntime.jsx(
1458
+ RealtimeCommentsOverlay,
1459
+ {
1460
+ items: options.items,
1461
+ onItemsChange: options.onItemsChange,
1462
+ isDragEnabled: options.isDragEnabled,
1463
+ cameraVersion: options.cameraVersion,
1464
+ viewportRef
1465
+ }
1466
+ ),
1467
+ /* @__PURE__ */ jsxRuntime.jsx(
1468
+ RealtimeCommentComposer,
1469
+ {
1470
+ viewportRef,
1471
+ cameraVersion: options.cameraVersion,
1472
+ composer: options.composer,
1473
+ author: options.author,
1474
+ authorImage: options.authorImage,
1475
+ roomId: options.roomId,
1476
+ title: options.title,
1477
+ description: options.description,
1478
+ placeholder: options.placeholder,
1479
+ cancelLabel: options.cancelLabel,
1480
+ submitLabel: options.submitLabel,
1481
+ submitHint: options.submitHint,
1482
+ onBodyChange: options.onBodyChange,
1483
+ onCommit: options.onCommit,
1484
+ onCancel: options.onCancel
1485
+ }
1486
+ )
1487
+ ] });
1488
+ }
1489
+ function useRealtimeComments({
1490
+ items,
1491
+ onItemsChange,
1492
+ author,
1493
+ viewportRef,
1494
+ activeToolId,
1495
+ roomId,
1496
+ setToolId,
1497
+ toolAfterDraft = "select",
1498
+ tools: providedTools = DEFAULT_VECTOR_TOOLS,
1499
+ composerTitle = "Novo coment\xE1rio",
1500
+ composerDescription = "Escreva algo para a equipe. Ao enviar, a bolha sincroniza na sala.",
1501
+ composerPlaceholder = "Contexto, decisao, duvida, feedback...",
1502
+ cancelLabel = "Cancelar",
1503
+ submitLabel = "Publicar coment\xE1rio",
1504
+ submitHint = "`Ctrl/Cmd + Enter` envia"
1505
+ }) {
1506
+ const [cameraVersion, setCameraVersion] = react.useState(0);
1507
+ const [commentComposer, setCommentComposer] = react.useState(null);
1508
+ const authorImage = react.useMemo(
1509
+ () => author.image ?? createRealtimeCommentAvatarDataUrl(author.displayName, author.color),
1510
+ [author.color, author.displayName, author.image]
1511
+ );
1512
+ const tools = react.useMemo(
1513
+ () => withRealtimeCommentTool(providedTools),
1514
+ [providedTools]
1515
+ );
1516
+ const customPlacement = react.useMemo(
1517
+ () => ({
1518
+ toolId: "comment",
1519
+ createItem: ({ id, bounds }) => createRealtimeCommentDraftItem(id, bounds, author.color)
1520
+ }),
1521
+ [author.color]
1522
+ );
1523
+ const onViewportItemsChange = react.useCallback(
1524
+ (nextItems) => {
1525
+ const currentIds = new Set(items.map((item) => item.id));
1526
+ const draftItem = nextItems.find(
1527
+ (item) => !currentIds.has(item.id) && isRealtimeCommentDraftItem(item)
1528
+ );
1529
+ if (!draftItem) {
1530
+ onItemsChange(nextItems);
1531
+ return;
1532
+ }
1533
+ const filteredItems = nextItems.filter((item) => item.id !== draftItem.id);
1534
+ const shouldPersistFiltered = filteredItems.length !== items.length || filteredItems.some((item, index) => items[index]?.id !== item.id);
1535
+ if (shouldPersistFiltered) {
1536
+ onItemsChange(filteredItems);
1537
+ }
1538
+ setCommentComposer({
1539
+ worldX: draftItem.bounds.x + draftItem.bounds.width / 2,
1540
+ worldY: draftItem.bounds.y + draftItem.bounds.height / 2,
1541
+ body: ""
1542
+ });
1543
+ setToolId?.(toolAfterDraft);
1544
+ },
1545
+ [items, onItemsChange, setToolId, toolAfterDraft]
1546
+ );
1547
+ const onCameraChange = react.useCallback(() => {
1548
+ setCameraVersion((value) => value + 1);
1549
+ }, []);
1550
+ const closeComposer = react.useCallback(() => {
1551
+ setCommentComposer(null);
1552
+ }, []);
1553
+ const onComposerBodyChange = react.useCallback((body) => {
1554
+ setCommentComposer((current) => current ? { ...current, body } : current);
1555
+ }, []);
1556
+ const onComposerCommit = react.useCallback(() => {
1557
+ if (!commentComposer) return;
1558
+ const body = commentComposer.body.trim();
1559
+ if (!body) return;
1560
+ const half = COMMENT_BUBBLE_WORLD_SIZE / 2;
1561
+ const nextItem = createRealtimeCommentItem(
1562
+ createCommentId(),
1563
+ {
1564
+ x: commentComposer.worldX - half,
1565
+ y: commentComposer.worldY - half,
1566
+ width: COMMENT_BUBBLE_WORLD_SIZE,
1567
+ height: COMMENT_BUBBLE_WORLD_SIZE
1568
+ },
1569
+ {
1570
+ body,
1571
+ createdAt: Date.now(),
1572
+ authorPeerId: author.peerId,
1573
+ authorDisplayName: author.displayName,
1574
+ authorColor: author.color,
1575
+ authorImage
1576
+ }
1577
+ );
1578
+ onItemsChange(items.concat(nextItem));
1579
+ setCommentComposer(null);
1580
+ }, [
1581
+ author.color,
1582
+ author.displayName,
1583
+ author.peerId,
1584
+ authorImage,
1585
+ commentComposer,
1586
+ items,
1587
+ onItemsChange
1588
+ ]);
1589
+ const overlay = react.useMemo(
1590
+ () => renderRealtimeCommentsOverlayNode(
1591
+ {
1592
+ items,
1593
+ onItemsChange,
1594
+ isDragEnabled: activeToolId === "select",
1595
+ cameraVersion,
1596
+ composer: commentComposer,
1597
+ author,
1598
+ authorImage,
1599
+ roomId,
1600
+ title: composerTitle,
1601
+ description: composerDescription,
1602
+ placeholder: composerPlaceholder,
1603
+ cancelLabel,
1604
+ submitLabel,
1605
+ submitHint,
1606
+ onBodyChange: onComposerBodyChange,
1607
+ onCommit: onComposerCommit,
1608
+ onCancel: closeComposer
1609
+ },
1610
+ viewportRef
1611
+ ),
1612
+ [
1613
+ author,
1614
+ authorImage,
1615
+ cameraVersion,
1616
+ cancelLabel,
1617
+ closeComposer,
1618
+ commentComposer,
1619
+ composerDescription,
1620
+ composerPlaceholder,
1621
+ composerTitle,
1622
+ items,
1623
+ activeToolId,
1624
+ onItemsChange,
1625
+ onComposerBodyChange,
1626
+ onComposerCommit,
1627
+ roomId,
1628
+ submitHint,
1629
+ submitLabel,
1630
+ viewportRef
1631
+ ]
1632
+ );
1633
+ const viewport = react.useMemo(
1634
+ () => ({
1635
+ customPlacement,
1636
+ onItemsChange: onViewportItemsChange,
1637
+ onCameraChange
1638
+ }),
1639
+ [customPlacement, onCameraChange, onViewportItemsChange]
1640
+ );
1641
+ return react.useMemo(
1642
+ () => ({
1643
+ tools,
1644
+ overlay,
1645
+ viewport,
1646
+ isComposerOpen: commentComposer != null,
1647
+ closeComposer
1648
+ }),
1649
+ [closeComposer, commentComposer, overlay, tools, viewport]
1650
+ );
1651
+ }
1652
+ function createClientId() {
1653
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1654
+ return crypto.randomUUID();
1655
+ }
1656
+ return `client-${Math.random().toString(36).slice(2, 10)}`;
1657
+ }
1658
+ function normalizeSocketUrl(input) {
1659
+ if (input.startsWith("ws://") || input.startsWith("wss://")) return input;
1660
+ if (input.startsWith("http://")) return `ws://${input.slice("http://".length)}`;
1661
+ if (input.startsWith("https://")) return `wss://${input.slice("https://".length)}`;
1662
+ if (input.startsWith("/")) {
1663
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
1664
+ return `${protocol}//${window.location.host}${input}`;
1665
+ }
1666
+ return input;
1667
+ }
1668
+ function isValidSocketUrl(input) {
1669
+ if (!input || input.includes("<") || input.includes(">")) return false;
1670
+ try {
1671
+ const parsed = new URL(input);
1672
+ return parsed.protocol === "ws:" || parsed.protocol === "wss:";
1673
+ } catch {
1674
+ return false;
1675
+ }
1676
+ }
1677
+ function serializeItems(items) {
1678
+ try {
1679
+ return JSON.stringify(items);
1680
+ } catch {
1681
+ return null;
1682
+ }
1683
+ }
1684
+ function sameSerializedItems(left, right) {
1685
+ if (left === right) return true;
1686
+ if (!left || !right) return false;
1687
+ const leftJson = serializeItems(left);
1688
+ const rightJson = serializeItems(right);
1689
+ return leftJson != null && leftJson === rightJson;
1690
+ }
1691
+ function nowMs() {
1692
+ return Date.now();
1693
+ }
1694
+ function useRealtimeSession(options) {
1695
+ const {
1696
+ url,
1697
+ roomId,
1698
+ peer,
1699
+ enabled = true,
1700
+ reconnect = true,
1701
+ heartbeatMs = 1e4,
1702
+ maxReconnectDelayMs = 12e3,
1703
+ initialReconnectDelayMs = 800,
1704
+ connectTimeoutMs = 1e4,
1705
+ onError
1706
+ } = options;
1707
+ const clientIdRef = react.useRef(peer.clientId ?? createClientId());
1708
+ const wsRef = react.useRef(null);
1709
+ const reconnectTimerRef = react.useRef(null);
1710
+ const heartbeatTimerRef = react.useRef(null);
1711
+ const connectTimeoutRef = react.useRef(null);
1712
+ const manualDisconnectRef = react.useRef(false);
1713
+ const retryCountRef = react.useRef(0);
1714
+ const currentRevisionRef = react.useRef(0);
1715
+ const outboundInFlightRef = react.useRef(null);
1716
+ const queuedItemsRef = react.useRef(null);
1717
+ const subscriberRefs = react.useRef(/* @__PURE__ */ new Set());
1718
+ const lastCursorRef = react.useRef(null);
1719
+ const lastMarkupStrokeRef = react.useRef(null);
1720
+ const lastActiveToolRef = react.useRef(void 0);
1721
+ const latestDocumentRef = react.useRef(null);
1722
+ const connectionStateRef = react.useRef(
1723
+ enabled ? "connecting" : "offline"
1724
+ );
1725
+ const onErrorRef = react.useRef(onError);
1726
+ onErrorRef.current = onError;
1727
+ const [connectSequence, setConnectSequence] = react.useState(0);
1728
+ const [connection, setConnection] = react.useState({
1729
+ state: enabled ? "connecting" : "offline",
1730
+ connected: false,
1731
+ roomId,
1732
+ clientId: null,
1733
+ retryCount: 0,
1734
+ lastConnectedAt: null,
1735
+ lastMessageAt: null,
1736
+ lastPongAt: null,
1737
+ lastError: null
1738
+ });
1739
+ const [sessionPeers, setSessionPeers] = react.useState([]);
1740
+ const [document2, setDocument] = react.useState(null);
1741
+ connectionStateRef.current = connection.state;
1742
+ const clearReconnectTimer = react.useCallback(() => {
1743
+ if (reconnectTimerRef.current != null) {
1744
+ window.clearTimeout(reconnectTimerRef.current);
1745
+ reconnectTimerRef.current = null;
1746
+ }
1747
+ }, []);
1748
+ const clearHeartbeatTimer = react.useCallback(() => {
1749
+ if (heartbeatTimerRef.current != null) {
1750
+ window.clearInterval(heartbeatTimerRef.current);
1751
+ heartbeatTimerRef.current = null;
1752
+ }
1753
+ }, []);
1754
+ const clearConnectTimeout = react.useCallback(() => {
1755
+ if (connectTimeoutRef.current != null) {
1756
+ window.clearTimeout(connectTimeoutRef.current);
1757
+ connectTimeoutRef.current = null;
1758
+ }
1759
+ }, []);
1760
+ const updateConnection = react.useCallback(
1761
+ (patch) => {
1762
+ setConnection((prev) => {
1763
+ if (typeof patch === "function") return patch(prev);
1764
+ return { ...prev, ...patch };
1765
+ });
1766
+ },
1767
+ []
1768
+ );
1769
+ const notifySubscribers = react.useCallback((items) => {
1770
+ for (const subscriber of subscriberRefs.current) {
1771
+ subscriber(items);
1772
+ }
1773
+ }, []);
1774
+ const applyDocument = react.useCallback(
1775
+ (snapshot, options2) => {
1776
+ currentRevisionRef.current = snapshot.revision;
1777
+ latestDocumentRef.current = snapshot;
1778
+ setDocument(snapshot);
1779
+ if (!options2?.suppressSubscriberNotify) {
1780
+ notifySubscribers(snapshot.items);
1781
+ }
1782
+ },
1783
+ [notifySubscribers]
1784
+ );
1785
+ const applyPeers = react.useCallback((peers) => {
1786
+ const selfClientId = clientIdRef.current;
1787
+ setSessionPeers(
1788
+ peers.map(
1789
+ (peerState) => peerState.clientId === selfClientId ? {
1790
+ ...peerState,
1791
+ isSelf: true,
1792
+ connectionState: connectionStateRef.current
1793
+ } : peerState
1794
+ )
1795
+ );
1796
+ }, []);
1797
+ const buildClientMessage = react.useCallback(
1798
+ (message) => JSON.stringify(message),
1799
+ []
1800
+ );
1801
+ const sendRaw = react.useCallback(
1802
+ (message) => {
1803
+ const ws = wsRef.current;
1804
+ if (!ws || ws.readyState !== WebSocket.OPEN) return false;
1805
+ ws.send(buildClientMessage(message));
1806
+ return true;
1807
+ },
1808
+ [buildClientMessage]
1809
+ );
1810
+ const flushQueuedDocument = react.useCallback(() => {
1811
+ const next = queuedItemsRef.current;
1812
+ if (!next || outboundInFlightRef.current) return;
1813
+ queuedItemsRef.current = null;
1814
+ const baseRevision = currentRevisionRef.current;
1815
+ const serialized = serializeItems(next);
1816
+ outboundInFlightRef.current = { baseRevision, items: next, serialized };
1817
+ const didSend = sendRaw({
1818
+ type: "document:update",
1819
+ roomId,
1820
+ clientId: clientIdRef.current,
1821
+ baseRevision,
1822
+ items: next
1823
+ });
1824
+ if (!didSend) {
1825
+ queuedItemsRef.current = next;
1826
+ outboundInFlightRef.current = null;
1827
+ }
1828
+ }, [roomId, sendRaw]);
1829
+ const queueDocumentSend = react.useCallback(
1830
+ (items) => {
1831
+ queuedItemsRef.current = items;
1832
+ flushQueuedDocument();
1833
+ },
1834
+ [flushQueuedDocument]
1835
+ );
1836
+ const sendPresenceUpdate = react.useCallback(() => {
1837
+ sendRaw({
1838
+ type: "presence:update",
1839
+ roomId,
1840
+ clientId: clientIdRef.current,
1841
+ presence: {
1842
+ cursor: lastCursorRef.current,
1843
+ markupStroke: lastMarkupStrokeRef.current ?? null,
1844
+ ...lastActiveToolRef.current ? { activeTool: lastActiveToolRef.current } : {}
1845
+ }
1846
+ });
1847
+ }, [roomId, sendRaw]);
1848
+ const scheduleReconnect = react.useCallback(() => {
1849
+ if (!reconnect || manualDisconnectRef.current) return;
1850
+ clearReconnectTimer();
1851
+ retryCountRef.current += 1;
1852
+ const delay = Math.min(
1853
+ maxReconnectDelayMs,
1854
+ initialReconnectDelayMs * 2 ** Math.max(0, retryCountRef.current - 1)
1855
+ );
1856
+ updateConnection((prev) => ({
1857
+ ...prev,
1858
+ state: "reconnecting",
1859
+ connected: false,
1860
+ retryCount: retryCountRef.current
1861
+ }));
1862
+ reconnectTimerRef.current = window.setTimeout(() => {
1863
+ setConnectSequence((value) => value + 1);
1864
+ }, delay);
1865
+ }, [
1866
+ clearReconnectTimer,
1867
+ initialReconnectDelayMs,
1868
+ maxReconnectDelayMs,
1869
+ reconnect,
1870
+ updateConnection
1871
+ ]);
1872
+ react.useEffect(() => {
1873
+ if (!enabled) {
1874
+ manualDisconnectRef.current = true;
1875
+ clearReconnectTimer();
1876
+ clearHeartbeatTimer();
1877
+ clearConnectTimeout();
1878
+ wsRef.current?.close();
1879
+ wsRef.current = null;
1880
+ setSessionPeers([]);
1881
+ queuedItemsRef.current = null;
1882
+ outboundInFlightRef.current = null;
1883
+ updateConnection((prev) => ({
1884
+ ...prev,
1885
+ state: "offline",
1886
+ connected: false,
1887
+ roomId,
1888
+ clientId: null
1889
+ }));
1890
+ return;
1891
+ }
1892
+ manualDisconnectRef.current = false;
1893
+ const socketUrl = normalizeSocketUrl(url);
1894
+ if (!isValidSocketUrl(socketUrl)) {
1895
+ updateConnection((prev) => ({
1896
+ ...prev,
1897
+ state: "error",
1898
+ connected: false,
1899
+ roomId,
1900
+ lastError: "URL de websocket invalida."
1901
+ }));
1902
+ onErrorRef.current?.("URL de websocket invalida.");
1903
+ return;
1904
+ }
1905
+ let disposed = false;
1906
+ updateConnection((prev) => ({
1907
+ ...prev,
1908
+ state: retryCountRef.current > 0 ? "reconnecting" : "connecting",
1909
+ connected: false,
1910
+ roomId,
1911
+ lastError: null
1912
+ }));
1913
+ const socket = new WebSocket(socketUrl);
1914
+ wsRef.current = socket;
1915
+ clearConnectTimeout();
1916
+ connectTimeoutRef.current = window.setTimeout(() => {
1917
+ if (socket.readyState === WebSocket.CONNECTING) {
1918
+ socket.close();
1919
+ }
1920
+ }, connectTimeoutMs);
1921
+ socket.addEventListener("open", () => {
1922
+ if (disposed) return;
1923
+ clearConnectTimeout();
1924
+ sendRaw({
1925
+ type: "session:join",
1926
+ roomId,
1927
+ peer: {
1928
+ clientId: clientIdRef.current,
1929
+ peerId: peer.id,
1930
+ ...peer.displayName ? { displayName: peer.displayName } : {},
1931
+ ...peer.color ? { color: peer.color } : {},
1932
+ ...peer.image ? { image: peer.image } : {}
1933
+ }
1934
+ });
1935
+ clearHeartbeatTimer();
1936
+ heartbeatTimerRef.current = window.setInterval(() => {
1937
+ sendRaw({
1938
+ type: "session:ping",
1939
+ roomId,
1940
+ clientId: clientIdRef.current,
1941
+ sentAt: nowMs()
1942
+ });
1943
+ }, heartbeatMs);
1944
+ });
1945
+ socket.addEventListener("message", (event) => {
1946
+ if (disposed) return;
1947
+ let payload = event.data;
1948
+ if (typeof event.data === "string") {
1949
+ try {
1950
+ payload = JSON.parse(event.data);
1951
+ } catch {
1952
+ return;
1953
+ }
1954
+ }
1955
+ const parsed = parseRealtimeServerMessage(payload);
1956
+ if (!parsed) return;
1957
+ updateConnection((prev) => ({
1958
+ ...prev,
1959
+ lastMessageAt: nowMs()
1960
+ }));
1961
+ if (parsed.type === "session:welcome") {
1962
+ retryCountRef.current = 0;
1963
+ const queuedBeforeWelcome = queuedItemsRef.current;
1964
+ const shouldPromoteQueuedLocalDraft = queuedBeforeWelcome != null && parsed.document.revision === 0 && parsed.document.items.length === 0;
1965
+ updateConnection((prev) => ({
1966
+ ...prev,
1967
+ state: "connected",
1968
+ connected: true,
1969
+ clientId: parsed.clientId,
1970
+ retryCount: 0,
1971
+ lastConnectedAt: nowMs(),
1972
+ lastError: null
1973
+ }));
1974
+ applyPeers(parsed.peers);
1975
+ applyDocument(parsed.document, {
1976
+ suppressSubscriberNotify: shouldPromoteQueuedLocalDraft
1977
+ });
1978
+ if (!shouldPromoteQueuedLocalDraft) {
1979
+ queuedItemsRef.current = null;
1980
+ outboundInFlightRef.current = null;
1981
+ }
1982
+ flushQueuedDocument();
1983
+ return;
1984
+ }
1985
+ if (parsed.type === "presence:sync") {
1986
+ applyPeers(parsed.peers);
1987
+ return;
1988
+ }
1989
+ if (parsed.type === "session:peer-joined") {
1990
+ setSessionPeers((prev) => {
1991
+ const next = prev.filter(
1992
+ (peerState) => peerState.clientId !== parsed.peer.clientId
1993
+ );
1994
+ next.push(
1995
+ parsed.peer.clientId === clientIdRef.current ? { ...parsed.peer, isSelf: true } : parsed.peer
1996
+ );
1997
+ return next;
1998
+ });
1999
+ return;
2000
+ }
2001
+ if (parsed.type === "session:peer-left") {
2002
+ setSessionPeers(
2003
+ (prev) => prev.filter((peerState) => peerState.clientId !== parsed.clientId)
2004
+ );
2005
+ return;
2006
+ }
2007
+ if (parsed.type === "session:pong") {
2008
+ updateConnection((prev) => ({
2009
+ ...prev,
2010
+ lastPongAt: parsed.serverTime
2011
+ }));
2012
+ return;
2013
+ }
2014
+ if (parsed.type === "session:error") {
2015
+ updateConnection((prev) => ({
2016
+ ...prev,
2017
+ state: prev.connected ? prev.state : "error",
2018
+ lastError: parsed.message
2019
+ }));
2020
+ onErrorRef.current?.(parsed.message);
2021
+ return;
2022
+ }
2023
+ if (parsed.type === "document:sync") {
2024
+ const selfClientId = clientIdRef.current;
2025
+ const inFlight = outboundInFlightRef.current;
2026
+ const isSelfAck = parsed.document.updatedByClientId === selfClientId;
2027
+ if (!isSelfAck) {
2028
+ outboundInFlightRef.current = null;
2029
+ queuedItemsRef.current = null;
2030
+ applyDocument(parsed.document);
2031
+ return;
2032
+ }
2033
+ const shouldSuppress = inFlight != null && sameSerializedItems(inFlight.items, parsed.document.items);
2034
+ outboundInFlightRef.current = null;
2035
+ applyDocument(parsed.document, { suppressSubscriberNotify: shouldSuppress });
2036
+ if (queuedItemsRef.current) {
2037
+ if (sameSerializedItems(queuedItemsRef.current, parsed.document.items)) {
2038
+ queuedItemsRef.current = null;
2039
+ } else {
2040
+ flushQueuedDocument();
2041
+ }
2042
+ }
2043
+ return;
2044
+ }
2045
+ if (parsed.type === "document:resync-required") {
2046
+ outboundInFlightRef.current = null;
2047
+ queuedItemsRef.current = null;
2048
+ updateConnection((prev) => ({
2049
+ ...prev,
2050
+ lastError: parsed.reason
2051
+ }));
2052
+ applyDocument(parsed.document);
2053
+ }
2054
+ });
2055
+ socket.addEventListener("error", () => {
2056
+ if (disposed) return;
2057
+ updateConnection((prev) => ({
2058
+ ...prev,
2059
+ state: prev.connected ? prev.state : "error",
2060
+ lastError: "Falha de conex\xE3o websocket."
2061
+ }));
2062
+ });
2063
+ socket.addEventListener("close", () => {
2064
+ if (disposed) return;
2065
+ clearHeartbeatTimer();
2066
+ clearConnectTimeout();
2067
+ wsRef.current = null;
2068
+ updateConnection((prev) => ({
2069
+ ...prev,
2070
+ connected: false,
2071
+ clientId: prev.clientId,
2072
+ state: manualDisconnectRef.current || !enabled ? "offline" : prev.state
2073
+ }));
2074
+ if (!manualDisconnectRef.current && enabled) {
2075
+ scheduleReconnect();
2076
+ }
2077
+ });
2078
+ return () => {
2079
+ disposed = true;
2080
+ clearReconnectTimer();
2081
+ clearHeartbeatTimer();
2082
+ clearConnectTimeout();
2083
+ socket.close();
2084
+ };
2085
+ }, [
2086
+ applyDocument,
2087
+ applyPeers,
2088
+ clearConnectTimeout,
2089
+ clearHeartbeatTimer,
2090
+ clearReconnectTimer,
2091
+ connectSequence,
2092
+ connectTimeoutMs,
2093
+ enabled,
2094
+ flushQueuedDocument,
2095
+ heartbeatMs,
2096
+ peer.color,
2097
+ peer.displayName,
2098
+ peer.image,
2099
+ peer.id,
2100
+ roomId,
2101
+ scheduleReconnect,
2102
+ sendRaw,
2103
+ updateConnection,
2104
+ url
2105
+ ]);
2106
+ react.useEffect(() => {
2107
+ setSessionPeers(
2108
+ (prev) => prev.map(
2109
+ (peerState) => peerState.clientId === clientIdRef.current ? {
2110
+ ...peerState,
2111
+ isSelf: true,
2112
+ connectionState: connection.state
2113
+ } : peerState
2114
+ )
2115
+ );
2116
+ }, [connection.state]);
2117
+ const remoteAdapter = react.useMemo(
2118
+ () => ({
2119
+ subscribe(onItems) {
2120
+ subscriberRefs.current.add(onItems);
2121
+ if (latestDocumentRef.current) {
2122
+ onItems(latestDocumentRef.current.items);
2123
+ }
2124
+ return () => {
2125
+ subscriberRefs.current.delete(onItems);
2126
+ };
2127
+ },
2128
+ send(items) {
2129
+ queueDocumentSend(items);
2130
+ }
2131
+ }),
2132
+ [queueDocumentSend]
2133
+ );
2134
+ const disconnect = react.useCallback(() => {
2135
+ manualDisconnectRef.current = true;
2136
+ clearReconnectTimer();
2137
+ clearHeartbeatTimer();
2138
+ clearConnectTimeout();
2139
+ sendRaw({
2140
+ type: "session:leave",
2141
+ roomId,
2142
+ clientId: clientIdRef.current
2143
+ });
2144
+ wsRef.current?.close();
2145
+ wsRef.current = null;
2146
+ updateConnection((prev) => ({
2147
+ ...prev,
2148
+ state: "offline",
2149
+ connected: false
2150
+ }));
2151
+ }, [
2152
+ clearConnectTimeout,
2153
+ clearHeartbeatTimer,
2154
+ clearReconnectTimer,
2155
+ roomId,
2156
+ sendRaw,
2157
+ updateConnection
2158
+ ]);
2159
+ const reconnectNow = react.useCallback(() => {
2160
+ disconnect();
2161
+ manualDisconnectRef.current = false;
2162
+ retryCountRef.current = 0;
2163
+ setConnectSequence((value) => value + 1);
2164
+ }, [disconnect]);
2165
+ const remotePresence = react.useMemo(
2166
+ () => sessionPeers.filter((peerState) => !peerState.isSelf),
2167
+ [sessionPeers]
2168
+ );
2169
+ const bindViewportPresence = react.useCallback(
2170
+ (bindingOptions) => ({
2171
+ remotePresence,
2172
+ onWorldPointerMove(world) {
2173
+ lastCursorRef.current = world;
2174
+ lastActiveToolRef.current = bindingOptions?.activeTool;
2175
+ sendPresenceUpdate();
2176
+ },
2177
+ onWorldPointerLeave() {
2178
+ lastCursorRef.current = null;
2179
+ lastActiveToolRef.current = bindingOptions?.activeTool;
2180
+ sendPresenceUpdate();
2181
+ },
2182
+ onPlacementPreviewChange(preview) {
2183
+ lastMarkupStrokeRef.current = remoteMarkupStrokeFromPlacementPreview(preview);
2184
+ lastActiveToolRef.current = bindingOptions?.activeTool;
2185
+ sendPresenceUpdate();
2186
+ }
2187
+ }),
2188
+ [remotePresence, sendPresenceUpdate]
2189
+ );
2190
+ return {
2191
+ connection,
2192
+ sessionPeers,
2193
+ remotePresence,
2194
+ remoteAdapter,
2195
+ document: document2,
2196
+ bindViewportPresence,
2197
+ disconnect,
2198
+ reconnectNow
2199
+ };
2200
+ }
2201
+ function RealtimeCollaborationPluginComponent({
2202
+ pluginId,
2203
+ options
2204
+ }) {
2205
+ const { viewportRef, viewport } = useCanvuViewportContext();
2206
+ const { items, onItemsChange } = useCanvuDocumentContext();
2207
+ const session = useRealtimeSession(options);
2208
+ const peerDisplayName = options.peer.displayName ?? options.peer.id;
2209
+ const peerColor = options.peer.color ?? defaultPresenceColorForId(options.peer.id);
2210
+ const peerAuthor = react.useMemo(
2211
+ () => ({
2212
+ peerId: options.peer.id,
2213
+ displayName: peerDisplayName,
2214
+ color: peerColor,
2215
+ ...options.peer.image ? { image: options.peer.image } : {}
2216
+ }),
2217
+ [options.peer.id, options.peer.image, peerColor, peerDisplayName]
2218
+ );
2219
+ const commentOptions = react.useMemo(
2220
+ () => options.comments === false ? null : options.comments ?? {},
2221
+ [options.comments]
2222
+ );
2223
+ const handleCommentItemsChange = react.useCallback(
2224
+ (nextItems) => {
2225
+ onItemsChange?.([...nextItems]);
2226
+ session.remoteAdapter.send?.([...nextItems]);
2227
+ },
2228
+ [onItemsChange, session.remoteAdapter]
2229
+ );
2230
+ const comments = useRealtimeComments({
2231
+ items,
2232
+ onItemsChange: handleCommentItemsChange,
2233
+ author: peerAuthor,
2234
+ viewportRef,
2235
+ activeToolId: viewport.toolId,
2236
+ roomId: options.roomId,
2237
+ setToolId: viewport.onToolChangeRequest,
2238
+ ...commentOptions ?? {}
2239
+ });
2240
+ const presenceBindings = react.useMemo(
2241
+ () => session.bindViewportPresence({ activeTool: viewport.toolId }),
2242
+ [session.bindViewportPresence, viewport.toolId]
2243
+ );
2244
+ react.useEffect(() => {
2245
+ if (!onItemsChange || !session.document) return;
2246
+ if (session.document.updatedByClientId === session.connection.clientId) return;
2247
+ onItemsChange(session.document.items);
2248
+ }, [onItemsChange, session.connection.clientId, session.document]);
2249
+ const contribution = react.useMemo(
2250
+ () => ({
2251
+ toolTransform: commentOptions ? () => comments.tools : void 0,
2252
+ customPlacements: commentOptions ? comments.viewport.customPlacement ? [comments.viewport.customPlacement] : void 0 : void 0,
2253
+ viewportProps: {
2254
+ remotePresence: presenceBindings.remotePresence
2255
+ },
2256
+ callbacks: {
2257
+ onWorldPointerMove: presenceBindings.onWorldPointerMove,
2258
+ onWorldPointerLeave: presenceBindings.onWorldPointerLeave,
2259
+ onPlacementPreviewChange: presenceBindings.onPlacementPreviewChange,
2260
+ onCameraChange: commentOptions ? comments.viewport.onCameraChange : void 0
2261
+ },
2262
+ wrapOnItemsChange: (nextItems, ctx) => {
2263
+ if (commentOptions) {
2264
+ comments.viewport.onItemsChange?.(nextItems);
2265
+ return;
2266
+ }
2267
+ ctx.next(nextItems);
2268
+ session.remoteAdapter.send?.(nextItems);
2269
+ }
2270
+ }),
2271
+ [
2272
+ commentOptions,
2273
+ comments.tools,
2274
+ comments.viewport,
2275
+ presenceBindings,
2276
+ session.remoteAdapter
2277
+ ]
2278
+ );
2279
+ useCanvuPluginContribution(pluginId, contribution);
2280
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2281
+ options.showSessionPanel === false ? null : /* @__PURE__ */ jsxRuntime.jsx(
2282
+ RealtimeSessionPanel,
2283
+ {
2284
+ title: options.sessionPanel?.title ?? "Sess\xE3o realtime",
2285
+ peers: session.sessionPeers,
2286
+ connected: session.connection.connected,
2287
+ connectionState: session.connection.state,
2288
+ roomId: options.roomId,
2289
+ children: options.sessionPanel?.children
2290
+ }
2291
+ ),
2292
+ commentOptions ? comments.overlay : null
2293
+ ] });
2294
+ }
2295
+ function realtimeCollaborationPlugin(options) {
2296
+ const pluginId = `canvu.plugin.realtime-collaboration:${options.roomId}:${options.peer.clientId ?? options.peer.id}`;
2297
+ return createCanvuPlugin({
2298
+ id: pluginId,
2299
+ Component() {
2300
+ return /* @__PURE__ */ jsxRuntime.jsx(
2301
+ RealtimeCollaborationPluginComponent,
2302
+ {
2303
+ pluginId,
2304
+ options
2305
+ }
2306
+ );
2307
+ }
2308
+ });
2309
+ }
2310
+ function realtimeSessionPlugin(options) {
2311
+ return {
2312
+ id: "trazo.plugin.realtime-session",
2313
+ render: () => /* @__PURE__ */ jsxRuntime.jsx(RealtimeSessionPanel, { ...options })
2314
+ };
2315
+ }
2316
+
2317
+ exports.PresenceRemoteLayer = PresenceRemoteLayer;
2318
+ exports.REALTIME_COMMENT_TOOL = REALTIME_COMMENT_TOOL;
2319
+ exports.RealtimeCommentsOverlay = RealtimeCommentsOverlay;
2320
+ exports.RealtimeSessionPanel = RealtimeSessionPanel;
2321
+ exports.createRealtimeCommentAvatarDataUrl = createRealtimeCommentAvatarDataUrl;
2322
+ exports.createRealtimeCommentDraftItem = createRealtimeCommentDraftItem;
2323
+ exports.createRealtimeCommentItem = createRealtimeCommentItem;
2324
+ exports.defaultPresenceColorForId = defaultPresenceColorForId;
2325
+ exports.getRealtimeCommentData = getRealtimeCommentData;
2326
+ exports.isRealtimeCommentDraftItem = isRealtimeCommentDraftItem;
2327
+ exports.isRealtimeCommentItem = isRealtimeCommentItem;
2328
+ exports.parseRealtimeClientMessage = parseRealtimeClientMessage;
2329
+ exports.parseRealtimeServerMessage = parseRealtimeServerMessage;
2330
+ exports.realtimeCollaborationPlugin = realtimeCollaborationPlugin;
2331
+ exports.realtimeCommentsPlugin = realtimeCommentsPlugin;
2332
+ exports.realtimeSessionPlugin = realtimeSessionPlugin;
2333
+ exports.remoteMarkupStrokeFromPlacementPreview = remoteMarkupStrokeFromPlacementPreview;
2334
+ exports.useRealtimeComments = useRealtimeComments;
2335
+ exports.useRealtimeSession = useRealtimeSession;
2336
+ exports.withRealtimeCommentTool = withRealtimeCommentTool;
2337
+ //# sourceMappingURL=realtime.cjs.map
2338
+ //# sourceMappingURL=realtime.cjs.map