canvu-react 0.3.22 → 0.3.24

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/react.js CHANGED
@@ -1604,6 +1604,21 @@ function reorderManagedImages(items, orderedManagedIds) {
1604
1604
  });
1605
1605
  return restackManagedImages(next);
1606
1606
  }
1607
+ var OPEN_KEYFRAME_ID = "canvu-images-menu-open-keyframe";
1608
+ var OPEN_KEYFRAME_CSS = `
1609
+ @keyframes canvu-images-menu-open {
1610
+ from { opacity: 0; transform: scale(0.6); }
1611
+ to { opacity: 1; transform: scale(1); }
1612
+ }
1613
+ `;
1614
+ function ensureOpenKeyframe() {
1615
+ if (typeof document === "undefined") return;
1616
+ if (document.getElementById(OPEN_KEYFRAME_ID)) return;
1617
+ const style = document.createElement("style");
1618
+ style.id = OPEN_KEYFRAME_ID;
1619
+ style.textContent = OPEN_KEYFRAME_CSS;
1620
+ document.head.appendChild(style);
1621
+ }
1607
1622
  var panelStyle = {
1608
1623
  width: "fit-content",
1609
1624
  maxHeight: "min(85dvh, 820px)",
@@ -1614,7 +1629,9 @@ var panelStyle = {
1614
1629
  boxShadow: "0 10px 40px rgba(15, 23, 42, 0.12)",
1615
1630
  fontFamily: "system-ui, sans-serif",
1616
1631
  fontSize: 14,
1617
- color: "#0f172a"
1632
+ color: "#0f172a",
1633
+ transformOrigin: "top right",
1634
+ animation: "canvu-images-menu-open 180ms cubic-bezier(0.2, 0.8, 0.2, 1)"
1618
1635
  };
1619
1636
  var headerStyle = {
1620
1637
  display: "flex",
@@ -1736,33 +1753,16 @@ var collapsedButtonStyle = {
1736
1753
  display: "inline-flex",
1737
1754
  alignItems: "center",
1738
1755
  justifyContent: "center",
1739
- gap: 6,
1756
+ width: 44,
1740
1757
  height: 44,
1741
- minWidth: 44,
1742
- padding: "0 12px",
1758
+ padding: 0,
1743
1759
  border: "1px solid #e2e8f0",
1744
- borderRadius: 22,
1760
+ borderRadius: 10,
1745
1761
  background: "#ffffff",
1746
1762
  color: "#0f172a",
1747
1763
  cursor: "pointer",
1748
- fontFamily: "system-ui, sans-serif",
1749
- fontSize: 13,
1750
- fontWeight: 600,
1751
1764
  boxShadow: "0 8px 24px rgba(15, 23, 42, 0.12)"
1752
1765
  };
1753
- var collapsedCountStyle = {
1754
- display: "inline-flex",
1755
- alignItems: "center",
1756
- justifyContent: "center",
1757
- minWidth: 20,
1758
- height: 20,
1759
- padding: "0 6px",
1760
- borderRadius: 10,
1761
- backgroundColor: "#0f172a",
1762
- color: "#ffffff",
1763
- fontSize: 11,
1764
- fontWeight: 600
1765
- };
1766
1766
  var defaultLabels = {
1767
1767
  title: "Images",
1768
1768
  dragHandle: "Drag to reorder",
@@ -1786,12 +1786,15 @@ function ImagesMenu({
1786
1786
  })
1787
1787
  );
1788
1788
  const [collapsed, setCollapsed] = useState(false);
1789
+ useEffect(() => {
1790
+ ensureOpenKeyframe();
1791
+ }, []);
1789
1792
  if (managed.length === 0) {
1790
1793
  return null;
1791
1794
  }
1792
1795
  const resolvedLabels = { ...defaultLabels, ...labels };
1793
1796
  if (collapsed) {
1794
- return /* @__PURE__ */ jsxs(
1797
+ return /* @__PURE__ */ jsx(
1795
1798
  "button",
1796
1799
  {
1797
1800
  type: "button",
@@ -1800,10 +1803,7 @@ function ImagesMenu({
1800
1803
  "aria-label": resolvedLabels.expand,
1801
1804
  title: resolvedLabels.expand,
1802
1805
  onClick: () => setCollapsed(false),
1803
- children: [
1804
- /* @__PURE__ */ jsx(Images, { size: 20 }),
1805
- /* @__PURE__ */ jsx("span", { style: collapsedCountStyle, children: managed.length })
1806
- ]
1806
+ children: /* @__PURE__ */ jsx(Images, { size: 20 })
1807
1807
  }
1808
1808
  );
1809
1809
  }
@@ -1872,11 +1872,20 @@ function ImagesMenuRow({
1872
1872
  onRotate,
1873
1873
  onDelete
1874
1874
  }) {
1875
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item.id });
1875
+ const {
1876
+ attributes,
1877
+ listeners,
1878
+ setNodeRef,
1879
+ setActivatorNodeRef,
1880
+ transform,
1881
+ transition,
1882
+ isDragging
1883
+ } = useSortable({ id: item.id });
1884
+ const feedbackTransition = "background-color 140ms ease, opacity 140ms ease";
1876
1885
  const wrapperStyle = {
1877
1886
  ...rowStyle,
1878
1887
  transform: CSS.Transform.toString(transform),
1879
- transition,
1888
+ transition: transition ? `${transition}, ${feedbackTransition}` : feedbackTransition,
1880
1889
  background: isDragging ? "#eef2f7" : "transparent",
1881
1890
  opacity: isDragging ? 0.85 : 1
1882
1891
  };
@@ -1885,6 +1894,7 @@ function ImagesMenuRow({
1885
1894
  /* @__PURE__ */ jsx(
1886
1895
  "button",
1887
1896
  {
1897
+ ref: setActivatorNodeRef,
1888
1898
  type: "button",
1889
1899
  style: handleStyle,
1890
1900
  "aria-label": labels.dragHandle,
@@ -4779,16 +4789,23 @@ function distanceBetween(a, b) {
4779
4789
  function midpoint(a, b) {
4780
4790
  return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
4781
4791
  }
4782
- function wheelDeltaYPixels(e) {
4783
- switch (e.deltaMode) {
4792
+ function wheelDeltaPixels(delta, deltaMode) {
4793
+ switch (deltaMode) {
4784
4794
  case WheelEvent.DOM_DELTA_LINE:
4785
- return e.deltaY * 16;
4795
+ return delta * 16;
4786
4796
  case WheelEvent.DOM_DELTA_PAGE:
4787
- return e.deltaY * 400;
4797
+ return delta * 400;
4788
4798
  default:
4789
- return e.deltaY;
4799
+ return delta;
4790
4800
  }
4791
4801
  }
4802
+ var TRACKPAD_MAX_DELTA = 20;
4803
+ var MOUSE_WHEEL_DAMPING = 0.4;
4804
+ function softenWheelDelta(d) {
4805
+ const a = Math.abs(d);
4806
+ if (a <= TRACKPAD_MAX_DELTA) return d;
4807
+ return Math.sign(d) * (TRACKPAD_MAX_DELTA + (a - TRACKPAD_MAX_DELTA) * MOUSE_WHEEL_DAMPING);
4808
+ }
4792
4809
  function attachViewportInput(options) {
4793
4810
  const {
4794
4811
  element,
@@ -4811,8 +4828,8 @@ function attachViewportInput(options) {
4811
4828
  const onWheel = (e) => {
4812
4829
  if (e.ctrlKey || e.metaKey) {
4813
4830
  e.preventDefault();
4814
- const dy = wheelDeltaYPixels(e);
4815
- const normDy = Math.abs(dy) < 20 ? dy * 12 : dy;
4831
+ const dy = wheelDeltaPixels(e.deltaY, e.deltaMode);
4832
+ const normDy = Math.abs(dy) < TRACKPAD_MAX_DELTA ? dy * 12 : dy;
4816
4833
  const factor = Math.exp(-normDy * wheelZoomSensitivity);
4817
4834
  const rect = element.getBoundingClientRect();
4818
4835
  camera.setZoom(camera.zoom * factor, {
@@ -4823,8 +4840,10 @@ function attachViewportInput(options) {
4823
4840
  return;
4824
4841
  }
4825
4842
  e.preventDefault();
4826
- camera.x -= e.deltaX * wheelPanSensitivity / camera.zoom;
4827
- camera.y -= e.deltaY * wheelPanSensitivity / camera.zoom;
4843
+ const panDx = softenWheelDelta(wheelDeltaPixels(e.deltaX, e.deltaMode));
4844
+ const panDy = softenWheelDelta(wheelDeltaPixels(e.deltaY, e.deltaMode));
4845
+ camera.x -= panDx * wheelPanSensitivity / camera.zoom;
4846
+ camera.y -= panDy * wheelPanSensitivity / camera.zoom;
4828
4847
  onUpdate();
4829
4848
  };
4830
4849
  const onPointerDown = (e) => {
@@ -6525,9 +6544,7 @@ function PresenceRemoteLayer({
6525
6544
  size: LUCIDE_POINTER_VIEWBOX,
6526
6545
  color,
6527
6546
  fill: color,
6528
- stroke: "#ffffff",
6529
- strokeWidth: 1.25,
6530
- absoluteStrokeWidth: true,
6547
+ stroke: "none",
6531
6548
  "aria-hidden": true
6532
6549
  }
6533
6550
  )
@@ -6539,9 +6556,6 @@ function PresenceRemoteLayer({
6539
6556
  x: cur.x + labelOffsetX,
6540
6557
  y: cur.y + labelOffsetY,
6541
6558
  fill: color,
6542
- stroke: "#ffffff",
6543
- strokeWidth: 2.5 / z,
6544
- paintOrder: "stroke",
6545
6559
  style: {
6546
6560
  fontSize: labelFont,
6547
6561
  fontFamily: "system-ui, sans-serif",
@@ -7656,15 +7670,22 @@ var VectorViewport = forwardRef(
7656
7670
  useEffect(() => {
7657
7671
  rememberImageBlobHrefs(items, rememberedImageBlobHrefsRef.current);
7658
7672
  }, [items]);
7659
- useEffect(
7660
- () => () => {
7661
- releaseRememberedBlobHrefs(
7662
- rememberedImageBlobHrefsRef.current,
7663
- (href) => URL.revokeObjectURL(href)
7664
- );
7665
- },
7666
- []
7667
- );
7673
+ const blobRevokeTimerRef = useRef(null);
7674
+ useEffect(() => {
7675
+ if (blobRevokeTimerRef.current !== null) {
7676
+ clearTimeout(blobRevokeTimerRef.current);
7677
+ blobRevokeTimerRef.current = null;
7678
+ }
7679
+ return () => {
7680
+ blobRevokeTimerRef.current = setTimeout(() => {
7681
+ releaseRememberedBlobHrefs(
7682
+ rememberedImageBlobHrefsRef.current,
7683
+ (href) => URL.revokeObjectURL(href)
7684
+ );
7685
+ blobRevokeTimerRef.current = null;
7686
+ }, 100);
7687
+ };
7688
+ }, []);
7668
7689
  const PASTE_OFFSET_WORLD = 24;
7669
7690
  const copyIdsToInternalClipboard = useCallback((ids) => {
7670
7691
  if (ids.length === 0) return;