canvu-react 0.3.26 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/realtime.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { MousePointer2, MessageSquare, Sparkles, Hand, Square, Circle, Minus, ArrowUpRight, PenLine, Highlighter, Eraser, Type, Image } from 'lucide-react';
2
+ import getStroke from 'perfect-freehand';
2
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
4
  import { createContext, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect, useContext } from 'react';
4
5
 
@@ -71,6 +72,126 @@ function smoothFreehandPointsToPathD(points) {
71
72
  return d;
72
73
  }
73
74
 
75
+ // src/scene/custom-shape.ts
76
+ function expandCustomShapeTemplate(template, width, height) {
77
+ return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
78
+ }
79
+ function resolveCustomInner(content, size) {
80
+ if ("render" in content) {
81
+ return content.render(size);
82
+ }
83
+ return expandCustomShapeTemplate(content.svg, size.width, size.height);
84
+ }
85
+ function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
86
+ const b = normalizeRect(bounds);
87
+ const sx = b.width / intrinsic.width;
88
+ const sy = b.height / intrinsic.height;
89
+ return `<g transform="scale(${sx},${sy})">${inner}</g>`;
90
+ }
91
+ function createCustomShapeItem(id, bounds, content) {
92
+ const r = normalizeRect(bounds);
93
+ const intrinsic = { width: r.width, height: r.height };
94
+ const inner = resolveCustomInner(content, intrinsic);
95
+ return {
96
+ id,
97
+ x: r.x,
98
+ y: r.y,
99
+ bounds: { ...r },
100
+ toolKind: "custom",
101
+ customIntrinsicSize: intrinsic,
102
+ customInnerSvg: inner,
103
+ childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
104
+ };
105
+ }
106
+
107
+ // src/scene/shape-builders.ts
108
+ function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware = false) {
109
+ const sw = style.strokeWidth;
110
+ const base2 = {
111
+ last: strokeComplete,
112
+ simulatePressure: true
113
+ };
114
+ if (toolKind === "draw" || toolKind === "pencil") {
115
+ if (pressureAware && toolKind === "draw") {
116
+ return {
117
+ ...base2,
118
+ size: Math.max(2, sw * 1.05),
119
+ thinning: 0.42,
120
+ smoothing: 0.78,
121
+ streamline: 0.62,
122
+ simulatePressure: true
123
+ };
124
+ }
125
+ return {
126
+ ...base2,
127
+ size: Math.max(2, sw * 1.18),
128
+ thinning: 0.12,
129
+ smoothing: 0.85,
130
+ streamline: 0.78,
131
+ simulatePressure: true
132
+ };
133
+ }
134
+ if (toolKind === "brush") {
135
+ return {
136
+ ...base2,
137
+ size: Math.max(4, sw * 1.22),
138
+ thinning: 0.52,
139
+ smoothing: 0.64,
140
+ streamline: 0.68
141
+ };
142
+ }
143
+ return {
144
+ ...base2,
145
+ size: Math.max(6, sw * 1.08),
146
+ thinning: 0.08,
147
+ smoothing: 0.88,
148
+ streamline: 0.84,
149
+ simulatePressure: true
150
+ };
151
+ }
152
+ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
153
+ if (pathPointsLocal.length === 0) return null;
154
+ if (pathPointsLocal.length === 1) {
155
+ const p = pathPointsLocal[0];
156
+ if (!p) return null;
157
+ return {
158
+ kind: "circle",
159
+ cx: p.x,
160
+ cy: p.y,
161
+ r: Math.max(0.5, style.strokeWidth / 2),
162
+ fill: style.stroke,
163
+ fillOpacity: style.strokeOpacity
164
+ };
165
+ }
166
+ const hasPressure = pathPointsLocal.some(
167
+ (p) => p.pressure != null && Number.isFinite(p.pressure)
168
+ );
169
+ const input = hasPressure ? pathPointsLocal.map(
170
+ (p) => [p.x, p.y, Math.min(1, Math.max(0, p.pressure ?? 0.5))]
171
+ ) : pathPointsLocal.map((p) => [p.x, p.y]);
172
+ const stroke = getStroke(
173
+ input,
174
+ perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure)
175
+ );
176
+ if (stroke.length < 3) return null;
177
+ const first = stroke[0];
178
+ if (!first) return null;
179
+ let d = `M ${first[0]} ${first[1]} Q`;
180
+ for (let i = 0; i < stroke.length; i++) {
181
+ const a = stroke[i];
182
+ const b = stroke[(i + 1) % stroke.length];
183
+ if (!a || !b) continue;
184
+ d += ` ${a[0]} ${a[1]} ${(a[0] + b[0]) / 2} ${(a[1] + b[1]) / 2}`;
185
+ }
186
+ d += " Z";
187
+ return {
188
+ kind: "fillPath",
189
+ d,
190
+ fill: style.stroke,
191
+ fillOpacity: style.strokeOpacity
192
+ };
193
+ }
194
+
74
195
  // src/react/presence/peer-color.ts
75
196
  function defaultPresenceColorForId(id) {
76
197
  let h = 2166136261;
@@ -95,6 +216,9 @@ function strokePaint(tool, fallback) {
95
216
  return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
96
217
  }
97
218
  }
