@bwp-web/canvas 0.15.0 → 1.1.0

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/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  FixedSizeContent: () => FixedSizeContent,
35
35
  ObjectOverlay: () => ObjectOverlay,
36
36
  OverlayBadge: () => OverlayBadge,
37
+ OverlayContainer: () => OverlayContainer,
37
38
  OverlayContent: () => OverlayContent,
38
39
  Point: () => import_fabric19.Point,
39
40
  Polygon: () => import_fabric19.Polygon,
@@ -258,6 +259,7 @@ function setupMousePan(canvas, getMode, isEnabled) {
258
259
  const isModifiedSelect = e instanceof MouseEvent && mode === "select" && (e.metaKey || e.ctrlKey);
259
260
  const shouldPan = mode === "pan" || isMiddleButton || isModifiedSelect || mode === "select" && !opt.target;
260
261
  if (shouldPan) {
262
+ e.preventDefault();
261
263
  isPanning = true;
262
264
  lastPanX = pos.x;
263
265
  lastPanY = pos.y;
@@ -3260,8 +3262,10 @@ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3260
3262
  const [state, setState] = (0, import_react7.useState)({
3261
3263
  visible: false,
3262
3264
  content: null,
3263
- position: { x: 0, y: 0 }
3265
+ position: { x: 0, y: 0 },
3266
+ ref: { current: null }
3264
3267
  });
3268
+ const tooltipElRef = (0, import_react7.useRef)(null);
3265
3269
  const hoveredObjectRef = (0, import_react7.useRef)(null);
3266
3270
  const optionsRef = (0, import_react7.useRef)(options);
3267
3271
  optionsRef.current = options;
@@ -3278,6 +3282,12 @@ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3278
3282
  y: bounds.top * zoom + vt[5] - 10
3279
3283
  };
3280
3284
  }
3285
+ function applyPositionToDOM(position) {
3286
+ const el = tooltipElRef.current;
3287
+ if (!el) return;
3288
+ el.style.left = `${position.x}px`;
3289
+ el.style.top = `${position.y}px`;
3290
+ }
3281
3291
  const handleMouseOver = (e) => {
3282
3292
  const target = e.target;
3283
3293
  if (!target) return;
@@ -3287,7 +3297,7 @@ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3287
3297
  hoveredObjectRef.current = target;
3288
3298
  const position = calculatePosition(target);
3289
3299
  if (position) {
3290
- setState({ visible: true, content, position });
3300
+ setState({ visible: true, content, position, ref: tooltipElRef });
3291
3301
  }
3292
3302
  }
3293
3303
  };
@@ -3301,7 +3311,7 @@ function useCanvasTooltip(canvasRefOrOptions, maybeOptions) {
3301
3311
  if (!hoveredObjectRef.current) return;
3302
3312
  const position = calculatePosition(hoveredObjectRef.current);
3303
3313
  if (position) {
3304
- setState((prev) => prev.visible ? { ...prev, position } : prev);
3314
+ applyPositionToDOM(position);
3305
3315
  }
3306
3316
  };
3307
3317
  canvas.on("mouse:over", handleMouseOver);
@@ -3519,10 +3529,61 @@ function useViewCanvasState() {
3519
3529
  }
3520
3530
 
3521
3531
  // src/overlay/ObjectOverlay.tsx
3532
+ var import_react12 = require("react");
3533
+ var import_material2 = require("@mui/material");
3534
+ var import_fabric18 = require("fabric");
3535
+
3536
+ // src/overlay/OverlayContainer.tsx
3522
3537
  var import_react11 = require("react");
3523
3538
  var import_material = require("@mui/material");
3524
- var import_fabric18 = require("fabric");
3525
3539
  var import_jsx_runtime4 = require("react/jsx-runtime");
