canvu-react 0.4.36 → 0.4.38

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/native.cjs CHANGED
@@ -1469,6 +1469,12 @@ function linkInitial(value) {
1469
1469
  const first = value.trim().charAt(0).toUpperCase();
1470
1470
  return first || "L";
1471
1471
  }
1472
+ function normalizeNativeLinkHref(value) {
1473
+ const trimmed = value.trim();
1474
+ if (!trimmed) return null;
1475
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return trimmed;
1476
+ return `https://${trimmed}`;
1477
+ }
1472
1478
  function buildNativeLinkCardDisplay(link) {
1473
1479
  const hostname = linkHostname(link.href);
1474
1480
  const title = link.title?.trim() || hostname || "Link";
@@ -1713,6 +1719,11 @@ function rgbaFromHexAndOpacity(hex, opacity) {
1713
1719
  function toNum(v) {
1714
1720
  return typeof v === "number" ? v : Number(v) || 0;
1715
1721
  }
1722
+ function dashIntervalsFromStrokeDasharray(strokeDasharray) {
1723
+ if (!strokeDasharray) return null;
1724
+ const intervals = strokeDasharray.split(/[\s,]+/).map((part) => Number(part)).filter((part) => Number.isFinite(part) && part > 0);
1725
+ return intervals.length > 0 ? intervals : null;
1726
+ }
1716
1727
  function SvgNodeRenderer({ nodes }) {
1717
1728
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => /* @__PURE__ */ jsxRuntime.jsx(SvgNodeItem, { node }, i)) });
1718
1729
  }
@@ -1827,6 +1838,7 @@ function SvgNodeItem({ node }) {
1827
1838
  const stroke = rgbaFromHexAndOpacity(node.stroke, node.strokeOpacity);
1828
1839
  const fill = isFill ? rgbaFromHexAndOpacity(node.fill, node.fillOpacity) ?? node.fill : void 0;
1829
1840
  const style = fill && fill !== "none" ? "fill" : "stroke";
1841
+ const intervals = style === "stroke" ? dashIntervalsFromStrokeDasharray(node.strokeDasharray) : null;
1830
1842
  return /* @__PURE__ */ jsxRuntime.jsx(
1831
1843
  reactNativeSkia.Path,
1832
1844
  {
@@ -1837,7 +1849,8 @@ function SvgNodeItem({ node }) {
1837
1849
  strokeCap: node.strokeLinecap === "round" ? "round" : "butt",
1838
1850
  strokeJoin: node.strokeLinejoin === "round" ? "round" : "miter",
1839
1851
  fillType: node.fillRule === "evenodd" ? "evenOdd" : "winding",
1840
- antiAlias: true
1852
+ antiAlias: true,
1853
+ children: intervals ? /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals }) : null
1841
1854
  }
1842
1855
  );
1843
1856
  }
@@ -2020,6 +2033,7 @@ function parsePathNode(attrs) {
2020
2033
  strokeOpacity: parseNumOpt(attrs["stroke-opacity"]),
2021
2034
  strokeLinecap: attrs["stroke-linecap"],
2022
2035
  strokeLinejoin: attrs["stroke-linejoin"],
2036
+ strokeDasharray: attrs["stroke-dasharray"],
2023
2037
  shapeRendering: attrs["shape-rendering"],
2024
2038
  vectorEffect: attrs["vector-effect"]
2025
2039
  };
@@ -2644,7 +2658,7 @@ function pointsToSmoothPathD(points) {
2644
2658
  const d = smoothFreehandPointsToPathD(points);
2645
2659
  return d || null;
2646
2660
  }
