canvu-react 0.3.25 → 0.3.27

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.cjs CHANGED
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var lucideReact = require('lucide-react');
4
+ var getStroke = require('perfect-freehand');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
  var react = require('react');
6
7
 
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var getStroke__default = /*#__PURE__*/_interopDefault(getStroke);
11
+
7
12
  // src/react/presence/map-placement-preview.ts
8
13
  function remoteMarkupStrokeFromPlacementPreview(preview) {
9
14
  if (!preview || preview.kind !== "stroke" || preview.points.length === 0) {
@@ -11,9 +16,13 @@ function remoteMarkupStrokeFromPlacementPreview(preview) {
11
16
  }
12
17
  const tool = preview.tool;
13
18
  const mapped = tool === "laser" || tool === "marker" || tool === "draw" ? tool : "draw";
19
+ const style = preview.style;
14
20
  return {
15
21
  points: preview.points,
16
- tool: mapped
22
+ tool: mapped,
23
+ ...style?.strokeWidth != null ? { strokeWidth: style.strokeWidth } : {},
24
+ ...style?.stroke ? { stroke: style.stroke } : {},
25
+ ...style?.strokeOpacity != null ? { strokeOpacity: style.strokeOpacity } : {}
17
26
  };
18
27
  }
19
28
 
@@ -69,6 +78,126 @@ function smoothFreehandPointsToPathD(points) {
69
78
  return d;
70
79
  }
71
80
 
81
+ // src/scene/custom-shape.ts
82
+ function expandCustomShapeTemplate(template, width, height) {
83
+ return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
84
+ }
85
+ function resolveCustomInner(content, size) {
86
+ if ("render" in content) {
87
+ return content.render(size);
88
+ }
89
+ return expandCustomShapeTemplate(content.svg, size.width, size.height);
90
+ }
91
+ function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
92
+ const b = normalizeRect(bounds);
93
+ const sx = b.width / intrinsic.width;
94
+ const sy = b.height / intrinsic.height;
95
+ return `<g transform="scale(${sx},${sy})">${inner}</g>`;
96
+ }
97
+ function createCustomShapeItem(id, bounds, content) {
98
+ const r = normalizeRect(bounds);
99
+ const intrinsic = { width: r.width, height: r.height };
100
+ const inner = resolveCustomInner(content, intrinsic);
101
+ return {
102
+ id,
103
+ x: r.x,
104
+ y: r.y,
105
+ bounds: { ...r },
106
+ toolKind: "custom",
107
+ customIntrinsicSize: intrinsic,
108
+ customInnerSvg: inner,
109
+ childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
110
+ };
111
+ }
112
+
113
+ // src/scene/shape-builders.ts
114
+ function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware = false) {
115
+ const sw = style.strokeWidth;
116
+ const base2 = {
117
+ last: strokeComplete,
118
+ simulatePressure: true
119
+ };
120
+ if (toolKind === "draw" || toolKind === "pencil") {
121
+ if (pressureAware && toolKind === "draw") {
122
+ return {
123
+ ...base2,
124
+ size: Math.max(2, sw * 1.05),
125
+ thinning: 0.42,
126
+ smoothing: 0.78,
127
+ streamline: 0.62,
128
+ simulatePressure: true
129
+ };
130
+ }
131
+ return {
132
+ ...base2,
133
+ size: Math.max(2, sw * 1.18),
134
+ thinning: 0.12,
135
+ smoothing: 0.85,
136
+ streamline: 0.78,
137
+ simulatePressure: true
138
+ };
139
+ }
140
+ if (toolKind === "brush") {
141
+ return {
142
+ ...base2,
143
+ size: Math.max(4, sw * 1.22),
144
+ thinning: 0.52,
145
+ smoothing: 0.64,
146
+ streamline: 0.68
147
+ };
148
+ }
149
+ return {
150
+ ...base2,
151
+ size: Math.max(6, sw * 1.08),
152
+ thinning: 0.08,
153
+ smoothing: 0.88,
154
+ streamline: 0.84,
155
+ simulatePressure: true
156
+ };
157
+ }
158
+ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
159
+ if (pathPointsLocal.length === 0) return null;
160
+ if (pathPointsLocal.length === 1) {
161
+ const p = pathPointsLocal[0];
162
+ if (!p) return null;
163
+ return {
164
+ kind: "circle",
165
+ cx: p.x,
166
+ cy: p.y,
167
+ r: Math.max(0.5, style.strokeWidth / 2),
168
+ fill: style.stroke,
169
+ fillOpacity: style.strokeOpacity
170
+ };
171
+ }
172
+ const hasPressure = pathPointsLocal.some(
173
+ (p) => p.pressure != null && Number.isFinite(p.pressure)
174
+ );
175
+ const input = hasPressure ? pathPointsLocal.map(
176
+ (p) => [p.x, p.y, Math.min(1, Math.max(0, p.pressure ?? 0.5))]
177
+ ) : pathPointsLocal.map((p) => [p.x, p.y]);
178
+ const stroke = getStroke__default.default(
179
+ input,
180
+ perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure)
181
+ );
182
+ if (stroke.length < 3) return null;
183
+ const first = stroke[0];
184
+ if (!first) return null;
185
+ let d = `M ${first[0]} ${first[1]} Q`;
186
+ for (let i = 0; i < stroke.length; i++) {
187
+ const a = stroke[i];
188
+ const b = stroke[(i + 1) % stroke.length];
189
+ if (!a || !b) continue;
190
+ d += ` ${a[0]} ${a[1]} ${(a[0] + b[0]) / 2} ${(a[1] + b[1]) / 2}`;
191
+ }
192
+ d += " Z";
193
+ return {
194
+ kind: "fillPath",
195
+ d,
196
+ fill: style.stroke,
197
+ fillOpacity: style.strokeOpacity
198
+ };
199
+ }
200
+
72
201
  // src/react/presence/peer-color.ts