3540
+ var OverlayContainerContext = (0, import_react11.createContext)(false);
3541
+ function useIsInsideOverlayContainer() {
3542
+ return (0, import_react11.useContext)(OverlayContainerContext);
3543
+ }
3544
+ function OverlayContainer({
3545
+ canvasRef: canvasRefProp,
3546
+ children
3547
+ }) {
3548
+ const contextCanvasRef = useCanvasRef();
3549
+ const canvasRef = canvasRefProp ?? contextCanvasRef;
3550
+ const containerRef = (0, import_react11.useRef)(null);
3551
+ (0, import_react11.useEffect)(() => {
3552
+ const canvas = canvasRef?.current;
3553
+ const el = containerRef.current;
3554
+ if (!canvas || !el) return;
3555
+ function updateContainer() {
3556
+ if (!canvas || !el) return;
3557
+ const vt = canvas.viewportTransform;
3558
+ if (!vt) return;
3559
+ el.style.transform = `translate(${vt[4]}px, ${vt[5]}px)`;
3560
+ }
3561
+ updateContainer();
3562
+ canvas.on("after:render", updateContainer);
3563
+ return () => {
3564
+ canvas.off("after:render", updateContainer);
3565
+ };
3566
+ }, [canvasRef]);
3567
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverlayContainerContext.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3568
+ import_material.Stack,
3569
+ {
3570
+ ref: containerRef,
3571
+ sx: {
3572
+ position: "absolute",
3573
+ left: 0,
3574
+ top: 0,
3575
+ width: "100%",
3576
+ height: "100%",
3577
+ pointerEvents: "none",
3578
+ willChange: "transform"
3579
+ },
3580
+ children
3581
+ }
3582
+ ) });
3583
+ }
3584
+
3585
+ // src/overlay/ObjectOverlay.tsx
3586
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3526
3587
  function ObjectOverlay({
3527
3588
  canvasRef: canvasRefProp,
3528
3589
  object,
@@ -3532,8 +3593,10 @@ function ObjectOverlay({
3532
3593
  }) {
3533
3594
  const contextCanvasRef = useCanvasRef();
3534
3595
  const canvasRef = canvasRefProp ?? contextCanvasRef;
3535
- const stackRef = (0, import_react11.useRef)(null);
3536
- (0, import_react11.useEffect)(() => {
3596
+ const insideContainer = useIsInsideOverlayContainer();
3597
+ const stackRef = (0, import_react12.useRef)(null);
3598
+ const prev = (0, import_react12.useRef)({ transform: "", w: "", h: "" });
3599
+ (0, import_react12.useEffect)(() => {
3537
3600
  const canvas = canvasRef?.current;
3538
3601
  if (!canvas || !object) return;
3539
3602
  function update() {
@@ -3545,15 +3608,32 @@ function ObjectOverlay({
3545
3608
  const center = object.getCenterPoint();
3546
3609
  const actualWidth = (object.width ?? 0) * (object.scaleX ?? 1);
3547
3610
  const actualHeight = (object.height ?? 0) * (object.scaleY ?? 1);
3548
- const screenCoords = import_fabric18.util.transformPoint(center, vt);
3549
3611
  const screenWidth = actualWidth * zoom;
3550
3612
  const screenHeight = actualHeight * zoom;
3551
3613
  const angle = object.angle ?? 0;
3552
- el.style.left = `${screenCoords.x - screenWidth / 2}px`;
3553
- el.style.top = `${screenCoords.y - screenHeight / 2}px`;
3554
- el.style.width = `${screenWidth}px`;
3555
- el.style.height = `${screenHeight}px`;
3556
- el.style.transform = angle !== 0 ? `rotate(${angle}deg)` : "";
3614
+ let tx;
3615
+ let ty;
3616
+ if (insideContainer) {
3617
+ tx = center.x * zoom - screenWidth / 2;
3618
+ ty = center.y * zoom - screenHeight / 2;
3619
+ } else {
3620
+ const screenCoords = import_fabric18.util.transformPoint(center, vt);
3621
+ tx = screenCoords.x - screenWidth / 2;
3622
+ ty = screenCoords.y - screenHeight / 2;
3623
+ }
3624
+ const transform = angle !== 0 ? `translate(${tx}px, ${ty}px) rotate(${angle}deg)` : `translate(${tx}px, ${ty}px)`;
3625
+ if (transform !== prev.current.transform) {
3626
+ el.style.transform = transform;
3627
+ prev.current.transform = transform;
3628
+ }
3629
+ const w = `${screenWidth}px`;
3630
+ const h = `${screenHeight}px`;
3631
+ if (prev.current.w !== w || prev.current.h !== h) {
3632
+ el.style.width = w;
3633
+ el.style.height = h;
3634
+ prev.current.w = w;
3635
+ prev.current.h = h;
3636
+ }
3557
3637
  }
3558
3638
  update();
3559
3639
  canvas.on("after:render", update);
@@ -3565,19 +3645,23 @@ function ObjectOverlay({
3565
3645
  object.off("moving", update);
3566
3646
  object.off("scaling", update);
3567
3647
  object.off("rotating", update);
3648
+ prev.current = { transform: "", w: "", h: "" };
3568
3649
  };
3569
- }, [canvasRef, object]);
3650
+ }, [canvasRef, object, insideContainer]);
3570
3651
  if (!object) return null;
3571
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3572
- import_material.Stack,
3652
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3653
+ import_material2.Stack,
3573
3654
  {
3574
3655
  ref: stackRef,
3575
3656
  sx: {
3576
3657
  position: "absolute",
3658
+ left: 0,
3659
+ top: 0,
3577
3660
  pointerEvents: "none",
3578
3661
  alignItems: "center",
3579
3662
  justifyContent: "center",
3580
3663
  zIndex: 1,
3664
+ willChange: "transform",
3581
3665
  ...sx
3582
3666
  },
3583
3667
  ...rest,
@@ -3587,28 +3671,28 @@ function ObjectOverlay({
3587
3671
  }
3588
3672
 
3589
3673
  // src/overlay/OverlayContent.tsx
3590
- var import_material2 = require("@mui/material");
3591
- var import_react12 = require("react");
3592
- var import_jsx_runtime5 = require("react/jsx-runtime");
3674
+ var import_material3 = require("@mui/material");
3675
+ var import_react13 = require("react");
3676
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3593
3677
  function OverlayContent({
3594
3678
  children,
3595
- padding = 4,
3679
+ padding = 6,
3596
3680
  maxScale = 2,
3597
3681
  sx,
3598
3682
  ...rest
3599
3683
  }) {
3600
- const outerRef = (0, import_react12.useRef)(null);
3601
- const innerRef = (0, import_react12.useRef)(null);
3602
- (0, import_react12.useEffect)(() => {
3684
+ const outerRef = (0, import_react13.useRef)(null);
3685
+ const innerRef = (0, import_react13.useRef)(null);
3686
+ (0, import_react13.useEffect)(() => {
3603
3687
  const outer = outerRef.current;
3604
3688
  const inner = innerRef.current;
3605
3689
  if (!outer || !inner) return;
3690
+ let natW = 0;
3691
+ let natH = 0;
3606
3692
  function fit() {
3607
3693
  if (!outer || !inner) return;
3608
3694
  const containerW = outer.clientWidth;
3609
3695
  const containerH = outer.clientHeight;
3610
- const natW = inner.scrollWidth;
3611
- const natH = inner.scrollHeight;
3612
3696
  if (natW === 0 || natH === 0 || containerW <= 0 || containerH <= 0) {
3613
3697
  inner.style.transform = "";
3614
3698
  inner.style.removeProperty("--overlay-scale");
@@ -3622,14 +3706,25 @@ function OverlayContent({
3622
3706
  inner.style.transform = `scale(${scale})`;
3623
3707
  inner.style.setProperty("--overlay-scale", String(scale));
3624
3708
  }
3625
- const observer = new ResizeObserver(fit);
3709
+ const observer = new ResizeObserver((entries) => {
3710
+ for (const entry of entries) {
3711
+ if (entry.target === inner) {
3712
+ natW = inner.scrollWidth;
3713
+ natH = inner.scrollHeight;
3714
+ break;
3715
+ }
3716
+ }
3717
+ fit();
3718
+ });
3626
3719
  observer.observe(outer);
3627
3720
  observer.observe(inner);
3721
+ natW = inner.scrollWidth;
3722
+ natH = inner.scrollHeight;
3628
3723
  fit();
3629
3724
  return () => observer.disconnect();
3630
3725
  }, [padding, maxScale]);
3631
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3632
- import_material2.Stack,
3726
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3727
+ import_material3.Stack,
3633
3728
  {
3634
3729
  ref: outerRef,
3635
3730
  sx: {
@@ -3641,8 +3736,8 @@ function OverlayContent({
3641
3736
  ...sx
3642
3737
  },
3643
3738
  ...rest,
3644
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3645
- import_material2.Stack,
3739
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3740
+ import_material3.Stack,
3646
3741
  {
3647
3742
  ref: innerRef,
3648
3743
  sx: {
@@ -3659,9 +3754,9 @@ function OverlayContent({
3659
3754
  }
3660
3755
 
3661
3756
  // src/overlay/FixedSizeContent.tsx
3662
- var import_material3 = require("@mui/material");
3663
- var import_react13 = require("react");
3664
- var import_jsx_runtime6 = require("react/jsx-runtime");
3757
+ var import_material4 = require("@mui/material");
3758
+ var import_react14 = require("react");
3759
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3665
3760
  function FixedSizeContent({
3666
3761
  children,
3667
3762
  hideOnOverflow = true,
@@ -3669,9 +3764,9 @@ function FixedSizeContent({
3669
3764
  sx,
3670
3765
  ...rest
3671
3766
  }) {
3672
- const ref = (0, import_react13.useRef)(null);
3673
- const totalContentHeightRef = (0, import_react13.useRef)(0);
3674
- (0, import_react13.useEffect)(() => {
3767
+ const ref = (0, import_react14.useRef)(null);
3768
+ const totalContentHeightRef = (0, import_react14.useRef)(0);
3769
+ (0, import_react14.useEffect)(() => {
3675
3770
  const el = ref.current;
3676
3771
  if (!el) return;
3677
3772
  let clipAncestor = el.parentElement;
@@ -3682,34 +3777,45 @@ function FixedSizeContent({
3682
3777
  if (!clipAncestor) return;
3683
3778
  const ancestor = clipAncestor;
3684
3779
  totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3685
- function check() {
3686
- requestAnimationFrame(() => {
3687
- if (!el) return;
3688
- const containerRect = ancestor.getBoundingClientRect();
3689
- el.style.maxWidth = `${Math.max(0, containerRect.width - truncationPadding * 2)}px`;
3690
- if (!hideOnOverflow) return;
3691
- const fits = containerRect.height >= totalContentHeightRef.current;
3692
- if (fits && el.style.display === "none") {
3693
- el.style.display = "";
3694
- totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3695
- if (containerRect.height < totalContentHeightRef.current) {
3696
- el.style.display = "none";
3697
- }
3698
- } else if (!fits && el.style.display !== "none") {
3780
+ let rafId = 0;
3781
+ function doCheck() {
3782
+ if (!el) return;
3783
+ const containerW = ancestor.clientWidth;
3784
+ const containerH = ancestor.clientHeight;
3785
+ el.style.maxWidth = `${Math.max(0, containerW - truncationPadding * 2)}px`;
3786
+ if (!hideOnOverflow) return;
3787
+ const fits = containerH >= totalContentHeightRef.current;
3788
+ if (fits && el.style.display === "none") {
3789
+ el.style.display = "";
3790
+ totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3791
+ if (containerH < totalContentHeightRef.current) {
3699
3792
  el.style.display = "none";
3700
3793
  }
3701
- if (el.style.display !== "none") {
3702
- totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3703
- }
3704
- });
3794
+ } else if (!fits && el.style.display !== "none") {
3795
+ el.style.display = "none";
3796
+ }
3797
+ if (el.style.display !== "none") {
3798
+ totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3799
+ }
3800
+ }
3801
+ function scheduleCheck() {
3802
+ if (!rafId) {
3803
+ rafId = requestAnimationFrame(() => {
3804
+ rafId = 0;
3805
+ doCheck();
3806
+ });
3807
+ }
3705
3808
  }
3706
- const observer = new ResizeObserver(check);
3809
+ const observer = new ResizeObserver(scheduleCheck);
3707
3810
  observer.observe(ancestor);
3708
- check();
3709
- return () => observer.disconnect();
3811
+ scheduleCheck();
3812
+ return () => {
3813
+ if (rafId) cancelAnimationFrame(rafId);
3814
+ observer.disconnect();
3815
+ };
3710
3816
  }, [hideOnOverflow, truncationPadding]);
3711
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3712
- import_material3.Stack,
3817
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3818
+ import_material4.Stack,
3713
3819
  {
3714
3820
  ref,
3715
3821
  sx: {
@@ -3733,9 +3839,9 @@ function FixedSizeContent({
3733
3839
  }
3734
3840
 
3735
3841
  // src/overlay/OverlayBadge.tsx
3736
- var import_material4 = require("@mui/material");
3737
- var import_react14 = require("react");
3738
- var import_jsx_runtime7 = require("react/jsx-runtime");
3842
+ var import_material5 = require("@mui/material");
3843
+ var import_react15 = require("react");
3844
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3739
3845
  function toPx(v) {
3740
3846
  if (v === void 0) return void 0;
3741
3847
  return typeof v === "number" ? `${v}px` : v;
@@ -3765,10 +3871,19 @@ function ellipsePosition(angleDeg) {
3765
3871
  function toNum(v) {
3766
3872
  return typeof v === "number" ? v : 0;
3767
3873
  }
3874
+ function readOverlayScale(el) {
3875
+ let node = el.parentElement;
3876
+ while (node) {
3877
+ const val = node.style.getPropertyValue("--overlay-scale");
3878
+ if (val) return parseFloat(val) || 1;
3879
+ node = node.parentElement;
3880
+ }
3881
+ return 1;
3882
+ }
3768
3883
  function OverlayBadge({
3769
3884
  children,
3770
- maxScale = 2,
3771
- minScale = 0.75,
3885
+ maxScale = 1.5,
3886
+ minScale = 0.5,
3772
3887
  top,
3773
3888
  right,
3774
3889
  bottom,
@@ -3777,41 +3892,47 @@ function OverlayBadge({
3777
3892
  sx,
3778
3893
  ...rest
3779
3894
  }) {
3780
- const ref = (0, import_react14.useRef)(null);
3781
- const baseSize = (0, import_react14.useRef)(null);
3782
- (0, import_react14.useEffect)(() => {
3895
+ const ref = (0, import_react15.useRef)(null);
3896
+ const baseSize = (0, import_react15.useRef)(null);
3897
+ (0, import_react15.useEffect)(() => {
3783
3898
  const el = ref.current;
3784
3899
  if (!el) return;
3785
3900
  const ancestor = el.parentElement;
3786
3901
  if (!ancestor) return;
3787
- function update() {
3788
- requestAnimationFrame(() => {
3789
- if (!el || !ancestor) return;
3790
- const containerW = ancestor.clientWidth;
3791
- const containerH = ancestor.clientHeight;
3792
- if (containerW <= 0 || containerH <= 0) {
3793
- el.style.transform = "";
3794
- return;
3795
- }
3796
- if (!baseSize.current) {
3797
- baseSize.current = { w: containerW, h: containerH };
3798
- }
3799
- const ratio = Math.min(
3800
- containerW / baseSize.current.w,
3801
- containerH / baseSize.current.h
3802
- );
3803
- const ownScale = Math.max(minScale, Math.min(ratio, maxScale));
3804
- const overlayScale = parseFloat(
3805
- getComputedStyle(el).getPropertyValue("--overlay-scale")
3806
- ) || 1;
3807
- const scale = `scale(${ownScale / overlayScale})`;
3808
- el.style.transform = circular ? `translate(-50%, -50%) ${scale}` : scale;
3809
- });
3902
+ let rafId = 0;
3903
+ function doUpdate() {
3904
+ if (!el || !ancestor) return;
3905
+ const containerW = ancestor.clientWidth;
3906
+ const containerH = ancestor.clientHeight;
3907
+ if (containerW <= 0 || containerH <= 0) {
3908
+ el.style.transform = "";
3909
+ return;
3910
+ }
3911
+ if (!baseSize.current) {
3912
+ baseSize.current = { w: containerW, h: containerH };
3913
+ }
3914
+ const ratio = Math.min(
3915
+ containerW / baseSize.current.w,
3916
+ containerH / baseSize.current.h
3917
+ );
3918
+ const ownScale = Math.max(minScale, Math.min(ratio, maxScale));
3919
+ const overlayScale = readOverlayScale(el);
3920
+ const scale = `scale(${ownScale / overlayScale})`;
3921
+ el.style.transform = circular ? `translate(-50%, -50%) ${scale}` : scale;
3810
3922
  }
3811
- const observer = new ResizeObserver(update);
3923
+ function scheduleUpdate() {
3924
+ if (!rafId) {
3925
+ rafId = requestAnimationFrame(() => {
3926
+ rafId = 0;
3927
+ doUpdate();
3928
+ });
3929
+ }
3930
+ }
3931
+ const observer = new ResizeObserver(scheduleUpdate);
3812
3932
  observer.observe(ancestor);
3813
- update();
3933
+ scheduleUpdate();
3814
3934
  return () => {
3935
+ if (rafId) cancelAnimationFrame(rafId);
3815
3936
  observer.disconnect();
3816
3937
  baseSize.current = null;
3817
3938
  };
@@ -3831,8 +3952,8 @@ function OverlayBadge({
3831
3952
  bottom: toPx(bottom),
3832
3953
  left: toPx(left)
3833
3954
  };
3834
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3835
- import_material4.Stack,
3955
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3956
+ import_material5.Stack,
3836
3957
  {
3837
3958
  ref,
3838
3959
  sx: {
@@ -3869,6 +3990,7 @@ var import_fabric19 = require("fabric");
3869
3990
  FixedSizeContent,
3870
3991
  ObjectOverlay,
3871
3992
  OverlayBadge,
3993
+ OverlayContainer,
3872
3994
  OverlayContent,
3873
3995
  Point,
3874
3996
  Polygon,