@umituz/react-native-photo-editor 2.0.25 → 2.0.27

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-photo-editor",
3
- "version": "2.0.25",
3
+ "version": "2.0.27",
4
4
  "description": "A powerful, generic photo editor for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -21,7 +21,7 @@ export interface PhotoEditorProps {
21
21
  onSave?: (uri: string, layers: Layer[], filters: FilterSettings) => void;
22
22
  onClose: () => void;
23
23
  title?: string;
24
- customTools?: React.ReactNode | ((ui: any) => React.ReactNode);
24
+ customTools?: React.ReactNode | ((ui: unknown) => React.ReactNode);
25
25
  initialCaption?: string;
26
26
  t: (key: string) => string;
27
27
  fonts?: readonly string[];
@@ -59,7 +59,7 @@ export function useEditor() {
59
59
  ]);
60
60
  }
61
61
 
62
- export type { Layer } from "../entities/Layer.entity"";
62
+ export type { Layer } from "../../domain/entities/Layer.entity";
63
63
  export type { Transform } from "../../domain/entities/Transform";
64
64
  export type { FilterValues } from "../../domain/entities/Filters";
65
- export type { TextLayerData } from "../entities/Layer.entity"";
65
+ export type { TextContent as TextLayerData } from "../../domain/types";
@@ -7,7 +7,7 @@ import { useState, useRef, useCallback, useEffect } from "react";
7
7
  import type { BottomSheetModalRef } from "@umituz/react-native-design-system/molecules";
8
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
9
9
  import { useEditor } from "./useEditor";
10
- import type { TextAlign } from "../entities/Layer.entity"";
10
+ import type { TextAlign } from "../../domain/types";
11
11
  import type { Transform } from "../../domain/entities/Transform";
12
12
 