2647
- function dashIntervalsFromStrokeDasharray(strokeDasharray) {
2661
+ function dashIntervalsFromStrokeDasharray2(strokeDasharray) {
2648
2662
  if (!strokeDasharray) return null;
2649
2663
  const intervals = strokeDasharray.split(/\s+/).map((part) => Number(part)).filter((part) => Number.isFinite(part) && part > 0);
2650
2664
  return intervals.length > 0 ? intervals : null;
@@ -2934,7 +2948,7 @@ function NativeInteractionOverlay({
2934
2948
  );
2935
2949
  }
2936
2950
  if (payload.kind === "strokePath") {
2937
- const intervals = dashIntervalsFromStrokeDasharray(payload.strokeDasharray);
2951
+ const intervals = dashIntervalsFromStrokeDasharray2(payload.strokeDasharray);
2938
2952
  return /* @__PURE__ */ jsxRuntime.jsx(
2939
2953
  reactNativeSkia.Path,
2940
2954
  {
@@ -4657,6 +4671,13 @@ function resizeItemByHandle(item, start, handle, currentWorld) {
4657
4671
  }
4658
4672
  return { ...item, x: nb.x, y: nb.y, bounds: nb };
4659
4673
  }
4674
+ var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
4675
+ title: "Add link",
4676
+ description: "Paste the link you want to add to the board.",
4677
+ inputPlaceholder: "https://example.com",
4678
+ cancelLabel: "Cancel",
4679
+ addLabel: "Add"
4680
+ };
4660
4681
  var MIN_PLACE_SIZE = 8;
4661
4682
  var MIN_ARROW_DRAG_PX = 8;
4662
4683
  var TAP_PX = 20;
@@ -4740,6 +4761,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4740
4761
  onItemsChange,
4741
4762
  onToolChangeRequest,
4742
4763
  onLinkToolRequest,
4764
+ linkToolDialogLabels,
4743
4765
  onWorldPointerDown,
4744
4766
  onWorldPointerMove,
4745
4767
  onWorldPointerLeave,
@@ -4794,6 +4816,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4794
4816
  );
4795
4817
  const [eraserTrail, setEraserTrail] = react.useState([]);
4796
4818
  const [laserTrail, setLaserTrail] = react.useState([]);
4819
+ const [pendingNativeLinkRequest, setPendingNativeLinkRequest] = react.useState(null);
4820
+ const [nativeLinkInputValue, setNativeLinkInputValue] = react.useState("");
4797
4821
  const laserClearTimerRef = react.useRef(null);
4798
4822
  const strokeStyleRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
4799
4823
  const markerStrokeStyleRef = react.useRef({ ...MARKER_TOOL_STYLE });
@@ -4871,6 +4895,21 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4871
4895
  onToolChangeRequestRef.current?.("select");
4872
4896
  }
4873
4897
  }, []);
4898
+ const requestSelectToolAfterNativeLinkUse = react.useCallback(() => {
4899
+ onToolChangeRequestRef.current?.("select");
4900
+ }, []);
4901
+ const closeNativeLinkDialog = react.useCallback(() => {
4902
+ setPendingNativeLinkRequest(null);
4903
+ setNativeLinkInputValue("");
4904
+ }, []);
4905
+ const submitNativeLinkDialog = react.useCallback(() => {
4906
+ const href = normalizeNativeLinkHref(nativeLinkInputValue);
4907
+ if (!href || !pendingNativeLinkRequest) return;
4908
+ const inserted = pendingNativeLinkRequest.insertLink({ href });
4909
+ if (inserted) {
4910
+ closeNativeLinkDialog();
4911
+ }
4912
+ }, [closeNativeLinkDialog, nativeLinkInputValue, pendingNativeLinkRequest]);
4874
4913
  if (!cameraRef.current) {
4875
4914
  cameraRef.current = new Camera2D({ minZoom: 0.05, maxZoom: 32 });
4876
4915
  }
@@ -5448,7 +5487,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5448
5487
  const item = createLinkItem(id, options.bounds ?? suggestedBounds, link);
5449
5488
  currentChange([...itemsRef.current, item]);
5450
5489
  onSelectionChangeRef.current?.([id]);
5451
- requestSelectToolAfterUse();
5490
+ requestSelectToolAfterNativeLinkUse();
5452
5491
  return item;
5453
5492
  };
5454
5493
  const requestLink = onLinkToolRequestRef.current;
@@ -5464,17 +5503,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5464
5503
  });
5465
5504
  return;
5466
5505
  }
5467
- const handleWorldPointerDown = onWorldPointerDownRef.current;
5468
- if (handleWorldPointerDown) {
5469
- handleWorldPointerDown({
5470
- toolId: "link",
5471
- worldX: st.startWorld.x,
5472
- worldY: st.startWorld.y,
5473
- screenX: st.startScreen.x,
5474
- screenY: st.startScreen.y
5475
- });
5476
- requestSelectToolAfterUse();
5477
- }
5506
+ setNativeLinkInputValue("");
5507
+ setPendingNativeLinkRequest({
5508
+ toolId: "link",
5509
+ worldX: st.startWorld.x,
5510
+ worldY: st.startWorld.y,
5511
+ screenX: st.startScreen.x,
5512
+ screenY: st.startScreen.y,
5513
+ suggestedBounds,
5514
+ insertLink
5515
+ });
5478
5516
  return;
