@arronqzy/react-view 1.0.3 → 1.0.7

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.
Files changed (43) hide show
  1. package/dist/styles.css +555 -15
  2. package/package.json +14 -7
  3. package/readme.md +74 -5
  4. package/src/core/template.tsx +12 -2
  5. package/src/panel/ReactViewOnlinePreview.tsx +18 -8
  6. package/src/panel/ReactViewPanel.tsx +403 -177
  7. package/src/panel/ai/AssistantChatPanel.tsx +895 -0
  8. package/src/panel/ai/AssistantMarkdown.tsx +256 -0
  9. package/src/panel/ai/applyAssistantAction.ts +652 -0
  10. package/src/panel/ai/revealConfigField.ts +137 -0
  11. package/src/panel/components/ConfigHintIcon.tsx +50 -0
  12. package/src/panel/components/ElementsLayer.tsx +39 -22
  13. package/src/panel/components/MaterialSidebar.tsx +157 -109
  14. package/src/panel/components/MoveableLayer.tsx +75 -61
  15. package/src/panel/components/PanelCanvas.tsx +81 -64
  16. package/src/panel/components/PanelConfigSidebar.tsx +471 -448
  17. package/src/panel/components/SelectLayer.tsx +1 -0
  18. package/src/panel/components/ViewElementScopePanel.tsx +13 -12
  19. package/src/panel/components/WorkspaceConfigSidebar.tsx +5 -2
  20. package/src/panel/components/WorkspaceProjectNav.tsx +25 -14
  21. package/src/panel/components/WorkspaceStageSplit.tsx +3 -1
  22. package/src/panel/components/scope-config/ScopeConfigContext.tsx +9 -0
  23. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.tsx +242 -0
  24. package/src/panel/components/scope-config/ScopeTemplateUsageHint.tsx +12 -0
  25. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.tsx +6 -3
  26. package/src/panel/components/table/ConditionEditor.tsx +205 -0
  27. package/src/panel/components/table/JsonCodeEditor.tsx +115 -0
  28. package/src/panel/components/table/PanelConfigTableSection.tsx +2192 -0
  29. package/src/panel/components/table/TableNodeContent.tsx +544 -0
  30. package/src/panel/constants/messages.ts +76 -34
  31. package/src/panel/hooks/usePanelElements.ts +64 -52
  32. package/src/panel/hooks/useRafThrottledScroll.ts +30 -0
  33. package/src/panel/hooks/useWorkspaceProjects.ts +11 -8
  34. package/src/panel/types.ts +6 -0
  35. package/src/panel/utils/chartOptionBuilder.ts +12 -1
  36. package/src/panel/utils/panelElementDefaults.ts +69 -39
  37. package/src/panel/utils/scope-field-labels.ts +51 -37
  38. package/src/panel/utils/scope-template-chart.ts +92 -0
  39. package/src/panel/utils/scope-template-preview.ts +128 -0
  40. package/src/panel/utils/scope-template-spread.ts +252 -0
  41. package/src/panel/utils/scope-template-warnings.ts +131 -11
  42. package/src/panel/utils/scope-template.ts +105 -3
  43. package/src/tailwind.css +36 -15
@@ -1,4 +1,5 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { useI18n } from "@arronqzy/i18n/react";
2
3
  import { createPortal } from "react-dom";
3
4
  import Moveable from "react-moveable";
4
5
 
@@ -41,8 +42,8 @@ export type MoveableLayerProps = {
41
42
  ) => void;
42
43
  /** 画布平移/缩放等视口变化时递增或变化,用于触发控制框重算 */
43
44
  refreshToken?: number | string;
44
- /** InfiniteViewer 虚拟滚动位置(无原生 scroll 事件) */
45
- viewportScroll?: { left: number; top: number };
45
+ /** 平移后立即同步控制框(由 PanelCanvas 写入,绕过 React scroll 节流) */
46
+ viewportSyncRef?: React.MutableRefObject<(() => void) | null>;
46
47
  };
47
48
 
