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.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) {
@@ -73,6 +78,126 @@ function smoothFreehandPointsToPathD(points) {
73
78
  return d;
74
79
  }
75
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
+
76
201
  // src/react/presence/peer-color.ts
77
202
  function defaultPresenceColorForId(id) {
78
203
  let h = 2166136261;
@@ -97,6 +222,9 @@ function strokePaint(tool, fallback) {
97
222
  return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
98
223
  }
99
224
  }
225
+ function isFreehandTool(tool) {
226
+ return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
227
+ }
100
228
  function PresenceRemoteLayer({
101
229
  camera,
102
230
  cameraVersion: _cameraVersion,
@@ -138,34 +266,72 @@ function PresenceRemoteLayer({
138
266
  strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
139
267
  widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
140
268
  };
141
- const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
142
- if (d) {
143
- strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
144
- "path",
145
- {
146
- d,
147
- fill: "none",
148
- stroke: paint.stroke,
149
- strokeOpacity: paint.strokeOpacity,
150
- strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
151
- strokeLinecap: "round",
152
- strokeLinejoin: "round",
153
- shapeRendering: "geometricPrecision",
154
- vectorEffect: "non-scaling-stroke"
155
- }
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
156
298
  );
157
- } else {
158
- const p0 = markup.points[0];
159
- if (p0) {
299
+ if (payload?.kind === "circle") {
160
300
  strokeNode = /* @__PURE__ */ jsxRuntime.jsx(
161
301
  "circle",
162
302
  {
163
- cx: p0.x,
164
- cy: p0.y,
165
- r: Math.max(3 / z, 2),
166
- fill: paint.stroke,
167
- fillOpacity: paint.strokeOpacity,
168
- 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"
169
335
  }
170
336
  );
171
337
  }
@@ -797,38 +963,6 @@ var DEFAULT_VECTOR_TOOLS = [
797
963
  shortcutHint: "I"
798
964
  }
799
965
  ];
800
-
801
- // src/scene/custom-shape.ts
802
- function expandCustomShapeTemplate(template, width, height) {
803
- return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
804
- }
805
- function resolveCustomInner(content, size) {
806
- if ("render" in content) {
807
- return content.render(size);
808
- }
809
- return expandCustomShapeTemplate(content.svg, size.width, size.height);
810
- }
811
- function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
812
- const b = normalizeRect(bounds);
813
- const sx = b.width / intrinsic.width;
814
- const sy = b.height / intrinsic.height;
815
- return `<g transform="scale(${sx},${sy})">${inner}</g>`;
816
- }
817
- function createCustomShapeItem(id, bounds, content) {
818
- const r = normalizeRect(bounds);
819
- const intrinsic = { width: r.width, height: r.height };
820
- const inner = resolveCustomInner(content, intrinsic);
821
- return {
822
- id,
823
- x: r.x,
824
- y: r.y,
825
- bounds: { ...r },
826
- toolKind: "custom",
827
- customIntrinsicSize: intrinsic,
828
- customInnerSvg: inner,
829
- childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
830
- };
831
- }
832
966
  var iconProps = { size: 20, strokeWidth: 2 };
833
967
  var COMMENT_PLUGIN_DATA_KEY = "realtimeComment";
834
968
  var REALTIME_COMMENT_TOOL = {