5479
5517
  }
5480
5518
  if (!change) return;
@@ -5524,6 +5562,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5524
5562
  dragStateRef.current = { kind: "idle" };
5525
5563
  },
5526
5564
  [
5565
+ requestSelectToolAfterNativeLinkUse,
5527
5566
  requestSelectToolAfterUse,
5528
5567
  screenToWorld,
5529
5568
  setRealtimePlacementPreview,
@@ -5655,7 +5694,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5655
5694
  [requestRender, size]
5656
5695
  );
5657
5696
  const activeStyleToolId = toolId === "draw" || toolId === "marker" ? toolId : null;
5658
- return /* @__PURE__ */ jsxRuntime.jsx(
5697
+ const nativeLinkDialogLabels = {
5698
+ ...DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS,
5699
+ ...linkToolDialogLabels ?? {}
5700
+ };
5701
+ const normalizedNativeLinkHref = normalizeNativeLinkHref(nativeLinkInputValue);
5702
+ const nativeLinkCanSubmit = pendingNativeLinkRequest !== null && normalizedNativeLinkHref !== null && onItemsChange != null;
5703
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5659
5704
  reactNative.View,
5660
5705
  {
5661
5706
  style: { flex: 1, overflow: "hidden" },
@@ -5669,80 +5714,225 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5669
5714
  notifyWorldPointerLeave();
5670
5715
  },
5671
5716
  ...panResponder.panHandlers,
5672
- children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5717
+ children: [
5718
+ size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5719
+ /* @__PURE__ */ jsxRuntime.jsx(
5720
+ NativeSceneRenderer,
5721
+ {
5722
+ items: sceneItems,
5723
+ camera,
5724
+ width: size.width,
5725
+ height: size.height
5726
+ }
5727
+ ),
5728
+ interactive && /* @__PURE__ */ jsxRuntime.jsx(
5729
+ NativeInteractionOverlay,
5730
+ {
5731
+ camera,
5732
+ width: size.width,
5733
+ height: size.height,
5734
+ selectedItems,
5735
+ showResizeHandles,
5736
+ placementPreview,
5737
+ laserTrail,
5738
+ eraserTrail,
5739
+ eraserPreviewItems: items.filter(
5740
+ (it) => eraserPreviewIds.includes(it.id)
5741
+ ),
5742
+ previewStrokeStyle: strokeStyleState,
5743
+ remotePresence
5744
+ }
5745
+ ),
5746
+ interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(
5747
+ reactNative.View,
5748
+ {
5749
+ pointerEvents: "box-none",
5750
+ style: styleInspectorPlacement === "top-left" ? {
5751
+ position: "absolute",
5752
+ left: 16,
5753
+ top: 104,
5754
+ alignItems: "flex-start"
5755
+ } : {
5756
+ position: "absolute",
5757
+ left: 16,
5758
+ right: 16,
5759
+ bottom: 84,
5760
+ alignItems: "center"
5761
+ },
5762
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5763
+ NativeVectorStyleInspector,
5764
+ {
5765
+ toolId: activeStyleToolId,
5766
+ value: strokeStyleState,
5767
+ onChange: patchCurrentStrokeStyle
5768
+ }
5769
+ )
5770
+ }
5771
+ ) : null,
5772
+ toolbar && /* @__PURE__ */ jsxRuntime.jsx(
5773
+ reactNative.View,
5774
+ {
5775
+ style: {
5776
+ position: "absolute",
5777
+ bottom: 16,
5778
+ left: 16,
5779
+ right: 16,
5780
+ flexDirection: "row",
5781
+ justifyContent: "center",
5782
+ alignItems: "center"
5783
+ },
5784
+ pointerEvents: "box-none",
5785
+ children: toolbar
5786
+ }
5787
+ )
5788
+ ] }),
5673
5789
  /* @__PURE__ */ jsxRuntime.jsx(
5674
- NativeSceneRenderer,
5790
+ reactNative.Modal,
5675
5791
  {
5676
- items: sceneItems,
5677
- camera,
5678
- width: size.width,
5679
- height: size.height
5680
- }
5681
- ),
5682
- interactive && /* @__PURE__ */ jsxRuntime.jsx(
5683
- NativeInteractionOverlay,
5684
- {
5685
- camera,
5686
- width: size.width,
5687
- height: size.height,
5688
- selectedItems,
5689
- showResizeHandles,
5690
- placementPreview,
5691
- laserTrail,
5692
- eraserTrail,
5693
- eraserPreviewItems: items.filter(
5694
- (it) => eraserPreviewIds.includes(it.id)
5695
- ),
5696
- previewStrokeStyle: strokeStyleState,
5697
- remotePresence
5698
- }
5699
- ),
5700
- interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(
5701
- reactNative.View,
5702
- {
5703
- pointerEvents: "box-none",
5704
- style: styleInspectorPlacement === "top-left" ? {
5705
- position: "absolute",
5706
- left: 16,
5707
- top: 104,
5708
- alignItems: "flex-start"
5709
- } : {
5710
- position: "absolute",
5711
- left: 16,
5712
- right: 16,
5713
- bottom: 84,
5714
- alignItems: "center"
5715
- },
5716
- children: /* @__PURE__ */ jsxRuntime.jsx(
5717
- NativeVectorStyleInspector,
5718
- {
5719
- toolId: activeStyleToolId,
5720
- value: strokeStyleState,
5721
- onChange: patchCurrentStrokeStyle
5722
- }
5723
- )
5724
- }
5725
- ) : null,
5726
- toolbar && /* @__PURE__ */ jsxRuntime.jsx(
5727
- reactNative.View,
5728
- {
5729
- style: {
5730
- position: "absolute",
5731
- bottom: 16,
5732
- left: 16,
5733
- right: 16,
5734
- flexDirection: "row",
5735
- justifyContent: "center",
5736
- alignItems: "center"
5737
- },
5738
- pointerEvents: "box-none",
5739
- children: toolbar
5792
+ animationType: "fade",
5793
+ transparent: true,
5794
+ visible: pendingNativeLinkRequest !== null,
5795
+ onRequestClose: closeNativeLinkDialog,
5796
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.nativeLinkDialogBackdrop, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.nativeLinkDialogCard, children: [
5797
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.nativeLinkDialogTitle, children: nativeLinkDialogLabels.title }),
5798
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.nativeLinkDialogDescription, children: nativeLinkDialogLabels.description }),
5799
+ /* @__PURE__ */ jsxRuntime.jsx(
5800
+ reactNative.TextInput,
5801
+ {
5802
+ accessibilityLabel: nativeLinkDialogLabels.title,
5803
+ autoCapitalize: "none",
5804
+ autoCorrect: false,
5805
+ keyboardType: "url",
5806
+ onChangeText: setNativeLinkInputValue,
5807
+ onSubmitEditing: submitNativeLinkDialog,
5808
+ placeholder: nativeLinkDialogLabels.inputPlaceholder,
5809
+ returnKeyType: "done",
5810
+ style: styles3.nativeLinkDialogInput,
5811
+ value: nativeLinkInputValue
5812
+ }
5813
+ ),
5814
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.nativeLinkDialogActions, children: [
5815
+ /* @__PURE__ */ jsxRuntime.jsx(
5816
+ reactNative.Pressable,
5817
+ {
5818
+ accessibilityRole: "button",
5819
+ onPress: closeNativeLinkDialog,
5820
+ style: ({ pressed }) => [
5821
+ styles3.nativeLinkDialogButton,
5822
+ pressed ? styles3.nativeLinkDialogButtonPressed : void 0
5823
+ ],
5824
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.nativeLinkDialogButtonText, children: nativeLinkDialogLabels.cancelLabel })
5825
+ }
5826
+ ),
5827
+ /* @__PURE__ */ jsxRuntime.jsx(
5828
+ reactNative.Pressable,
5829
+ {
5830
+ accessibilityRole: "button",
5831
+ accessibilityState: { disabled: !nativeLinkCanSubmit },
5832
+ disabled: !nativeLinkCanSubmit,
5833
+ onPress: submitNativeLinkDialog,
5834
+ style: ({ pressed }) => [
5835
+ styles3.nativeLinkDialogButton,
5836
+ styles3.nativeLinkDialogPrimaryButton,
5837
+ pressed && nativeLinkCanSubmit ? styles3.nativeLinkDialogPrimaryButtonPressed : void 0,
5838
+ !nativeLinkCanSubmit ? styles3.nativeLinkDialogDisabledButton : void 0
5839
+ ],
5840
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.nativeLinkDialogPrimaryButtonText, children: nativeLinkDialogLabels.addLabel })
5841
+ }
5842
+ )
5843
+ ] })
5844
+ ] }) })
5740
5845
  }