219
+ function isFreehandTool(tool) {
220
+ return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
221
+ }
98
222
  function PresenceRemoteLayer({
99
223
  camera,
100
224
  cameraVersion: _cameraVersion,
@@ -136,34 +260,72 @@ function PresenceRemoteLayer({
136
260
  strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
137
261
  widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
138
262
  };
139
- const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
140
- if (d) {
141
- strokeNode = /* @__PURE__ */ jsx(
142
- "path",
143
- {
144
- d,
145
- fill: "none",
146
- stroke: paint.stroke,
147
- strokeOpacity: paint.strokeOpacity,
148
- strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
149
- strokeLinecap: "round",
150
- strokeLinejoin: "round",
151
- shapeRendering: "geometricPrecision",
152
- vectorEffect: "non-scaling-stroke"
153
- }
263
+ if (markup.tool === "laser") {
264
+ const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
265
+ if (d) {
266
+ strokeNode = /* @__PURE__ */ jsx(
267
+ "path",
268
+ {
269
+ d,
270
+ fill: "none",
271
+ stroke: paint.stroke,
272
+ strokeOpacity: paint.strokeOpacity,
273
+ strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
274
+ strokeLinecap: "round",
275
+ strokeLinejoin: "round",
276
+ shapeRendering: "geometricPrecision",
277
+ vectorEffect: "non-scaling-stroke"
278
+ }
279
+ );
280
+ }
281
+ } else if (isFreehandTool(markup.tool)) {
282
+ const style = {
283
+ stroke: paint.stroke,
284
+ strokeWidth: paint.widthWorld,
285
+ ...paint.strokeOpacity != null ? { strokeOpacity: paint.strokeOpacity } : {}
286
+ };
287
+ const payload = computeFreehandSvgPayload(
288
+ markup.points.map((p) => ({ x: p.x, y: p.y })),
289
+ style,
290
+ markup.tool,
291
+ false
154
292
  );
155
- } else {
156
- const p0 = markup.points[0];
157
- if (p0) {
293
+ if (payload?.kind === "circle") {
158
294
  strokeNode = /* @__PURE__ */ jsx(
159
295
  "circle",
160
296
  {
161
- cx: p0.x,
162
- cy: p0.y,
163
- r: Math.max(3 / z, 2),
164
- fill: paint.stroke,
165
- fillOpacity: paint.strokeOpacity,
166
- vectorEffect: "non-scaling-stroke"
297
+ cx: payload.cx,
298
+ cy: payload.cy,
299
+ r: payload.r,
300
+ fill: payload.fill,
301
+ ...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
302
+ shapeRendering: "geometricPrecision"
303
+ }
304
+ );
305
+ } else if (payload?.kind === "fillPath") {
306
+ strokeNode = /* @__PURE__ */ jsx(
307
+ "path",
308
+ {
309
+ d: payload.d,
310
+ fill: payload.fill,
311
+ fillRule: "nonzero",
312
+ stroke: "none",
313
+ ...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
314
+ shapeRendering: "geometricPrecision"
315
+ }
316
+ );
317
+ } else if (payload?.kind === "strokePath") {
318
+ strokeNode = /* @__PURE__ */ jsx(
319
+ "path",
320
+ {
321
+ d: payload.d,
322
+ fill: "none",
323
+ stroke: payload.stroke,
324
+ strokeWidth: payload.strokeWidth,
325
+ ...payload.strokeOpacity != null ? { strokeOpacity: payload.strokeOpacity } : {},
326
+ strokeLinecap: "round",
327
+ strokeLinejoin: "round",
328
+ shapeRendering: "geometricPrecision"
167
329
  }
168
330
  );
169
331
  }
@@ -795,38 +957,6 @@ var DEFAULT_VECTOR_TOOLS = [
795
957
  shortcutHint: "I"
796
958
  }
797
959
  ];
798
-
799
- // src/scene/custom-shape.ts
800
- function expandCustomShapeTemplate(template, width, height) {
801
- return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
802
- }
803
- function resolveCustomInner(content, size) {
804
- if ("render" in content) {
805
- return content.render(size);
806
- }
807
- return expandCustomShapeTemplate(content.svg, size.width, size.height);
808
- }
809
- function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
810
- const b = normalizeRect(bounds);
811
- const sx = b.width / intrinsic.width;
812
- const sy = b.height / intrinsic.height;
813
- return `<g transform="scale(${sx},${sy})">${inner}</g>`;
814
- }
815
- function createCustomShapeItem(id, bounds, content) {
816
- const r = normalizeRect(bounds);
817
- const intrinsic = { width: r.width, height: r.height };
818
- const inner = resolveCustomInner(content, intrinsic);
819
- return {
820
- id,
821
- x: r.x,
822
- y: r.y,
823
- bounds: { ...r },
824
- toolKind: "custom",
825
- customIntrinsicSize: intrinsic,
826
- customInnerSvg: inner,
827
- childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
828
- };
829
- }
830
960
  var iconProps = { size: 20, strokeWidth: 2 };
831
961
  var COMMENT_PLUGIN_DATA_KEY = "realtimeComment";
832
962
  var REALTIME_COMMENT_TOOL = {