canvu-react 0.3.23 → 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.cjs CHANGED
@@ -1611,6 +1611,21 @@ function reorderManagedImages(items, orderedManagedIds) {
1611
1611
  });
1612
1612
  return restackManagedImages(next);
1613
1613
  }
1614
+ var OPEN_KEYFRAME_ID = "canvu-images-menu-open-keyframe";
1615
+ var OPEN_KEYFRAME_CSS = `
1616
+ @keyframes canvu-images-menu-open {
1617
+ from { opacity: 0; transform: scale(0.6); }
1618
+ to { opacity: 1; transform: scale(1); }
1619
+ }
1620
+ `;
1621
+ function ensureOpenKeyframe() {
1622
+ if (typeof document === "undefined") return;
1623
+ if (document.getElementById(OPEN_KEYFRAME_ID)) return;
1624
+ const style = document.createElement("style");
1625
+ style.id = OPEN_KEYFRAME_ID;
1626
+ style.textContent = OPEN_KEYFRAME_CSS;
1627
+ document.head.appendChild(style);
1628
+ }
1614
1629
  var panelStyle = {
1615
1630
  width: "fit-content",
1616
1631
  maxHeight: "min(85dvh, 820px)",
@@ -1621,7 +1636,9 @@ var panelStyle = {
1621
1636
  boxShadow: "0 10px 40px rgba(15, 23, 42, 0.12)",
1622
1637
  fontFamily: "system-ui, sans-serif",
1623
1638
  fontSize: 14,
1624
- color: "#0f172a"
1639
+ color: "#0f172a",
1640
+ transformOrigin: "top right",
1641
+ animation: "canvu-images-menu-open 180ms cubic-bezier(0.2, 0.8, 0.2, 1)"
1625
1642
  };
1626
1643
  var headerStyle = {
1627
1644
  display: "flex",
@@ -1743,33 +1760,16 @@ var collapsedButtonStyle = {
1743
1760
  display: "inline-flex",
1744
1761
  alignItems: "center",
1745
1762
  justifyContent: "center",
1746
- gap: 6,
1763
+ width: 44,
1747
1764
  height: 44,
1748
- minWidth: 44,
1749
- padding: "0 12px",
1765
+ padding: 0,
1750
1766
  border: "1px solid #e2e8f0",
1751
- borderRadius: 22,
1767
+ borderRadius: 10,
1752
1768
  background: "#ffffff",
1753
1769
  color: "#0f172a",
1754
1770
  cursor: "pointer",
1755
- fontFamily: "system-ui, sans-serif",
1756
- fontSize: 13,
1757
- fontWeight: 600,
1758
1771
  boxShadow: "0 8px 24px rgba(15, 23, 42, 0.12)"
1759
1772
  };
1760
- var collapsedCountStyle = {
1761
- display: "inline-flex",
1762
- alignItems: "center",
1763
- justifyContent: "center",
1764
- minWidth: 20,
1765
- height: 20,
1766
- padding: "0 6px",
1767
- borderRadius: 10,
1768
- backgroundColor: "#0f172a",
1769
- color: "#ffffff",
1770
- fontSize: 11,
1771
- fontWeight: 600
1772
- };
1773
1773
  var defaultLabels = {
1774
1774
  title: "Images",
1775
1775
  dragHandle: "Drag to reorder",
@@ -1793,12 +1793,15 @@ function ImagesMenu({
1793
1793
  })
1794
1794
  );
1795
1795
  const [collapsed, setCollapsed] = react.useState(false);
1796
+ react.useEffect(() => {
1797
+ ensureOpenKeyframe();
1798
+ }, []);
1796
1799
  if (managed.length === 0) {
1797
1800
  return null;
1798
1801
  }
1799
1802
  const resolvedLabels = { ...defaultLabels, ...labels };
1800
1803
  if (collapsed) {
1801
- return /* @__PURE__ */ jsxRuntime.jsxs(
1804
+ return /* @__PURE__ */ jsxRuntime.jsx(
1802
1805
  "button",
1803
1806
  {
1804
1807
  type: "button",
@@ -1807,10 +1810,7 @@ function ImagesMenu({
1807
1810
  "aria-label": resolvedLabels.expand,
1808
1811
  title: resolvedLabels.expand,
1809
1812
  onClick: () => setCollapsed(false),
1810
- children: [
1811
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Images, { size: 20 }),
1812
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: collapsedCountStyle, children: managed.length })
1813
- ]
1813
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Images, { size: 20 })
1814
1814
  }
1815
1815
  );
1816
1816
  }
@@ -1879,11 +1879,20 @@ function ImagesMenuRow({
1879
1879
  onRotate,
1880
1880
  onDelete
1881
1881
  }) {
1882
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({ id: item.id });
1882
+ const {
1883
+ attributes,
1884
+ listeners,
1885
+ setNodeRef,
1886
+ setActivatorNodeRef,
1887
+ transform,
1888
+ transition,
1889
+ isDragging
1890
+ } = sortable.useSortable({ id: item.id });
1891
+ const feedbackTransition = "background-color 140ms ease, opacity 140ms ease";
1883
1892
  const wrapperStyle = {
1884
1893
  ...rowStyle,
1885
1894
  transform: utilities.CSS.Transform.toString(transform),
1886
- transition,
1895
+ transition: transition ? `${transition}, ${feedbackTransition}` : feedbackTransition,
1887
1896
  background: isDragging ? "#eef2f7" : "transparent",
1888
1897
  opacity: isDragging ? 0.85 : 1
1889
1898
  };
@@ -1892,6 +1901,7 @@ function ImagesMenuRow({
1892
1901
  /* @__PURE__ */ jsxRuntime.jsx(
1893
1902
  "button",
1894
1903
  {
1904
+ ref: setActivatorNodeRef,
1895
1905
  type: "button",
1896
1906
  style: handleStyle,
1897
1907
  "aria-label": labels.dragHandle,
@@ -4786,16 +4796,23 @@ function distanceBetween(a, b) {
4786
4796
  function midpoint(a, b) {
4787
4797
  return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
4788
4798
  }
4789
- function wheelDeltaYPixels(e) {
4790
- switch (e.deltaMode) {
4799
+ function wheelDeltaPixels(delta, deltaMode) {
4800
+ switch (deltaMode) {
4791
4801
  case WheelEvent.DOM_DELTA_LINE:
4792
- return e.deltaY * 16;
4802
+ return delta * 16;
4793
4803
  case WheelEvent.DOM_DELTA_PAGE:
4794
- return e.deltaY * 400;
4804
+ return delta * 400;
4795
4805
  default:
4796
- return e.deltaY;
4806
+ return delta;
4797
4807
  }
4798
4808
  }
4809
+ var TRACKPAD_MAX_DELTA = 20;
4810
+ var MOUSE_WHEEL_DAMPING = 0.4;
4811
+ function softenWheelDelta(d) {
4812
+ const a = Math.abs(d);
4813
+ if (a <= TRACKPAD_MAX_DELTA) return d;
4814
+ return Math.sign(d) * (TRACKPAD_MAX_DELTA + (a - TRACKPAD_MAX_DELTA) * MOUSE_WHEEL_DAMPING);
4815
+ }
4799
4816
  function attachViewportInput(options) {
4800
4817
  const {
4801
4818
  element,
@@ -4818,8 +4835,8 @@ function attachViewportInput(options) {
4818
4835
  const onWheel = (e) => {
4819
4836
  if (e.ctrlKey || e.metaKey) {
4820
4837
  e.preventDefault();
4821
- const dy = wheelDeltaYPixels(e);
4822
- const normDy = Math.abs(dy) < 20 ? dy * 12 : dy;
4838
+ const dy = wheelDeltaPixels(e.deltaY, e.deltaMode);
4839
+ const normDy = Math.abs(dy) < TRACKPAD_MAX_DELTA ? dy * 12 : dy;
4823
4840
  const factor = Math.exp(-normDy * wheelZoomSensitivity);
4824
4841
  const rect = element.getBoundingClientRect();
4825
4842
  camera.setZoom(camera.zoom * factor, {
@@ -4830,8 +4847,10 @@ function attachViewportInput(options) {
4830
4847
  return;
4831
4848
  }
4832
4849
  e.preventDefault();
4833
- camera.x -= e.deltaX * wheelPanSensitivity / camera.zoom;
4834
- camera.y -= e.deltaY * wheelPanSensitivity / camera.zoom;
4850
+ const panDx = softenWheelDelta(wheelDeltaPixels(e.deltaX, e.deltaMode));
4851
+ const panDy = softenWheelDelta(wheelDeltaPixels(e.deltaY, e.deltaMode));
4852
+ camera.x -= panDx * wheelPanSensitivity / camera.zoom;
4853
+ camera.y -= panDy * wheelPanSensitivity / camera.zoom;
4835
4854
  onUpdate();
4836
4855
  };
4837
4856
  const onPointerDown = (e) => {
@@ -7658,15 +7677,22 @@ var VectorViewport = react.forwardRef(
7658
7677
  react.useEffect(() => {
7659
7678
  rememberImageBlobHrefs(items, rememberedImageBlobHrefsRef.current);
7660
7679
  }, [items]);
7661
- react.useEffect(
7662
- () => () => {
7663
- releaseRememberedBlobHrefs(
7664
- rememberedImageBlobHrefsRef.current,
7665
- (href) => URL.revokeObjectURL(href)
7666
- );
7667
- },
7668
- []
7669
- );
7680
+ const blobRevokeTimerRef = react.useRef(null);
7681
+ react.useEffect(() => {
7682
+ if (blobRevokeTimerRef.current !== null) {
7683
+ clearTimeout(blobRevokeTimerRef.current);
7684
+ blobRevokeTimerRef.current = null;
7685
+ }
7686
+ return () => {
7687
+ blobRevokeTimerRef.current = setTimeout(() => {
7688
+ releaseRememberedBlobHrefs(
7689
+ rememberedImageBlobHrefsRef.current,
7690
+ (href) => URL.revokeObjectURL(href)
7691
+ );
7692
+ blobRevokeTimerRef.current = null;
7693
+ }, 100);
7694
+ };
7695
+ }, []);
7670
7696
  const PASTE_OFFSET_WORLD = 24;
7671
7697
  const copyIdsToInternalClipboard = react.useCallback((ids) => {
7672
7698
  if (ids.length === 0) return;