5741
5846
  )
5742
- ] })
5847
+ ]
5743
5848
  }
5744
5849
  );
5745
5850
  });
5851
+ var styles3 = reactNative.StyleSheet.create({
5852
+ nativeLinkDialogBackdrop: {
5853
+ flex: 1,
5854
+ alignItems: "center",
5855
+ justifyContent: "center",
5856
+ paddingHorizontal: 24,
5857
+ backgroundColor: "rgba(0, 0, 0, 0.45)"
5858
+ },
5859
+ nativeLinkDialogCard: {
5860
+ width: "100%",
5861
+ maxWidth: 480,
5862
+ padding: 24,
5863
+ borderRadius: 16,
5864
+ backgroundColor: "#ffffff",
5865
+ shadowColor: "#000000",
5866
+ shadowOpacity: 0.18,
5867
+ shadowRadius: 24,
5868
+ shadowOffset: { width: 0, height: 8 },
5869
+ elevation: 12
5870
+ },
5871
+ nativeLinkDialogTitle: {
5872
+ color: "#111827",
5873
+ fontSize: 24,
5874
+ fontWeight: "700",
5875
+ lineHeight: 30
5876
+ },
5877
+ nativeLinkDialogDescription: {
5878
+ marginTop: 12,
5879
+ color: "#6b7280",
5880
+ fontSize: 16,
5881
+ lineHeight: 22
5882
+ },
5883
+ nativeLinkDialogInput: {
5884
+ marginTop: 24,
5885
+ height: 52,
5886
+ paddingHorizontal: 14,
5887
+ borderRadius: 10,
5888
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
5889
+ borderColor: "#d1d5db",
5890
+ color: "#111827",
5891
+ fontSize: 18,
5892
+ backgroundColor: "#ffffff"
5893
+ },
5894
+ nativeLinkDialogActions: {
5895
+ marginTop: 24,
5896
+ flexDirection: "row",
5897
+ justifyContent: "flex-end",
5898
+ gap: 12
5899
+ },
5900
+ nativeLinkDialogButton: {
5901
+ minWidth: 92,
5902
+ height: 48,
5903
+ alignItems: "center",
5904
+ justifyContent: "center",
5905
+ borderRadius: 10,
5906
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
5907
+ borderColor: "#d1d5db",
5908
+ backgroundColor: "#ffffff",
5909
+ paddingHorizontal: 18
5910
+ },
5911
+ nativeLinkDialogButtonPressed: {
5912
+ backgroundColor: "#f3f4f6"
5913
+ },
5914
+ nativeLinkDialogButtonText: {
5915
+ color: "#111827",
5916
+ fontSize: 17,
5917
+ fontWeight: "600"
5918
+ },
5919
+ nativeLinkDialogPrimaryButton: {
5920
+ borderColor: "#18181b",
5921
+ backgroundColor: "#18181b"
5922
+ },
5923
+ nativeLinkDialogPrimaryButtonPressed: {
5924
+ backgroundColor: "#27272a"
5925
+ },
5926
+ nativeLinkDialogDisabledButton: {
5927
+ borderColor: "#9ca3af",
5928
+ backgroundColor: "#9ca3af"
5929
+ },
5930
+ nativeLinkDialogPrimaryButtonText: {
5931
+ color: "#ffffff",
5932
+ fontSize: 17,
5933
+ fontWeight: "700"
5934
+ }
5935
+ });
5746
5936
 
5747
5937
  exports.DEFAULT_LINK_CARD_SIZE = DEFAULT_LINK_CARD_SIZE;
5748
5938
  exports.DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = DEFAULT_NATIVE_OVERFLOW_TOOL_IDS;