13
13
  export function useEditorUI(initialCaption?: string) {
@@ -4,8 +4,9 @@
4
4
  */
5
5
 
6
6
  import { useState, useCallback, useMemo } from "react";
7
- import { Layer, type TextLayerData } from "../entities/Layer.entity"";
8
- import { FiltersVO, FilterValues } from "../../domain/entities/Filters";
7
+ import { Layer } from "../../domain/entities/Layer.entity";
8
+ import type { TextContent as TextLayerData } from "../../domain/types";
9
+ import { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
9
10
  import { HistoryService, HistoryState } from "../../domain/services/HistoryService";
10
11
  import { LayerService } from "../../domain/services/LayerService";
11
12
  import type { Transform } from "../../domain/entities/Transform";
@@ -19,7 +20,7 @@ export function useEditorStore() {
19
20
  historyService.createInitialState([])
20
21
  );
21
22
  const [activeLayerId, setActiveLayerId] = useState<string | null>(null);
22
- const [filters, setFilters] = useState<FiltersVO>(FiltersVO.default());
23
+ const [filters, setFilters] = useState<FilterSettings>(FilterSettings.DEFAULT);
23
24
 
24
25
  // History actions
25
26
  const pushLayers = useCallback((layers: Layer[]) => {
@@ -35,11 +36,8 @@ export function useEditorStore() {
35
36
  }, []);
36
37
 
37
38
  // Layer actions
38
- const addTextLayer = useCallback((overrides?: Partial<Omit<TextLayerData, "id" | "type">>) => {
39
- const layer = layerService.createTextLayer({
40
- ...overrides,
41
- zIndex: history.present.length,
42
- });
39
+ const addTextLayer = useCallback((overrides?: Partial<TextLayerData>) => {
40
+ const layer = layerService.createTextLayer(overrides || {});
43
41
  pushLayers([...history.present, layer]);
44
42
  setActiveLayerId(layer.id);
45
43
  return layer.id;
@@ -99,13 +97,29 @@ export function useEditorStore() {
99
97
  }, []);
100
98
 
101
99
  // Filter actions
102
- const updateFilters = useCallback((updates: Partial<FilterValues>) => {
103
- const current = filters.toJSON();
104
- setFilters(FiltersVO.from({ ...current, ...updates }));
100
+ const updateFilters = useCallback((updates: Partial<{ brightness: number; contrast: number; saturation: number; sepia: number; grayscale: number; hueRotate?: number }>) => {
101
+ if (updates.brightness !== undefined) {
102
+ setFilters(filters.withBrightness(updates.brightness));
103
+ }
104
+ if (updates.contrast !== undefined) {
105
+ setFilters(filters.withContrast(updates.contrast));
106
+ }
107
+ if (updates.saturation !== undefined) {
108
+ setFilters(filters.withSaturation(updates.saturation));
109
+ }
110
+ if (updates.sepia !== undefined) {
111
+ setFilters(filters.withSepia(updates.sepia));
112
+ }
113
+ if (updates.grayscale !== undefined) {
114
+ setFilters(filters.withGrayscale(updates.grayscale));
115
+ }
116
+ if (updates.hueRotate !== undefined) {
117
+ setFilters(filters.withHueRotate(updates.hueRotate));
118
+ }
105
119
  }, [filters]);
106
120
 
107
121
  const resetFilters = useCallback(() => {
108
- setFilters(FiltersVO.default());
122
+ setFilters(FilterSettings.DEFAULT);
109
123
  }, []);
110
124
 
111
125
  // Getters
@@ -122,7 +136,7 @@ export function useEditorStore() {
122
136
  layers,
123
137
  activeLayerId,
124
138
  activeLayer,
125
- filters: filters.toJSON(),
139
+ filters,
126
140
  canUndo,
127
141
  canRedo,
128
142
 
@@ -6,6 +6,8 @@
6
6
  import type { LayerType, Position, Appearance, LayerContent } from "../types";
7
7
  import type { TextLayer } from "./TextLayer.entity";
8
8
  import type { StickerLayer } from "./StickerLayer.entity";
9
+ import { TextLayer as TextLayerClass } from "./TextLayer.entity";
10
+ import { StickerLayer as StickerLayerClass } from "./StickerLayer.entity";
9
11
 
10
12
  export class Layer {
11
13
  readonly id: string;
@@ -34,6 +36,12 @@ export class Layer {
34
36
  this.content = data.content;
35
37
  }
36
38
 
39
+ // Convenience getters for backward compatibility
40
+ get x(): number { return this.position.x; }
41
+ get y(): number { return this.position.y; }
42
+ get zIndex(): number { return this.appearance.zIndex; }
43
+ get opacity(): number { return this.appearance.opacity; }
44
+
37
45
  // Type guards
38
46
  isText(): this is TextLayer {
39
47
  return this.type === "text";
@@ -65,6 +73,10 @@ export class Layer {
65
73
  });
66
74
  }
67
75
 
76
+ withZIndex(zIndex: number): Layer {
77
+ return this.withAppearance({ zIndex });
78
+ }
79
+
68
80
  toJSON() {
69
81
  return {
70
82
  id: this.id,
@@ -78,9 +90,49 @@ export class Layer {
78
90
  ...this.content,
79
91
  };
80
92
  }
93
+
94
+ static from(data: ReturnType<Layer["toJSON"]>): Layer {
95
+ if (data.type === "text") {
96
+ return new TextLayerClass({
97
+ id: data.id,
98
+ position: { x: data.x, y: data.y },
99
+ rotation: data.rotation,
100
+ scale: data.scale,
101
+ appearance: { opacity: data.opacity, zIndex: data.zIndex },
102
+ content: {
103
+ text: (data as Record<string, unknown>).text as string || "",
104
+ fontSize: (data as Record<string, unknown>).fontSize as number || 32,
105
+ fontFamily: (data as Record<string, unknown>).fontFamily as string || "System",
106
+ color: (data as Record<string, unknown>).color as string || "#FFFFFF",
107
+ backgroundColor: (data as Record<string, unknown>).backgroundColor as string || "transparent",
108
+ textAlign: (data as Record<string, unknown>).textAlign as "left" | "center" | "right" || "center",
109
+ isBold: (data as Record<string, unknown>).isBold as boolean || undefined,
110
+ isItalic: (data as Record<string, unknown>).isItalic as boolean || undefined,
111
+ },
112
+ });
113
+ }
114
+ return new StickerLayerClass({
115
+ id: data.id,
116
+ position: { x: data.x, y: data.y },
117
+ rotation: data.rotation,
118
+ scale: data.scale,
119
+ appearance: { opacity: data.opacity, zIndex: data.zIndex },
120
+ content: { uri: (data as Record<string, unknown>).uri as string || "" },
121
+ });
122
+ }
81
123
  }
82
124
 
83
- // Re-export for convenience
125
+ // Type exports for backward compatibility
84
126
  export type { TextLayer } from "./TextLayer.entity";
85
127
  export type { StickerLayer } from "./StickerLayer.entity";
128
+ export type { TextAlign } from "../types";
129
+
130
+ // Type guard functions for backward compatibility
131
+ export function isTextLayer(layer: Layer): layer is TextLayer {
132
+ return layer.isText();
133
+ }
134
+
135
+ export function isStickerLayer(layer: Layer): layer is StickerLayer {
136
+ return layer.isSticker();
137
+ }
86
138
 
@@ -6,7 +6,7 @@
6
6
  import { TextLayer } from "../entities/TextLayer.entity";
7
7
  import { StickerLayer } from "../entities/StickerLayer.entity";
8
8
  import { LayerDefaults } from "../value-objects/LayerDefaults.vo";
9
- import type { Position, Appearance, TextContent, StickerContent } from "../types";
9
+ import type { Position, Appearance, TextContent } from "../types";
10
10
 
11
11
  export class LayerFactory {
12
12
  createTextLayer(overrides?: Partial<Position & Appearance & TextContent>): TextLayer {
@@ -3,7 +3,7 @@
3
3
  * CRUD operations for layers with history support
4
4
  */
5
5
 
6
- import type { Layer } from "../entities/Layer.entity".entity";
6
+ import type { Layer } from "../entities/Layer.entity";
7
7
  import { LayerFactory } from "./LayerFactory.service";
8
8
 
9
9
  export class LayerRepository {
@@ -48,6 +48,11 @@ export class LayerRepository {
48
48
  const layer = layers.find((l) => l.id === layerId);
49
49
  if (!layer) return layers;
50
50
 
51
+ // Type narrowing - only TextLayer and StickerLayer can be duplicated
52
+ if (!layer.isText() && !layer.isSticker()) {
53
+ return layers;
54
+ }
55
+
51
56
  const duplicate = this.factory.duplicateLayer(layer, layers);
52
57
  return [...layers, duplicate];
53
58
  }
@@ -3,7 +3,10 @@
3
3
  * Business logic for layer operations
4
4
  */
5
5
 
6
- import { Layer, TextLayer, StickerLayer, type TextLayerData, type StickerLayerData } from "../entities/Layer.entity"";
6
+ import { Layer } from "../entities/Layer.entity";
7
+ import { TextLayer } from "../entities/TextLayer.entity";
8
+ import { StickerLayer } from "../entities/StickerLayer.entity";
9
+ import type { TextContent } from "../types";
7
10
  import type { Transform } from "../entities/Transform";
8
11
 
9
12
  export class LayerService {
@@ -11,18 +14,10 @@ export class LayerService {
11
14
  return `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
12
15
  }
13
16
 
14
- createTextLayer(overrides: Partial<Omit<TextLayerData, "id" | "type">> = {}): TextLayer {
17
+ createTextLayer(overrides: Partial<TextContent> = {}): TextLayer {
15
18
  const id = this.generateId("text");
16
- const defaults: TextLayerData = {
17
- id,
18
- type: "text",
19
+ const contentDefaults: TextContent = {
19
20
  text: "",
20
- x: 50,
21
- y: 50,
22
- rotation: 0,
23
- scale: 1,
24
- opacity: 1,
25
- zIndex: 0,
26
21
  fontSize: 32,
27
22
  fontFamily: "System",
28
23
  color: "#FFFFFF",
@@ -32,24 +27,27 @@ export class LayerService {
32
27
  isItalic: false,
33
28
  };
34
29
 
35
- return new TextLayer({ ...defaults, ...overrides });
30
+ return new TextLayer({
31
+ id,
32
+ position: { x: 50, y: 50 },
33
+ rotation: 0,
34
+ scale: 1,
35
+ appearance: { opacity: 1, zIndex: 0 },
36
+ content: { ...contentDefaults, ...overrides },
37
+ });
36
38
  }
37
39
 
38
- createStickerLayer(uri: string, overrides: Partial<Omit<StickerLayerData, "id" | "type" | "uri">> = {}): StickerLayer {
40
+ createStickerLayer(uri: string, _overrides: Record<string, never> = {}): StickerLayer {
39
41
  const id = this.generateId("sticker");
40
- const defaults: StickerLayerData = {
42
+
43
+ return new StickerLayer({
41
44
  id,
42
- type: "sticker",
43
- uri,
44
- x: 100,
45
- y: 100,
45
+ position: { x: 100, y: 100 },
46
46
  rotation: 0,
47
47
  scale: 1,
48
- opacity: 1,
49
- zIndex: 0,
50
- };
51
-
52
- return new StickerLayer({ ...defaults, ...overrides });
48
+ appearance: { opacity: 1, zIndex: 0 },
49
+ content: { uri },
50
+ });
53
51
  }
54
52
 
55
53
  updateLayer(layers: Layer[], layerId: string, updates: Partial<Transform>): Layer[] {
@@ -53,4 +53,4 @@ export const LayerDefaults = {
53
53
  },
54
54
  } as const;
55
55
 
56
- export type LayerDefaults = typeof LayerDefaults;
56
+ export type LayerDefaultsType = typeof LayerDefaults;
package/src/index.ts CHANGED
@@ -10,10 +10,12 @@ export { PhotoEditor } from "./PhotoEditor";
10
10
  export type { PhotoEditorProps } from "./PhotoEditor";
11
11
 
12
12
  // Domain entities
13
- export type { Layer, TextLayer, StickerLayer } from "../entities/Layer.entity"";
13
+ export type { Layer, TextLayer, StickerLayer } from "./domain/entities/Layer.entity";
14
+ export type { TextContent as TextLayerData, StickerContent as StickerLayerData } from "./domain/types";
15
+ export type { TextAlign } from "./domain/types";
14
16
  export type { Transform } from "./domain/entities/Transform";
15
17
  export type { FilterValues, FiltersVO } from "./domain/entities/Filters";
16
- export { isTextLayer, isStickerLayer } from "../entities/Layer.entity"";
18
+ export { isTextLayer, isStickerLayer } from "./domain/entities/Layer.entity";
17
19
 
18
20
  // Application hooks
19
21
  export { useEditor } from "./application/hooks/useEditor";
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { useState, useCallback, useEffect, useRef } from "react";
7
7
  import { Gesture } from "react-native-gesture-handler";
8
- import type { Layer } from "../entities/Layer.entity".entity";
8
+ import type { Layer } from "../../domain/entities/Layer.entity";
9
9
 
10
10
  export interface TransformGestureState {
11
11
  position: { x: number; y: number };
@@ -11,7 +11,7 @@ import { Image } from "expo-image";
11
11
  import { AtomicText } from "@umituz/react-native-design-system/atoms";
12
12
  import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system/theme";
13
13
  import { useTransformGesture } from "../../infrastructure/gesture/useTransformGesture";
14
- import type { Layer } from "../entities/Layer.entity".entity";
14
+ import type { Layer } from "../../domain/entities/Layer.entity";
15
15
 
16
16
  interface DraggableLayerProps {
17
17
  layer: Layer;
@@ -7,7 +7,7 @@ import React, { memo } from "react";
7
7
  import { View, StyleSheet, type ViewStyle } from "react-native";
8
8
  import { Image } from "expo-image";
9
9
  import { DraggableLayer } from "./DraggableLayer";
10
- import type { Layer } from "../entities/Layer.entity".entity";
10
+ import type { Layer } from "../../domain/entities/Layer.entity";
11
11
  import type { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
12
12
 
13
13
  interface EditorCanvasProps {
@@ -8,7 +8,7 @@ import { ScrollView } from "react-native";
8
8
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
9
9
  import { EditorCanvas } from "./EditorCanvas";
10
10
  import { FontControls } from "./FontControls";
11
- import type { Layer } from "../entities/Layer.entity".entity";
11
+ import type { Layer } from "../../domain/entities/Layer.entity";
12
12
  import type { FilterSettings } from "../../domain/value-objects/FilterSettings.vo";
13
13
  import type { EditorUIState } from "../../application/hooks/useEditorUI";
14
14
 
@@ -7,7 +7,7 @@ import React, { memo } from "react";
7
7
  import { View, ScrollView, TouchableOpacity } from "react-native";
8
8
  import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
- import { Layer } from "../entities/Layer.entity"";
10
+ import { Layer } from "../../../domain/entities/Layer.entity";
11
11
 
12
12
  interface LayerManagerProps {
13
13
  layers: Layer[];
@@ -8,7 +8,7 @@ import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
8
8
  import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
9
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
10
10
  import { ColorPicker } from "../ui/ColorPicker";
11
- import type { TextAlign } from "../entities/Layer.entity"";
11
+ import type { TextAlign } from "../../../domain/types";
12
12
 
13
13
  interface TextEditorSheetProps {
14
14
  value: string;
package/src/types.ts CHANGED
@@ -3,13 +3,14 @@
3
3
  * Re-exports domain types for convenience
4
4
  */
5
5
 
6
- export type { TextAlign } from "../entities/Layer.entity"";
7
- export type { Layer, TextLayer, StickerLayer, TextLayerData, StickerLayerData } from "../entities/Layer.entity"";
6
+ export type { TextAlign } from "./domain/types";
7
+ export type { Layer, TextLayer, StickerLayer } from "./domain/entities/Layer.entity";
8
+ export type { TextContent as TextLayerData, StickerContent as StickerLayerData } from "./domain/types";
8
9
  export type { Transform } from "./domain/entities/Transform";
9
10
  export type { FilterValues as ImageFilters } from "./domain/entities/Filters";
10
11
 
11
12
  // Re-export type guards
12
- export { isTextLayer, isStickerLayer } from "../entities/Layer.entity"";
13
+ export { isTextLayer, isStickerLayer } from "./domain/entities/Layer.entity";
13
14
 
14
15
  // Re-export DEFAULT_FILTERS as value (not type)
15
16
  export { DEFAULT_FILTERS as DEFAULT_IMAGE_FILTERS } from "./domain/entities/Filters";