@principal-ai/file-city-react 0.5.12 → 0.5.13

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.
@@ -569,7 +569,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
569
569
  const { camera } = useThree();
570
570
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
571
571
  const controlsRef = useRef(null);
572
- // Start false - only block rotation during active camera animations
572
+ const prevTargetRef = useRef(null);
573
573
  const isAnimatingRef = useRef(false);
574
574
  // Animated camera position and target
575
575
  const targetPos = useMemo(() => {
@@ -598,17 +598,9 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
598
598
  targetZ: 0,
599
599
  };
600
600
  }, [focusTarget, isFlat, citySize]);
601
- // Set initial camera position on mount
602
- useEffect(() => {
603
- camera.position.set(targetPos.x, targetPos.y, targetPos.z);
604
- if (controlsRef.current) {
605
- controlsRef.current.target.set(targetPos.targetX, targetPos.targetY, targetPos.targetZ);
606
- controlsRef.current.update();
607
- }
608
- // Only run on mount
609
- // eslint-disable-next-line react-hooks/exhaustive-deps
610
- }, []);
611
- // Spring animation for camera movement (only for subsequent changes)
601
+ // Create a stable key to detect when target actually changes
602
+ const targetKey = `${targetPos.x},${targetPos.y},${targetPos.z},${targetPos.targetX},${targetPos.targetZ}`;
603
+ // Spring animation for camera movement
612
604
  const { camX, camY, camZ, lookX, lookY, lookZ } = useSpring({
613
605
  camX: targetPos.x,
614
606
  camY: targetPos.y,
@@ -617,6 +609,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
617
609
  lookY: targetPos.targetY,
618
610
  lookZ: targetPos.targetZ,
619
611
  config: { tension: 60, friction: 20 },
612
+ immediate: prevTargetRef.current === null, // Skip animation on first render
620
613
  onStart: () => {
621
614
  isAnimatingRef.current = true;
622
615
  },
@@ -624,14 +617,20 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }) {
624
617
  isAnimatingRef.current = false;
625
618
  },
626
619
  });
627
- // Update camera each frame only while spring is animating
628
- // Once animation settles, let OrbitControls handle user interaction
620
+ // Track target changes
621
+ useEffect(() => {
622
+ prevTargetRef.current = targetKey;
623
+ }, [targetKey]);
624
+ // Update camera each frame while animating or on first frame
629
625
  useFrame(() => {
630
- if (!controlsRef.current || !isAnimatingRef.current)
626
+ if (!controlsRef.current)
631
627
  return;
632
- camera.position.set(camX.get(), camY.get(), camZ.get());
633
- controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
634
- controlsRef.current.update();
628
+ // On first frame or while animating, update camera from spring values
629
+ if (prevTargetRef.current === null || isAnimatingRef.current) {
630
+ camera.position.set(camX.get(), camY.get(), camZ.get());
631
+ controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
632
+ controlsRef.current.update();
633
+ }
635
634
  });
636
635
  const resetToInitial = useCallback(() => {
637
636
  const targetHeight = isFlat ? citySize * 1.5 : citySize * 1.1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principal-ai/file-city-react",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "type": "module",
5
5
  "description": "React components for File City visualization",
6
6
  "main": "dist/index.js",
@@ -996,7 +996,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
996
996
  const { camera } = useThree();
997
997
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
998
998
  const controlsRef = useRef<any>(null);
999
- // Start false - only block rotation during active camera animations
999
+ const prevTargetRef = useRef<string | null>(null);
1000
1000
  const isAnimatingRef = useRef(false);
1001
1001
 
1002
1002
  // Animated camera position and target
@@ -1027,18 +1027,10 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
1027
1027
  };
1028
1028
  }, [focusTarget, isFlat, citySize]);
1029
1029
 
1030
- // Set initial camera position on mount
1031
- useEffect(() => {
1032
- camera.position.set(targetPos.x, targetPos.y, targetPos.z);
1033
- if (controlsRef.current) {
1034
- controlsRef.current.target.set(targetPos.targetX, targetPos.targetY, targetPos.targetZ);
1035
- controlsRef.current.update();
1036
- }
1037
- // Only run on mount
1038
- // eslint-disable-next-line react-hooks/exhaustive-deps
1039
- }, []);
1030
+ // Create a stable key to detect when target actually changes
1031
+ const targetKey = `${targetPos.x},${targetPos.y},${targetPos.z},${targetPos.targetX},${targetPos.targetZ}`;
1040
1032
 
1041
- // Spring animation for camera movement (only for subsequent changes)
1033
+ // Spring animation for camera movement
1042
1034
  const { camX, camY, camZ, lookX, lookY, lookZ } = useSpring({
1043
1035
  camX: targetPos.x,
1044
1036
  camY: targetPos.y,
@@ -1047,6 +1039,7 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
1047
1039
  lookY: targetPos.targetY,
1048
1040
  lookZ: targetPos.targetZ,
1049
1041
  config: { tension: 60, friction: 20 },
1042
+ immediate: prevTargetRef.current === null, // Skip animation on first render
1050
1043
  onStart: () => {
1051
1044
  isAnimatingRef.current = true;
1052
1045
  },
@@ -1055,14 +1048,21 @@ function AnimatedCamera({ citySize, isFlat, focusTarget }: AnimatedCameraProps)
1055
1048
  },
1056
1049
  });
1057
1050
 
1058
- // Update camera each frame only while spring is animating
1059
- // Once animation settles, let OrbitControls handle user interaction
1051
+ // Track target changes
1052
+ useEffect(() => {
1053
+ prevTargetRef.current = targetKey;
1054
+ }, [targetKey]);
1055
+
1056
+ // Update camera each frame while animating or on first frame
1060
1057
  useFrame(() => {
1061
- if (!controlsRef.current || !isAnimatingRef.current) return;
1058
+ if (!controlsRef.current) return;
1062
1059
 
1063
- camera.position.set(camX.get(), camY.get(), camZ.get());
1064
- controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
1065
- controlsRef.current.update();
1060
+ // On first frame or while animating, update camera from spring values
1061
+ if (prevTargetRef.current === null || isAnimatingRef.current) {
1062
+ camera.position.set(camX.get(), camY.get(), camZ.get());
1063
+ controlsRef.current.target.set(lookX.get(), lookY.get(), lookZ.get());
1064
+ controlsRef.current.update();
1065
+ }
1066
1066
  });
1067
1067
 
1068
1068
  const resetToInitial = useCallback(() => {