48
49
  function getGridSlotLayout(grid: PanelElement) {
@@ -155,6 +156,17 @@ function isDescendantGrid(
155
156
  return false;
156
157
  }
157
158
 
159
+ function readLiveCanvasScale(
160
+ canvasContainer: HTMLElement | null,
161
+ fallback: number
162
+ ): number {
163
+ if (!canvasContainer) return fallback;
164
+ const raw = canvasContainer.dataset.canvasScale;
165
+ if (!raw) return fallback;
166
+ const parsed = Number(raw);
167
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
168
+ }
169
+
158
170
  export function MoveableLayer({
159
171
  zoomX,
160
172
  zoomY,
@@ -164,8 +176,10 @@ export function MoveableLayer({
164
176
  elementsById,
165
177
  updateElement,
166
178
  refreshToken,
167
- viewportScroll,
179
+ viewportSyncRef,
168
180
  }: MoveableLayerProps) {
181
+ const { t } = useI18n();
182
+
169
183
  const moveableRef = useRef<any>(null);
170
184
  const targets = useMemo(() => {
171
185
  const validTargets = selectedTargets.filter(
@@ -210,15 +224,13 @@ export function MoveableLayer({
210
224
  [getId, movableTargets]
211
225
  );
212
226
  const moveableRenderKey = useMemo(() => movableIds.join(","), [movableIds]);
213
- const canvasScale = useMemo(
227
+ const propCanvasScale = useMemo(
214
228
  () => uniformViewportZoom({ x: zoomX, y: zoomY }),
215
229
  [zoomX, zoomY]
216
230
  );
217
- /** 控制框在 scale 画布内渲染,用倒数保持屏幕像素尺寸 */
218
- const moveableControlZoom = useMemo(
219
- () => 1 / Math.max(0.0001, canvasScale),
220
- [canvasScale]
221
- );
231
+ const canvasScale = readLiveCanvasScale(canvasContainer, propCanvasScale);
232
+ /** 在 scale 画布内渲染,用倒数补偿,保证锚点/边线屏幕像素恒定 */
233
+ const moveableControlZoom = 1 / Math.max(0.0001, canvasScale);
222
234
  const pointerRoot = dragContainer ?? canvasContainer;
223
235
  const resolveSingleEventId = useCallback(
224
236
  (target: HTMLElement | undefined | null) => {
@@ -270,9 +282,10 @@ export function MoveableLayer({
270
282
  const [lockBadgeScreen, setLockBadgeScreen] = useState<{ x: number; y: number } | null>(
271
283
  null
272
284
  );
273
- useEffect(() => {
285
+ const syncRafRef = useRef<number | null>(null);
286
+ const syncLockBadge = useCallback(() => {
274
287
  const id = lockedSelected[0];
275
- if (!id || !canvasContainer) {
288
+ if (!id || !pointerRoot) {
276
289
  setLockBadgeScreen(null);
277
290
  return;
278
291
  }
@@ -283,33 +296,37 @@ export function MoveableLayer({
283
296
  setLockBadgeScreen(null);
284
297
  return;
285
298
  }
286
- const sync = () => {
287
- const rect = target.getBoundingClientRect();
288
- const rootRect = canvasContainer.getBoundingClientRect();
289
- setLockBadgeScreen({
290
- x: rect.left - rootRect.left,
291
- y: rect.top - rootRect.top,
292
- });
293
- };
294
- sync();
295
- const ro = new ResizeObserver(sync);
296
- ro.observe(target);
297
- ro.observe(canvasContainer);
298
- canvasContainer.addEventListener("scroll", sync, { passive: true });
299
+ const rect = target.getBoundingClientRect();
300
+ const rootRect = pointerRoot.getBoundingClientRect();
301
+ setLockBadgeScreen({
302
+ x: rect.left - rootRect.left,
303
+ y: rect.top - rootRect.top,
304
+ });
305
+ }, [lockedSelected, pointerRoot, selectedTargets]);
306
+
307
+ const scheduleViewportSync = useCallback(() => {
308
+ if (syncRafRef.current != null) return;
309
+ syncRafRef.current = requestAnimationFrame(() => {
310
+ syncRafRef.current = null;
311
+ moveableRef.current?.updateRect?.();
312
+ syncLockBadge();
313
+ });
314
+ }, [syncLockBadge]);
315
+
316
+ useEffect(() => {
317
+ if (!viewportSyncRef) return;
318
+ viewportSyncRef.current = scheduleViewportSync;
299
319
  return () => {
300
- ro.disconnect();
301
- canvasContainer.removeEventListener("scroll", sync);
320
+ viewportSyncRef.current = null;
302
321
  };
303
- }, [canvasContainer, lockedSelected, selectedTargets, viewportScroll, zoomX, zoomY]);
322
+ }, [scheduleViewportSync, viewportSyncRef]);
304
323
 
305
- const updateRectNextFrame = useCallback(() => {
306
- // InfiniteViewer 用 transform 平移,需等视口与节点布局提交后再 updateRect
307
- requestAnimationFrame(() => {
308
- requestAnimationFrame(() => {
309
- moveableRef.current?.updateRect?.();
310
- });
311
- });
312
- }, []);
324
+ useLayoutEffect(() => {
325
+ moveableRef.current?.updateRect?.();
326
+ syncLockBadge();
327
+ }, [canvasScale, syncLockBadge]);
328
+
329
+ const updateRectNextFrame = scheduleViewportSync;
313
330
  const getGridDirectChildren = useCallback(
314
331
  (gridId: string) => {
315
332
  const result: PanelElement[] = [];
@@ -421,22 +438,16 @@ export function MoveableLayer({
421
438
  useEffect(() => {
422
439
  if (!targets) return;
423
440
  updateRectNextFrame();
424
- }, [targets, updateRectNextFrame]);
425
-
426
- useEffect(() => {
427
- if (!targets) return;
428
- updateRectNextFrame();
429
- }, [refreshToken, targets, updateRectNextFrame]);
441
+ }, [moveableRenderKey, refreshToken, targets, updateRectNextFrame]);
430
442
 
431
- useEffect(() => {
432
- if (!targets) return;
433
- updateRectNextFrame();
434
- }, [targets, updateRectNextFrame, viewportScroll?.left, viewportScroll?.top]);
435
-
436
- useEffect(() => {
437
- if (!targets) return;
438
- updateRectNextFrame();
439
- }, [targets, updateRectNextFrame, zoomX, zoomY]);
443
+ useEffect(
444
+ () => () => {
445
+ if (syncRafRef.current != null) {
446
+ cancelAnimationFrame(syncRafRef.current);
447
+ }
448
+ },
449
+ []
450
+ );
440
451
 
441
452
  if (!canvasContainer) return null;
442
453
 
@@ -445,19 +456,22 @@ export function MoveableLayer({
445
456
  <div
446
457
  className="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
447
458
  style={{ left: lockBadgeScreen.x + 6, top: lockBadgeScreen.y - 22 }}
448
- title="节点已锁定"
449
- >
459
+ title={t("panel.config.nodeLockedTitle")}>
450
460
  <LockGlyph />
451
- <span>已锁定</span>
461
+ <span>{t("panel.config.nodeLockedBadge")}</span>
452
462
  </div>
453
463
  ) : null;
454
464
 
455
465
  if (!targets) {
456
- return lockBadgeOnly ? createPortal(lockBadgeOnly, canvasContainer) : null;
466
+ return lockBadgeOnly && pointerRoot
467
+ ? createPortal(lockBadgeOnly, pointerRoot)
468
+ : null;
457
469
  }
458
470
 
459
471
  if (!movableTargets) {
460
- return lockBadgeOnly ? createPortal(lockBadgeOnly, canvasContainer) : null;
472
+ return lockBadgeOnly && pointerRoot
473
+ ? createPortal(lockBadgeOnly, pointerRoot)
474
+ : null;
461
475
  }
462
476
 
463
477
  const layer = (
@@ -469,9 +483,10 @@ export function MoveableLayer({
469
483
  target={movableTargets as unknown as HTMLElement[]}
470
484
  rootContainer={canvasContainer}
471
485
  container={canvasContainer}
472
- dragContainer={pointerRoot ?? canvasContainer}
486
+ dragContainer={pointerRoot}
473
487
  className="rv-moveable-layer"
474
488
  zoom={moveableControlZoom}
489
+ hideChildMoveableDefaultLines={false}
475
490
  // 允许在控制框内部区域拖动(不必必须点到某个节点本体)
476
491
  dragArea
477
492
  draggable
@@ -789,15 +804,14 @@ export function MoveableLayer({
789
804
  <div
790
805
  className="pointer-events-none absolute z-[80] inline-flex select-none items-center gap-1 rounded border border-border bg-background/95 px-1.5 py-0.5 text-[11px] text-foreground shadow-sm"
791
806
  style={{ left: lockBadgeScreen.x + 6, top: lockBadgeScreen.y - 22 }}
792
- title="部分节点已锁定,已自动排除"
793
- >
807
+ title={t("panel.config.lockedExcludedTitle")}>
794
808
  <LockGlyph />
795
- <span>锁定节点已排除</span>
809
+ <span>{t("panel.config.lockedExcludedBadge")}</span>
796
810
  </div>
797
811
  ) : null}
798
812
  </>
799
813
  );
800
814
 
801
- return createPortal(layer, canvasContainer);
815
+ return canvasContainer ? createPortal(layer, canvasContainer) : null;
802
816
  }
803
817
 
@@ -1,6 +1,5 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
2
  import InfiniteViewer from "react-infinite-viewer";
3
- import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
4
3
 
5
4
  import {
6
5
  clampViewportZoom,
@@ -23,6 +22,8 @@ export type PanelCanvasProps = {
23
22
  onDropMaterial?: (payload: { materialId: string; x: number; y: number }) => void;
24
23
  /** 渲染在 InfiniteViewer 内、画布 scale 层之外(如 Moveable 控制框) */
25
24
  viewportOverlay?: React.ReactNode;
25
+ /** 平移等视口变换后同步 Moveable 控制框(绕过 React scroll 节流) */
26
+ viewportSyncRef?: React.MutableRefObject<(() => void) | null>;
26
27
  };
27
28
 
28
29
  export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
@@ -40,12 +41,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
40
41
  onCanvasContextMenuCapture,
41
42
  onDropMaterial,
42
43
  viewportOverlay,
44
+ viewportSyncRef,
43
45
  },
44
46
  scrollRef
45
47
  ) => {
46
48
  const viewportRef = useRef<HTMLDivElement | null>(null);
47
49
  const viewerRef = useRef<any>(null);
48
- const transformRef = useRef<any>(null);
49
50
  const canvasElRef = useRef<HTMLDivElement | null>(null);
50
51
  const syncingZoomRef = useRef(false);
51
52
  const didAutoCenterRef = useRef(false);
@@ -76,6 +77,14 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
76
77
  height: number;
77
78
  } | null>(null);
78
79
 
80
+ const applyCanvasTransform = useCallback((scale: number) => {
81
+ const el = canvasElRef.current;
82
+ if (!el) return;
83
+ el.style.transform = `scale(${scale})`;
84
+ el.style.transformOrigin = "0 0";
85
+ el.dataset.canvasScale = String(scale);
86
+ }, []);
87
+
79
88
  const commitScroll = useCallback(
80
89
  (left: number, top: number) => {
81
90
  const next = { left, top };
@@ -85,6 +94,10 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
85
94
  [onScrollChange]
86
95
  );
87
96
 
97
+ const notifyViewportSync = useCallback(() => {
98
+ viewportSyncRef?.current?.();
99
+ }, [viewportSyncRef]);
100
+
88
101
  useEffect(() => {
89
102
  zoomRef.current = zoom;
90
103
  }, [zoom]);
@@ -155,8 +168,6 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
155
168
  if (!el) return;
156
169
 
157
170
  const onWheelZoom = (e: WheelEvent) => {
158
- const api = transformRef.current;
159
- if (!api) return;
160
171
  const viewer = viewerRef.current as any;
161
172
  if (!viewer) return;
162
173
  const target = e.target as HTMLElement | null;
@@ -189,10 +200,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
189
200
  const worldY = (pointerY + currentScrollTop) / Math.max(0.0001, zy);
190
201
  const nextScrollLeft = worldX * nextZoom.x - pointerX;
191
202
  const nextScrollTop = worldY * nextZoom.y - pointerY;
203
+ const nextScale = uniformViewportZoom(nextZoom);
192
204
 
193
205
  syncingZoomRef.current = true;
206
+ zoomRef.current = nextZoom;
207
+ applyCanvasTransform(nextScale);
194
208
  viewer.scrollTo(nextScrollLeft, nextScrollTop);
195
- commitScroll(nextScrollLeft, nextScrollTop);
196
209
  const { width: vw, height: vh } = viewportSizeRef.current;
197
210
  resizeLayoutAnchorRef.current = {
198
211
  viewportWidth: vw,
@@ -208,25 +221,22 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
208
221
  zoomX: nextZoom.x,
209
222
  zoomY: nextZoom.y,
210
223
  };
211
- zoomRef.current = nextZoom;
212
224
  onZoomChange({
213
225
  x: Number(nextZoom.x.toFixed(4)),
214
226
  y: Number(nextZoom.y.toFixed(4)),
215
227
  });
216
- requestAnimationFrame(() => {
217
- syncingZoomRef.current = false;
218
- });
219
228
  };
220
229
 
221
230
  el.addEventListener("wheel", onWheelZoom, { passive: false, capture: true });
222
231
  return () => {
223
232
  el.removeEventListener("wheel", onWheelZoom, true);
224
233
  };
225
- }, [commitScroll, onZoomChange, viewport.width, viewport.height]);
234
+ }, [applyCanvasTransform, onZoomChange, viewport.width, viewport.height]);
226
235
 
227
236
  const applyViewportResizeZoom = useCallback(
228
237
  (w: number, h: number) => {
229
238
  if (w < 8 || h < 8) return;
239
+ if (syncingZoomRef.current) return;
230
240
 
231
241
  const viewer = viewerRef.current as {
232
242
  scrollTo?: (x: number, y: number) => void;
@@ -285,8 +295,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
285
295
  const nextZoom = { x: nextZoomValue, y: nextZoomValue };
286
296
 
287
297
  syncingZoomRef.current = true;
298
+ zoomRef.current = nextZoom;
299
+ applyCanvasTransform(nextZoomValue);
288
300
  viewer.scrollTo?.(nextScrollLeft, nextScrollTop);
289
- commitScroll(nextScrollLeft, nextScrollTop);
290
301
  resizeLayoutAnchorRef.current = {
291
302
  viewportWidth: w,
292
303
  viewportHeight: h,
@@ -301,16 +312,12 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
301
312
  zoomX: nextZoomValue,
302
313
  zoomY: nextZoomValue,
303
314
  };
304
- zoomRef.current = nextZoom;
305
315
  onZoomChange({
306
316
  x: Number(nextZoomValue.toFixed(4)),
307
317
  y: Number(nextZoomValue.toFixed(4)),
308
318
  });
309
- requestAnimationFrame(() => {
310
- syncingZoomRef.current = false;
311
- });
312
319
  },
313
- [commitScroll, onZoomChange]
320
+ [applyCanvasTransform, onZoomChange]
314
321
  );
315
322
 
316
323
  const syncViewportElement = useCallback(
@@ -326,11 +333,14 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
326
333
  const syncCanvasElement = useCallback(
327
334
  (el: HTMLDivElement | null) => {
328
335
  canvasElRef.current = el;
336
+ if (el) {
337
+ applyCanvasTransform(uniformViewportZoom(zoomRef.current));
338
+ }
329
339
  if (!canvasRef) return;
330
340
  if (typeof canvasRef === "function") canvasRef(el);
331
341
  else (canvasRef as React.MutableRefObject<HTMLDivElement | null>).current = el;
332
342
  },
333
- [canvasRef]
343
+ [applyCanvasTransform, canvasRef]
334
344
  );
335
345
 
336
346
  useEffect(() => {
@@ -465,15 +475,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
465
475
  const nextLeft = panRef.current.startLeft - dx;
466
476
  const nextTop = panRef.current.startTop - dy;
467
477
  viewer.scrollTo(nextLeft, nextTop);
468
- const left =
469
- typeof viewer.getScrollLeft === "function"
470
- ? viewer.getScrollLeft()
471
- : nextLeft;
472
- const top =
473
- typeof viewer.getScrollTop === "function"
474
- ? viewer.getScrollTop()
475
- : nextTop;
476
- commitScroll(left, top);
478
+ lastScrollRef.current = { left: nextLeft, top: nextTop };
479
+ onScrollChange?.({ left: nextLeft, top: nextTop });
480
+ notifyViewportSync();
477
481
  };
478
482
 
479
483
  const up = (ev: MouseEvent) => {
@@ -498,7 +502,7 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
498
502
  el.removeEventListener("contextmenu", onContextMenu, true);
499
503
  el.removeEventListener("mousedown", onMouseDown, true);
500
504
  };
501
- }, [clearWindowPanListeners, commitScroll]);
505
+ }, [clearWindowPanListeners, commitScroll, notifyViewportSync, onScrollChange]);
502
506
 
503
507
  const resolvedContentSize = useMemo(() => {
504
508
  if (contentSize) return contentSize;
@@ -511,6 +515,35 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
511
515
 
512
516
  const canvasScale = useMemo(() => uniformViewportZoom(zoom), [zoom.x, zoom.y]);
513
517
 
518
+ const reconcileCanvasTransform = useCallback(() => {
519
+ const scale = syncingZoomRef.current
520
+ ? uniformViewportZoom(zoomRef.current)
521
+ : uniformViewportZoom(zoom);
522
+ applyCanvasTransform(scale);
523
+ }, [applyCanvasTransform, zoom.x, zoom.y]);
524
+
525
+ useLayoutEffect(() => {
526
+ reconcileCanvasTransform();
527
+ }, [canvasScale, reconcileCanvasTransform]);
528
+
529
+ useEffect(() => {
530
+ if (
531
+ Math.abs(zoom.x - zoomRef.current.x) < 0.0001 &&
532
+ Math.abs(zoom.y - zoomRef.current.y) < 0.0001
533
+ ) {
534
+ syncingZoomRef.current = false;
535
+ }
536
+ }, [zoom.x, zoom.y]);
537
+
538
+ const worldStyle = useMemo<React.CSSProperties>(
539
+ () => ({
540
+ width: resolvedContentSize.width,
541
+ height: resolvedContentSize.height,
542
+ position: "relative",
543
+ }),
544
+ [resolvedContentSize.height, resolvedContentSize.width]
545
+ );
546
+
514
547
  const style = useMemo<React.CSSProperties>(
515
548
  () => ({
516
549
  width: resolvedContentSize.width,
@@ -518,10 +551,9 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
518
551
  minWidth: "100%",
519
552
  minHeight: "100%",
520
553
  position: "relative",
521
- transform: `scale(${canvasScale})`,
522
554
  transformOrigin: "0 0",
523
555
  }),
524
- [canvasScale, resolvedContentSize.height, resolvedContentSize.width]
556
+ [resolvedContentSize.height, resolvedContentSize.width]
525
557
  );
526
558
 
527
559
  const hasMaterialPayload = useCallback((types: ArrayLike<string> | null | undefined) => {
@@ -613,44 +645,29 @@ export const PanelCanvas = React.forwardRef<HTMLDivElement, PanelCanvasProps>(
613
645
  const next = { left: e.scrollLeft ?? 0, top: e.scrollTop ?? 0 };
614
646
  lastScrollRef.current = next;
615
647
  onScrollChange?.(next);
648
+ if (!syncingZoomRef.current) {
649
+ notifyViewportSync();
650
+ }
616
651
  }}
617
652
  >
618
- <TransformWrapper
619
- ref={transformRef}
620
- initialScale={1}
621
- minScale={1}
622
- maxScale={1}
623
- centerOnInit={false}
624
- limitToBounds={false}
625
- panning={{ disabled: true }}
626
- doubleClick={{ disabled: true }}
627
- pinch={{ disabled: true }}
628
- wheel={{ step: 0.008, disabled: true, wheelDisabled: true }}
629
- centerZoomedOut={false}
630
- >
631
- <TransformComponent
632
- // 始终占满操作容器,但不在这一层裁切放大后的内容
633
- wrapperStyle={{ width: "100%", height: "100%", overflow: "visible" }}
634
- contentStyle={{ width: "100%", height: "100%", overflow: "visible" }}
653
+ <div style={worldStyle}>
654
+ <div
655
+ ref={syncCanvasElement}
656
+ data-panel-canvas
657
+ style={style}
658
+ className="h-full w-full"
659
+ onMouseDownCapture={onCanvasMouseDownCapture}
660
+ onClickCapture={onCanvasClickCapture}
661
+ onContextMenuCapture={onCanvasContextMenuCapture}
635
662
  >
636
- <div
637
- ref={syncCanvasElement}
638
- data-panel-canvas
639
- style={style}
640
- className=""
641
- onMouseDownCapture={onCanvasMouseDownCapture}
642
- onClickCapture={onCanvasClickCapture}
643
- onContextMenuCapture={onCanvasContextMenuCapture}
644
- >
645
- {children}
646
- {viewportOverlay ? (
647
- <div className="rv-viewport-overlay pointer-events-none absolute inset-0 z-[70] overflow-visible [&_.moveable-control-box]:pointer-events-auto">
648
- {viewportOverlay}
649
- </div>
650
- ) : null}
663
+ {children}
664
+ </div>
665
+ {viewportOverlay ? (
666
+ <div className="rv-viewport-overlay pointer-events-none absolute inset-0 z-[70] overflow-visible [&_.moveable-control-box]:pointer-events-auto [&_.moveable-group]:pointer-events-auto [&_.moveable-area]:pointer-events-auto">
667
+ {viewportOverlay}
651
668
  </div>
652
- </TransformComponent>
653
- </TransformWrapper>
669
+ ) : null}
670
+ </div>
654
671
  </InfiniteViewer>
655
672
  );
656
673
  }