73
202
  function defaultPresenceColorForId(id) {
74
203
  let h = 2166136261;
@@ -93,6 +222,9 @@ function strokePaint(tool, fallback) {
93
222
  return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
94
223
  }
95
224
  }
225
+ function isFreehandTool(tool) {
226
+ return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
227
+ }
96
228
  function PresenceRemoteLayer({
97
229
  camera,
98
230
  cameraVersion: _cameraVersion,
@@ -128,35 +260,78 @@ function PresenceRemoteLayer({
128
260
  const markup = peer.markupStroke;
129
261
  let strokeNode = null;
130
262
  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
- }
263
+ const fallbackPaint = strokePaint(markup.tool, color);
264
+ const paint = {
265
+ stroke: markup.stroke ?? fallbackPaint.stroke,
266
+ strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
267
+ widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
268
+ };
269
+ if (markup.tool === "laser") {
270
+ const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
271
+ if (d) {
272
+ strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
273
+ "path",
274
+ {
275
+ d,
276
+ fill: "none",
277
+ stroke: paint.stroke,
278
+ strokeOpacity: paint.strokeOpacity,
279
+ strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
280
+ strokeLinecap: "round",
281
+ strokeLinejoin: "round",
282
+ shapeRendering: "geometricPrecision",
283
+ vectorEffect: "non-scaling-stroke"
284
+ }
285
+ );
286
+ }
287
+ } else if (isFreehandTool(markup.tool)) {
288
+ const style = {
289
+ stroke: paint.stroke,
290
+ strokeWidth: paint.widthWorld,
291
+ ...paint.strokeOpacity != null ? { strokeOpacity: paint.strokeOpacity } : {}
292
+ };
293
+ const payload = computeFreehandSvgPayload(
294
+ markup.points.map((p) => ({ x: p.x, y: p.y })),
295
+ style,
296
+ markup.tool,
297
+ false
147
298
  );
148
- } else {
149
- const p0 = markup.points[0];
150
- if (p0) {
299
+ if (payload?.kind === "circle") {
151
300
  strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
152
301
  "circle",
153
302
  {
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"
303
+ cx: payload.cx,
304
+ cy: payload.cy,
305
+ r: payload.r,
306
+ fill: payload.fill,
307
+ ...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
308
+ shapeRendering: "geometricPrecision"
309
+ }
310
+ );
311
+ } else if (payload?.kind === "fillPath") {
312
+ strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
313
+ "path",
314
+ {
315
+ d: payload.d,
316
+ fill: payload.fill,
317
+ fillRule: "nonzero",
318
+ stroke: "none",
319
+ ...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
320
+ shapeRendering: "geometricPrecision"
321
+ }
322
+ );
323
+ } else if (payload?.kind === "strokePath") {
324
+ strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
325
+ "path",
326
+ {
327
+ d: payload.d,
328
+ fill: "none",
329
+ stroke: payload.stroke,
330
+ strokeWidth: payload.strokeWidth,
331
+ ...payload.strokeOpacity != null ? { strokeOpacity: payload.strokeOpacity } : {},
332
+ strokeLinecap: "round",
333
+ strokeLinejoin: "round",
334
+ shapeRendering: "geometricPrecision"
160
335
  }
161
336
  );
162
337
  }
@@ -257,7 +432,16 @@ function parseMarkupStroke(value) {
257
432
  if (x == null || y == null) return null;
258
433
  return { x, y };
259
434
  }).filter((point) => point != null);
260
- return { points, tool };
435
+ const strokeWidth = getNumber(value.strokeWidth);
436
+ const stroke = getString(value.stroke);
437
+ const strokeOpacity = getNumber(value.strokeOpacity);
438
+ return {
439
+ points,
440
+ tool,
441
+ ...strokeWidth != null ? { strokeWidth } : {},
442
+ ...stroke ? { stroke } : {},
443
+ ...strokeOpacity != null ? { strokeOpacity } : {}
444
+ };
261
445
  }
262
446
  function parsePresencePayload(value) {
263
447
  if (!isRecord(value)) return void 0;
@@ -779,38 +963,6 @@ var DEFAULT_VECTOR_TOOLS = [
779
963
  shortcutHint: "I"
780
964
  }
781
965
  ];
782
-
783
- // src/scene/custom-shape.ts
784
- function expandCustomShapeTemplate(template, width, height) {
785
- return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
786
- }
787
- function resolveCustomInner(content, size) {
788
- if ("render" in content) {
789
- return content.render(size);
790
- }
791
- return expandCustomShapeTemplate(content.svg, size.width, size.height);
792
- }
793
- function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
794
- const b = normalizeRect(bounds);
795
- const sx = b.width / intrinsic.width;
796
- const sy = b.height / intrinsic.height;
797
- return `<g transform="scale(${sx},${sy})">${inner}</g>`;
798
- }
799
- function createCustomShapeItem(id, bounds, content) {
800
- const r = normalizeRect(bounds);
801
- const intrinsic = { width: r.width, height: r.height };
802
- const inner = resolveCustomInner(content, intrinsic);
803
- return {
804
- id,
805
- x: r.x,
806
- y: r.y,
807
- bounds: { ...r },
808
- toolKind: "custom",
809
- customIntrinsicSize: intrinsic,
810
- customInnerSvg: inner,
811
- childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
812
- };
813
- }
814
966
  var iconProps = { size: 20, strokeWidth: 2 };
815
967
  var COMMENT_PLUGIN_DATA_KEY = "realtimeComment";
816
968
  var REALTIME_COMMENT_TOOL = {