@umituz/react-native-video-editor 1.1.70 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.1.70",
3
+ "version": "1.2.0",
4
4
  "description": "Professional video editor with layer-based timeline, text/image/shape/audio/animation layers, and export functionality",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -43,15 +43,11 @@
43
43
  "react": ">=18.2.0",
44
44
  "react-native": ">=0.74.0",
45
45
  "react-native-gesture-handler": ">=2.0.0",
46
- "react-native-reanimated": ">=3.0.0",
47
46
  "zustand": ">=4.0.0"
48
47
  },
49
48
  "peerDependenciesMeta": {
50
49
  "react-native-gesture-handler": {
51
50
  "optional": true
52
- },
53
- "react-native-reanimated": {
54
- "optional": true
55
51
  }
56
52
  },
57
53
  "devDependencies": {
@@ -74,7 +70,6 @@
74
70
  "react": "19.1.0",
75
71
  "react-native": "0.81.5",
76
72
  "react-native-gesture-handler": "^2.30.0",
77
- "react-native-reanimated": "^3.16.0",
78
73
  "typescript": "~5.9.2"
79
74
  },
80
75
  "publishConfig": {
package/src/index.ts CHANGED
@@ -15,13 +15,11 @@ export type {
15
15
  ImageLayer,
16
16
  VideoLayer,
17
17
  ShapeLayer,
18
- Animation,
19
18
  Audio,
20
19
  ExportSettings,
21
20
  AspectRatio,
22
21
  LayerType,
23
22
  TransitionType,
24
- AnimationType,
25
23
  Position,
26
24
  Size,
27
25
  Transition,
@@ -73,7 +71,6 @@ export { SceneActionsMenu } from "./presentation/components/SceneActionsMenu";
73
71
  export { TextLayerEditor } from "./presentation/components/TextLayerEditor";
74
72
  export { AudioEditor } from "./presentation/components/AudioEditor";
75
73
  export { ShapeLayerEditor } from "./presentation/components/ShapeLayerEditor";
76
- export { AnimationEditor } from "./presentation/components/AnimationEditor";
77
74
  export { DraggableLayer } from "./presentation/components/DraggableLayer";
78
75
  export { ImageLayerEditor } from "./presentation/components/ImageLayerEditor";
79
76
  export { ExportDialog } from "./presentation/components/ExportDialog";
@@ -94,7 +91,6 @@ export { useEditorActions } from "./presentation/hooks/useEditorActions";
94
91
  export { useTextLayerForm } from "./presentation/hooks/useTextLayerForm";
95
92
  export { useImageLayerForm } from "./presentation/hooks/useImageLayerForm";
96
93
  export { useShapeLayerForm } from "./presentation/hooks/useShapeLayerForm";
97
- export { useAnimationLayerForm } from "./presentation/hooks/useAnimationLayerForm";
98
94
  export { useAudioLayerForm } from "./presentation/hooks/useAudioLayerForm";
99
95
  export { useTextLayerOperations } from "./presentation/hooks/useTextLayerOperations";
100
96
  export { useImageLayerOperations } from "./presentation/hooks/useImageLayerOperations";
@@ -6,8 +6,6 @@
6
6
 
7
7
  import { useState, useRef, useCallback, useEffect } from "react";
8
8
  import { Gesture } from "react-native-gesture-handler";
9
- // @ts-ignore - react-native-reanimated is an optional peer dependency
10
- import { runOnJS } from "react-native-reanimated";
11
9
 
12
10
  interface UseDraggableLayerGesturesParams {
13
11
  initialX: number;
@@ -77,23 +75,20 @@ export function useDraggableLayerGestures({
77
75
  const gestureHandler = Gesture.Pan()
78
76
  .onStart(() => {
79
77
  startRef.current = { ...state, x: state.x, y: state.y };
80
- runOnJS(onSelect)();
78
+ onSelect();
81
79
  })
82
80
  .onUpdate((event) => {
83
- 'worklet';
84
81
  const newX = startRef.current.x + event.translationX;
85
82
  const newY = startRef.current.y + event.translationY;
86
- // Use runOnJS for state updates to prevent blocking UI thread
87
- runOnJS(setState)({ x: newX, y: newY, width: state.width, height: state.height });
83
+ setState({ x: newX, y: newY, width: state.width, height: state.height });
88
84
  })
89
85
  .onEnd(() => {
90
- 'worklet';
91
86
  const clampedX = clamp(state.x, 0, canvasWidth - state.width);
92
87
  const clampedY = clamp(state.y, 0, canvasHeight - state.height);
93
88
  const newX = (clampedX / canvasWidth) * 100;
94
89
  const newY = (clampedY / canvasHeight) * 100;
95
- runOnJS(setState)({ x: clampedX, y: clampedY, width: state.width, height: state.height });
96
- runOnJS(onPositionChange)(newX, newY);
90
+ setState({ x: clampedX, y: clampedY, width: state.width, height: state.height });
91
+ onPositionChange(newX, newY);
97
92
  });
98
93
 
99
94
  const createResizeHandler = (
@@ -102,12 +97,10 @@ export function useDraggableLayerGestures({
102
97
  ) => {
103
98
  return Gesture.Pan()
104
99
  .onStart(() => {
105
- 'worklet';
106
100
  startRef.current = { ...state };
107
- runOnJS(onSelect)();
101
+ onSelect();
108
102
  })
109
103
  .onUpdate((event) => {
110
- 'worklet';
111
104
  const newWidth = Math.max(MIN_SIZE, startRef.current.width + deltaX(event.translationX));
112
105
  const newHeight = Math.max(MIN_SIZE, startRef.current.height + deltaY(event.translationY));
113
106
  const clampedWidth = Math.min(newWidth, canvasWidth - startRef.current.x);
@@ -123,10 +116,9 @@ export function useDraggableLayerGestures({
123
116
  newY = Math.max(0, startRef.current.y + (startRef.current.height - clampedHeight));
124
117
  }
125
118
 
126
- runOnJS(setState)({ x: newX, y: newY, width: clampedWidth, height: clampedHeight });
119
+ setState({ x: newX, y: newY, width: clampedWidth, height: clampedHeight });
127
120
  })
128
121
  .onEnd(() => {
129
- 'worklet';
130
122
  // Clamp position to canvas bounds
131
123
  const clampedX = Math.max(0, Math.min(state.x, canvasWidth - state.width));
132
124
  const clampedY = Math.max(0, Math.min(state.y, canvasHeight - state.height));
@@ -136,9 +128,9 @@ export function useDraggableLayerGestures({
136
128
  const newX = (clampedX / canvasWidth) * 100;
137
129
  const newY = (clampedY / canvasHeight) * 100;
138
130
 
139
- runOnJS(onSizeChange)(newWidth, newHeight);
140
- runOnJS(onPositionChange)(newX, newY);
141
- runOnJS(setState)({ x: clampedX, y: clampedY, width: state.width, height: state.height });
131
+ onSizeChange(newWidth, newHeight);
132
+ onPositionChange(newX, newY);
133
+ setState({ x: clampedX, y: clampedY, width: state.width, height: state.height });
142
134
  });
143
135
  };
144
136
 
@@ -1,32 +0,0 @@
1
- /**
2
- * Animation Layer Constants
3
- * Centralized constants for animation layer editor
4
- */
5
-
6
- import type { AnimationType } from "../../domain/entities/video-project.types";
7
-
8
- export type Easing = "linear" | "ease-in" | "ease-out" | "ease-in-out";
9
-
10
- export const ANIMATION_TYPES: {
11
- type: AnimationType;
12
- label: string;
13
- icon: string;
14
- }[] = [
15
- { type: "none", label: "None", icon: "Ban" },
16
- { type: "fade", label: "Fade", icon: "Eye" },
17
- { type: "slide", label: "Slide", icon: "MoveRight" },
18
- { type: "bounce", label: "Bounce", icon: "ArrowUp" },
19
- { type: "zoom", label: "Zoom", icon: "Maximize2" },
20
- { type: "rotate", label: "Rotate", icon: "RotateCw" },
21
- ];
22
-
23
- export const DURATIONS = [300, 500, 800, 1000, 1500, 2000];
24
-
25
- export const DELAYS = [0, 200, 500, 1000];
26
-
27
- export const EASINGS: { value: Easing; label: string }[] = [
28
- { value: "linear", label: "Linear" },
29
- { value: "ease-in", label: "Ease In" },
30
- { value: "ease-out", label: "Ease Out" },
31
- { value: "ease-in-out", label: "Ease In-Out" },
32
- ];
@@ -1,101 +0,0 @@
1
- /**
2
- * AnimationEditor Component
3
- * Main component for editing animation layers
4
- */
5
-
6
- import React from "react";
7
- import { View, ScrollView, StyleSheet } from "react-native";
8
-
9
- import type { Animation } from "../../domain/entities/video-project.types";
10
- import { useAnimationLayerForm } from "../hooks/useAnimationLayerForm";
11
- import {
12
- DURATIONS,
13
- DELAYS,
14
- EASINGS,
15
- type Easing,
16
- } from "../../infrastructure/constants/animation-layer.constants";
17
- import { ValueSelector } from "./shape-layer/ValueSelector";
18
- import { OptionSelector } from "./text-layer/OptionSelector";
19
- import { AnimationTypeSelector } from "./animation-layer/AnimationTypeSelector";
20
- import { AnimationEditorActions } from "./animation-layer/AnimationEditorActions";
21
- import { AnimationInfoBanner } from "./animation-layer/AnimationInfoBanner";
22
-
23
- interface AnimationEditorProps {
24
- animation?: Animation;
25
- onSave: (animation: Animation) => void;
26
- onRemove?: () => void;
27
- onCancel: () => void;
28
- }
29
-
30
- export const AnimationEditor: React.FC<AnimationEditorProps> = ({
31
- animation,
32
- onSave,
33
- onRemove,
34
- onCancel,
35
- }) => {
36
- const {
37
- formState,
38
- setAnimationType,
39
- setDuration,
40
- setDelay,
41
- setEasing,
42
- buildAnimationData,
43
- } = useAnimationLayerForm(animation);
44
-
45
- const handleSave = () => {
46
- onSave(buildAnimationData());
47
- };
48
-
49
- return (
50
- <View style={styles.container}>
51
- <ScrollView showsVerticalScrollIndicator={false}>
52
- <AnimationTypeSelector
53
- selectedType={formState.animationType}
54
- onTypeChange={setAnimationType}
55
- />
56
-
57
- {formState.animationType !== "none" && (
58
- <>
59
- <ValueSelector
60
- title="Duration"
61
- value={formState.duration}
62
- options={DURATIONS}
63
- formatValue={(val) => `${val}ms`}
64
- onValueChange={setDuration}
65
- />
66
-
67
- <ValueSelector
68
- title="Delay"
69
- value={formState.delay}
70
- options={DELAYS}
71
- formatValue={(val) => `${val}ms`}
72
- onValueChange={setDelay}
73
- />
74
-
75
- <OptionSelector
76
- title="Easing"
77
- options={EASINGS}
78
- selectedValue={formState.easing}
79
- onValueChange={(value) => setEasing(value as Easing)}
80
- />
81
-
82
- <AnimationInfoBanner />
83
- </>
84
- )}
85
- </ScrollView>
86
-
87
- <AnimationEditorActions
88
- hasAnimation={!!animation}
89
- onRemove={onRemove}
90
- onCancel={onCancel}
91
- onSave={handleSave}
92
- />
93
- </View>
94
- );
95
- };
96
-
97
- const styles = StyleSheet.create({
98
- container: {
99
- paddingVertical: 16,
100
- },
101
- });
@@ -1,101 +0,0 @@
1
- /**
2
- * AnimationEditorActions Component
3
- * Action buttons for animation editor
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet, TouchableOpacity } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
-
11
- interface AnimationEditorActionsProps {
12
- hasAnimation: boolean;
13
- onRemove?: () => void;
14
- onCancel: () => void;
15
- onSave: () => void;
16
- }
17
-
18
- export const AnimationEditorActions: React.FC<AnimationEditorActionsProps> = ({
19
- hasAnimation,
20
- onRemove,
21
- onCancel,
22
- onSave,
23
- }) => {
24
- const tokens = useAppDesignTokens();
25
-
26
- return (
27
- <View style={styles.actions}>
28
- {hasAnimation && onRemove && (
29
- <TouchableOpacity
30
- style={[
31
- styles.actionButton,
32
- styles.removeButton,
33
- { borderColor: tokens.colors.error },
34
- ]}
35
- onPress={onRemove}
36
- >
37
- <AtomicIcon name="trash-outline" size="sm" color="error" />
38
- </TouchableOpacity>
39
- )}
40
-
41
- <TouchableOpacity
42
- style={[
43
- styles.actionButton,
44
- styles.cancelButton,
45
- { borderColor: tokens.colors.borderLight },
46
- ]}
47
- onPress={onCancel}
48
- >
49
- <AtomicText
50
- type="bodyMedium"
51
- style={{ color: tokens.colors.textSecondary }}
52
- >
53
- Cancel
54
- </AtomicText>
55
- </TouchableOpacity>
56
-
57
- <TouchableOpacity
58
- style={[
59
- styles.actionButton,
60
- styles.saveButton,
61
- { backgroundColor: tokens.colors.primary },
62
- ]}
63
- onPress={onSave}
64
- >
65
- <AtomicIcon name="checkmark-outline" size="sm" color="onSurface" />
66
- <AtomicText
67
- type="bodyMedium"
68
- style={{ color: tokens.colors.onPrimary, fontWeight: "600", marginLeft: 6 }}
69
- >
70
- Apply
71
- </AtomicText>
72
- </TouchableOpacity>
73
- </View>
74
- );
75
- };
76
-
77
- const styles = StyleSheet.create({
78
- actions: {
79
- flexDirection: "row",
80
- gap: 8,
81
- },
82
- actionButton: {
83
- flex: 1,
84
- flexDirection: "row",
85
- paddingVertical: 12,
86
- borderRadius: 12,
87
- alignItems: "center",
88
- justifyContent: "center",
89
- },
90
- removeButton: {
91
- flex: 0,
92
- paddingHorizontal: 16,
93
- borderWidth: 1,
94
- },
95
- cancelButton: {
96
- borderWidth: 1,
97
- },
98
- saveButton: {
99
- // backgroundColor set dynamically
100
- },
101
- });
@@ -1,40 +0,0 @@
1
- /**
2
- * AnimationInfoBanner Component
3
- * Info banner for animation editor
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
-
11
- export const AnimationInfoBanner: React.FC = () => {
12
- const tokens = useAppDesignTokens();
13
-
14
- return (
15
- <View
16
- style={[
17
- styles.infoBanner,
18
- { backgroundColor: tokens.colors.primary + "20" },
19
- ]}
20
- >
21
- <AtomicIcon name="information-circle-outline" size="sm" color="primary" />
22
- <AtomicText
23
- type="labelSmall"
24
- style={{ color: tokens.colors.primary, marginLeft: 8, flex: 1 }}
25
- >
26
- Animation will play when the layer first appears in the scene
27
- </AtomicText>
28
- </View>
29
- );
30
- };
31
-
32
- const styles = StyleSheet.create({
33
- infoBanner: {
34
- flexDirection: "row",
35
- alignItems: "center",
36
- padding: 12,
37
- borderRadius: 8,
38
- marginBottom: 8,
39
- },
40
- });
@@ -1,102 +0,0 @@
1
- /**
2
- * AnimationTypeSelector Component
3
- * Animation type selector for animation layer
4
- */
5
-
6
- import React from "react";
7
- import { View, ScrollView, StyleSheet, TouchableOpacity } from "react-native";
8
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
- import { ANIMATION_TYPES } from "../../../infrastructure/constants/animation-layer.constants";
11
- import type { AnimationType } from "../../../domain/entities/video-project.types";
12
-
13
- interface AnimationTypeSelectorProps {
14
- selectedType: AnimationType;
15
- onTypeChange: (type: AnimationType) => void;
16
- }
17
-
18
- export const AnimationTypeSelector: React.FC<AnimationTypeSelectorProps> = ({
19
- selectedType,
20
- onTypeChange,
21
- }) => {
22
- const tokens = useAppDesignTokens();
23
-
24
- return (
25
- <View style={styles.section}>
26
- <AtomicText
27
- type="bodyMedium"
28
- style={{
29
- color: tokens.colors.textPrimary,
30
- fontWeight: "600",
31
- marginBottom: 12,
32
- }}
33
- >
34
- Animation Type
35
- </AtomicText>
36
-
37
- <ScrollView
38
- horizontal
39
- showsHorizontalScrollIndicator={false}
40
- style={styles.animationTypesScroll}
41
- >
42
- {ANIMATION_TYPES.map((anim) => (
43
- <TouchableOpacity
44
- key={anim.type}
45
- style={[
46
- styles.animationTypeCard,
47
- {
48
- backgroundColor:
49
- selectedType === anim.type
50
- ? tokens.colors.primary
51
- : tokens.colors.surface,
52
- borderColor:
53
- selectedType === anim.type
54
- ? tokens.colors.primary
55
- : tokens.colors.borderLight,
56
- },
57
- ]}
58
- onPress={() => onTypeChange(anim.type)}
59
- >
60
- <AtomicIcon
61
- name={anim.icon as "Ban" | "Eye" | "MoveRight" | "ArrowUp" | "Maximize2" | "RotateCw"}
62
- size="md"
63
- color={selectedType === anim.type ? "onSurface" : "primary"}
64
- />
65
- <AtomicText
66
- type="labelSmall"
67
- style={{
68
- color:
69
- selectedType === anim.type
70
- ? tokens.colors.onPrimary
71
- : tokens.colors.textPrimary,
72
- marginTop: 6,
73
- fontWeight: selectedType === anim.type ? "600" : "400",
74
- }}
75
- >
76
- {anim.label}
77
- </AtomicText>
78
- </TouchableOpacity>
79
- ))}
80
- </ScrollView>
81
- </View>
82
- );
83
- };
84
-
85
- const styles = StyleSheet.create({
86
- section: {
87
- marginBottom: 24,
88
- },
89
- animationTypesScroll: {
90
- marginHorizontal: -16,
91
- paddingHorizontal: 16,
92
- },
93
- animationTypeCard: {
94
- width: 90,
95
- padding: 12,
96
- borderRadius: 12,
97
- borderWidth: 2,
98
- alignItems: "center",
99
- justifyContent: "center",
100
- marginRight: 12,
101
- },
102
- });
@@ -1,72 +0,0 @@
1
- /**
2
- * useAnimationLayerForm Hook
3
- * Manages form state for animation layer editor
4
- */
5
-
6
- import { useState, useCallback } from "react";
7
- import type { Animation, AnimationType } from "../../domain/entities/video-project.types";
8
- import type { Easing } from "../../infrastructure/constants/animation-layer.constants";
9
-
10
- interface AnimationLayerFormState {
11
- animationType: AnimationType;
12
- duration: number;
13
- delay: number;
14
- easing: Easing;
15
- }
16
-
17
- interface UseAnimationLayerFormReturn {
18
- formState: AnimationLayerFormState;
19
- setAnimationType: (type: AnimationType) => void;
20
- setDuration: (duration: number) => void;
21
- setDelay: (delay: number) => void;
22
- setEasing: (easing: Easing) => void;
23
- buildAnimationData: () => Animation;
24
- }
25
-
26
- /**
27
- * Hook for managing animation layer form state
28
- */
29
- export function useAnimationLayerForm(
30
- initialAnimation?: Animation,
31
- ): UseAnimationLayerFormReturn {
32
- const [formState, setFormState] = useState<AnimationLayerFormState>({
33
- animationType: initialAnimation?.type || "fade",
34
- duration: initialAnimation?.duration || 500,
35
- delay: initialAnimation?.delay || 0,
36
- easing: initialAnimation?.easing || "ease-in-out",
37
- });
38
-
39
- const setAnimationType = useCallback((type: AnimationType) => {
40
- setFormState((prev) => ({ ...prev, animationType: type }));
41
- }, []);
42
-
43
- const setDuration = useCallback((duration: number) => {
44
- setFormState((prev) => ({ ...prev, duration }));
45
- }, []);
46
-
47
- const setDelay = useCallback((delay: number) => {
48
- setFormState((prev) => ({ ...prev, delay }));
49
- }, []);
50
-
51
- const setEasing = useCallback((easing: Easing) => {
52
- setFormState((prev) => ({ ...prev, easing }));
53
- }, []);
54
-
55
- const buildAnimationData = useCallback((): Animation => {
56
- return {
57
- type: formState.animationType,
58
- duration: formState.duration,
59
- delay: formState.delay,
60
- easing: formState.easing,
61
- };
62
- }, [formState]);
63
-
64
- return {
65
- formState,
66
- setAnimationType,
67
- setDuration,
68
- setDelay,
69
- setEasing,
70
- buildAnimationData,
71
- };
72
- }