@symbiote-native/image-manipulator 0.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +2 -0
  5. package/build/angular/inject-image-manipulator.d.ts +3 -0
  6. package/build/angular/inject-image-manipulator.js +8 -0
  7. package/build/core/image-manipulator.d.ts +10 -0
  8. package/build/core/image-manipulator.js +34 -0
  9. package/build/core/index.d.ts +4 -0
  10. package/build/core/index.js +2 -0
  11. package/build/core/manipulator-context-controller.d.ts +9 -0
  12. package/build/core/manipulator-context-controller.js +13 -0
  13. package/build/core/manipulator-resource-controller.d.ts +6 -0
  14. package/build/core/manipulator-resource-controller.js +17 -0
  15. package/build/core/native-module.d.ts +27 -0
  16. package/build/core/native-module.js +3 -0
  17. package/build/core/types.d.ts +47 -0
  18. package/build/core/types.js +11 -0
  19. package/build/core/validators.d.ts +5 -0
  20. package/build/core/validators.js +92 -0
  21. package/build/react/index.d.ts +2 -0
  22. package/build/react/index.js +2 -0
  23. package/build/react/use-image-manipulator.d.ts +3 -0
  24. package/build/react/use-image-manipulator.js +7 -0
  25. package/build/solid/index.d.ts +2 -0
  26. package/build/solid/index.js +2 -0
  27. package/build/solid/use-image-manipulator.d.ts +4 -0
  28. package/build/solid/use-image-manipulator.js +7 -0
  29. package/build/svelte/index.d.ts +2 -0
  30. package/build/svelte/index.js +2 -0
  31. package/build/svelte/use-image-manipulator.svelte.d.ts +5 -0
  32. package/build/svelte/use-image-manipulator.svelte.js +7 -0
  33. package/build/vue/index.d.ts +2 -0
  34. package/build/vue/index.js +2 -0
  35. package/build/vue/use-image-manipulator.d.ts +4 -0
  36. package/build/vue/use-image-manipulator.js +8 -0
  37. package/build-ngc/angular/index.d.ts +2 -0
  38. package/build-ngc/angular/index.js +3 -0
  39. package/build-ngc/angular/index.js.map +1 -0
  40. package/build-ngc/angular/inject-image-manipulator.d.ts +3 -0
  41. package/build-ngc/angular/inject-image-manipulator.js +9 -0
  42. package/build-ngc/angular/inject-image-manipulator.js.map +1 -0
  43. package/build-ngc/core/image-manipulator.d.ts +10 -0
  44. package/build-ngc/core/image-manipulator.js +35 -0
  45. package/build-ngc/core/image-manipulator.js.map +1 -0
  46. package/build-ngc/core/index.d.ts +4 -0
  47. package/build-ngc/core/index.js +3 -0
  48. package/build-ngc/core/index.js.map +1 -0
  49. package/build-ngc/core/manipulator-context-controller.d.ts +9 -0
  50. package/build-ngc/core/manipulator-context-controller.js +14 -0
  51. package/build-ngc/core/manipulator-context-controller.js.map +1 -0
  52. package/build-ngc/core/manipulator-resource-controller.d.ts +6 -0
  53. package/build-ngc/core/manipulator-resource-controller.js +18 -0
  54. package/build-ngc/core/manipulator-resource-controller.js.map +1 -0
  55. package/build-ngc/core/native-module.d.ts +27 -0
  56. package/build-ngc/core/native-module.js +4 -0
  57. package/build-ngc/core/native-module.js.map +1 -0
  58. package/build-ngc/core/types.d.ts +47 -0
  59. package/build-ngc/core/types.js +12 -0
  60. package/build-ngc/core/types.js.map +1 -0
  61. package/build-ngc/core/validators.d.ts +5 -0
  62. package/build-ngc/core/validators.js +93 -0
  63. package/build-ngc/core/validators.js.map +1 -0
  64. package/native-link.json +12 -0
  65. package/package.json +151 -0
  66. package/src/angular/index.ts +2 -0
  67. package/src/angular/inject-image-manipulator.ts +17 -0
  68. package/src/core/image-manipulator.ts +51 -0
  69. package/src/core/index.ts +12 -0
  70. package/src/core/manipulator-context-controller.ts +34 -0
  71. package/src/core/manipulator-resource-controller.ts +40 -0
  72. package/src/core/native-module.ts +40 -0
  73. package/src/core/types.ts +47 -0
  74. package/src/core/validators.ts +128 -0
  75. package/src/react/index.ts +2 -0
  76. package/src/react/use-image-manipulator.ts +14 -0
  77. package/src/solid/index.ts +2 -0
  78. package/src/solid/use-image-manipulator.ts +15 -0
  79. package/src/svelte/index.ts +2 -0
  80. package/src/svelte/svelte-ambient.d.ts +3 -0
  81. package/src/svelte/svelte-compile.test-helper.ts +7 -0
  82. package/src/svelte/use-image-manipulator.svelte.ts +19 -0
  83. package/src/vue/index.ts +2 -0
  84. package/src/vue/use-image-manipulator.ts +19 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # @symbiote-native/image-manipulator
2
+
3
+ Shrink a photo before uploading it, rotate a scan, crop an avatar: build a chain of edits, render
4
+ once, save to a new file. One API for every [SymbioteNative](../../README.md) adapter (React, Vue,
5
+ Svelte, Solid and Angular).
6
+
7
+ It wraps [`expo-image-manipulator`](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator)
8
+ the same way [`@symbiote-native/print`](../print) wraps its upstream: `expo-modules-core` is a
9
+ direct dependency, never the `expo` meta-package, and the upstream JS is hand-ported into `core/`.
10
+ The mechanics live in the `symbiote-expo-native-module` project skill.
11
+
12
+ ## Install
13
+
14
+ **New app:**
15
+
16
+ ```bash
17
+ npx @symbiote-native/cli new my-app --image-manipulator
18
+ ```
19
+
20
+ **Existing SymbioteNative app:**
21
+
22
+ ```bash
23
+ npx @symbiote-native/cli add --image-manipulator
24
+ ```
25
+
26
+ Either way: installs `@symbiote-native/image-manipulator` and wires the native autolinking
27
+ automatically - see [`@symbiote-native/cli`](../cli).
28
+
29
+ <details>
30
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
31
+
32
+ ```bash
33
+ npm install @symbiote-native/image-manipulator
34
+ ```
35
+
36
+ `expo-image-manipulator` and `expo-modules-core` come along as regular, pinned dependencies -
37
+ never install either yourself, and never add the `expo` meta-package to this project.
38
+
39
+ ### Required one-time step: native autolinking wiring
40
+
41
+ Same one-time app wiring every `expo-modules-core` package shares - see
42
+ [`@symbiote-native/print`'s README](../print/README.md#required-one-time-step-native-autolinking-wiring)
43
+ for the full table; nothing package-specific here.
44
+
45
+ ### No permissions, no config plugin
46
+
47
+ - **No runtime permission on either platform.** The module only ever touches a URI/file the
48
+ caller already has access to - no camera, no photo library.
49
+ - **No manifest edit.** `expo-image-manipulator`'s own `AndroidManifest.xml` declares nothing.
50
+ - **No config plugin.** Upstream ships none.
51
+
52
+ </details>
53
+
54
+ ## Shape
55
+
56
+ ```
57
+ src/core/ manipulate + manipulateAsync + FlipType + SaveFormat + the shared
58
+ releasing-context controller. native-module.ts resolves
59
+ ExpoImageManipulator through expo-modules-core's requireNativeModule.
60
+ src/react/ useImageManipulator (hook)
61
+ src/vue/ useImageManipulator (composable)
62
+ src/solid/ useImageManipulator (primitive)
63
+ src/svelte/ useImageManipulator (rune)
64
+ src/angular/ injectImageManipulator
65
+ ```
66
+
67
+ `manipulate`/`manipulateAsync`/`FlipType`/`SaveFormat` stay framework-agnostic free functions,
68
+ re-exported by every adapter's own barrel. Upstream's `useImageManipulator` is ported to
69
+ **every** adapter, not just React: the recreate/release rule lives once in
70
+ `src/core/manipulator-context-controller.ts`, each adapter supplies only its own reactive
71
+ lifecycle, per this project's `components_split_logic_view_lifecycle` convention.
72
+
73
+ ## Use it
74
+
75
+ The current, chainable API (mirrors upstream's `ImageManipulator.manipulate`/`ImageManipulatorContext`):
76
+
77
+ ```ts
78
+ import { manipulate } from '@symbiote-native/image-manipulator';
79
+
80
+ const context = manipulate('file:///photo.jpg');
81
+ context.resize({ width: 300 }).rotate(90);
82
+ const image = await context.renderAsync();
83
+ const { uri, width, height } = await image.saveAsync({ format: 'jpeg', compress: 0.8 });
84
+
85
+ // Free the native memory once done - no lifecycle wrapper here (framework-agnostic core), each
86
+ // adapter's own hook/composable/service is responsible for calling `.release()` on unmount.
87
+ context.release();
88
+ image.release();
89
+ ```
90
+
91
+ The deprecated, one-shot API (kept for parity - upstream still ships it too):
92
+
93
+ ```ts
94
+ import { manipulateAsync, SaveFormat } from '@symbiote-native/image-manipulator';
95
+
96
+ const { uri } = await manipulateAsync(
97
+ 'file:///photo.jpg',
98
+ [{ resize: { width: 300 } }, { rotate: 90 }],
99
+ { format: SaveFormat.JPEG, compress: 0.8 },
100
+ );
101
+ ```
102
+
103
+ ## API
104
+
105
+ | Export | Signature | Notes |
106
+ | ------------------ | ------------------------------------------------------------------------ | ----------------------------------------------- |
107
+ | `manipulate` | `(source: string \| IImageRef) => IImageManipulatorContext` | Chainable `resize`/`rotate`/`flip`/`crop`/`reset`/`renderAsync`. |
108
+ | `manipulateAsync` | `(uri, actions?, saveOptions?) => Promise<IImageResult>` | @deprecated - replaced by `manipulate`. |
109
+ | `FlipType` | `{ Vertical, Horizontal }` | Enum for the `flip` action. |
110
+ | `SaveFormat` | `{ JPEG, PNG, WEBP }` | Enum for `ISaveOptions.format`. |
111
+
112
+ ## Declarative `useImageManipulator`
113
+
114
+ ```
115
+ useImageManipulator(source) -> IImageManipulatorContext
116
+ ```
117
+
118
+ React takes a plain value, re-invoked per render (matches upstream exactly). Vue/Solid/Svelte
119
+ take a reactive source (`Ref`/getter/`MaybeRefOrGetter`/accessor) so switching it recreates the
120
+ context and releases the stale one. Angular's `injectImageManipulator(source)` takes a `Signal`
121
+ and returns one, `injectX` shape matching `@symbiote-native/navigation`'s
122
+ `injectLinkingIntegration`.
123
+
124
+ ## Notes
125
+
126
+ - **Release what you create outside a component.** `manipulate()` and `renderAsync()` hold native
127
+ memory until `release()`. The adapter bindings release on unmount; the free function does not.
128
+ - **`extent` action is not ported.** It's web-only in upstream - neither the iOS nor the Android
129
+ native module registers an `extent` function (verified by reading both `ImageManipulatorModule`
130
+ sources). Calling `.extent()` on a native context is a type error here, matching runtime reality.
131
+ - **`ImageManipulator.Image` (the hidden native class property) is not exposed.** Upstream itself
132
+ marks it `@hidden` - only reachable indirectly via `context.renderAsync()`'s resolved `IImageRef`.
133
+
134
+ ## Common questions
135
+
136
+ - **Smaller upload.** Resize first (`resize({ width: 1080 })`), then
137
+ `saveAsync({ format: SaveFormat.JPEG, compress: 0.7 })`; `compress` is 0 to 1 and applies to JPEG
138
+ and WebP, PNG is lossless.
139
+ - **Keep the aspect ratio.** Give only one of `width` and `height`.
140
+ - **Memory growth.** `manipulate()` and `renderAsync()` hold native memory until `release()`.
141
+ - **`manipulateAsync`.** Works but deprecated upstream; use `manipulate()`.
142
+
143
+ Sources: [Expo docs: ImageManipulator](https://docs.expo.dev/versions/latest/sdk/imagemanipulator/),
144
+ [expo/expo#2512](https://github.com/expo/expo/issues/2512).
145
+
146
+ ## Test it
147
+
148
+ ```bash
149
+ pnpm vitest run packages/image-manipulator
150
+ ```
151
+
152
+ Upstream ships one test suite, `src/__tests__/validators-test.ts` - ported to
153
+ `validators.test.ts` (minus the `extent` cases, see above). `image-manipulator.test.ts` is net-new
154
+ coverage of `manipulate`/`manipulateAsync` against a faked native module.
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { injectImageManipulator } from './inject-image-manipulator';
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export { injectImageManipulator } from './inject-image-manipulator.js';
@@ -0,0 +1,3 @@
1
+ import type { Signal } from '@angular/core';
2
+ import type { IImageManipulatorContext, IImageRef } from '../core';
3
+ export declare function injectImageManipulator(source: () => string | IImageRef): Signal<IImageManipulatorContext>;
@@ -0,0 +1,8 @@
1
+ // Angular twin of `useImageManipulator`, `injectX` shape matching
2
+ // `@symbiote-native/navigation`'s `injectLinkingIntegration`
3
+ import { createResourceHook } from '@symbiote-native/angular';
4
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
5
+ const injectManipulatorResource = createResourceHook(createImageManipulatorResourceController);
6
+ export function injectImageManipulator(source) {
7
+ return injectManipulatorResource(() => [source()]);
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { NativeImageManipulatorContext, NativeImageRef } from './native-module';
2
+ import type { IAction, IImageResult, ISaveOptions } from './types';
3
+ export type { NativeImageManipulatorContext as IImageManipulatorContext };
4
+ export type { NativeImageRef as IImageRef };
5
+ /** Loads `source` and starts a new chainable manipulation context. */
6
+ export declare function manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
7
+ /**
8
+ * @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
9
+ */
10
+ export declare function manipulateAsync(uri: string, actions?: IAction[], saveOptions?: ISaveOptions): Promise<IImageResult>;
@@ -0,0 +1,34 @@
1
+ import { expoImageManipulator } from './native-module.js';
2
+ import { SaveFormat } from './types.js';
3
+ import { validateArguments } from './validators.js';
4
+ /** Loads `source` and starts a new chainable manipulation context. */
5
+ export function manipulate(source) {
6
+ return expoImageManipulator.manipulate(source);
7
+ }
8
+ /**
9
+ * @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
10
+ */
11
+ export async function manipulateAsync(uri, actions = [], saveOptions = {}) {
12
+ validateArguments(uri, actions, saveOptions);
13
+ const { format = SaveFormat.JPEG, ...rest } = saveOptions;
14
+ const context = expoImageManipulator.manipulate(uri);
15
+ for (const action of actions) {
16
+ if ('resize' in action) {
17
+ context.resize(action.resize);
18
+ }
19
+ else if ('rotate' in action) {
20
+ context.rotate(action.rotate);
21
+ }
22
+ else if ('flip' in action) {
23
+ context.flip(action.flip);
24
+ }
25
+ else if ('crop' in action) {
26
+ context.crop(action.crop);
27
+ }
28
+ }
29
+ const image = await context.renderAsync();
30
+ const result = await image.saveAsync({ format, ...rest });
31
+ context.release();
32
+ image.release();
33
+ return result;
34
+ }
@@ -0,0 +1,4 @@
1
+ export { manipulate, manipulateAsync } from './image-manipulator';
2
+ export type { IImageManipulatorContext, IImageRef } from './image-manipulator';
3
+ export { FlipType, SaveFormat } from './types';
4
+ export type { IAction, IActionCrop, IActionFlip, IActionResize, IActionRotate, IImageResult, ISaveOptions, } from './types';
@@ -0,0 +1,2 @@
1
+ export { manipulate, manipulateAsync } from './image-manipulator.js';
2
+ export { FlipType, SaveFormat } from './types.js';
@@ -0,0 +1,9 @@
1
+ export type IReleasableContext = {
2
+ release: () => void;
3
+ };
4
+ export type IManipulatorContextController<TSource, TContext extends IReleasableContext> = {
5
+ resolve: (source: TSource) => TContext;
6
+ flushRelease: () => void;
7
+ release: () => void;
8
+ };
9
+ export declare function createManipulatorContextController<TSource, TContext extends IReleasableContext>(manipulate: (source: TSource) => TContext): IManipulatorContextController<TSource, TContext>;
@@ -0,0 +1,13 @@
1
+ // Thin binding of `@symbiote-native/engine`'s generic `createResourceController` to this
2
+ // package's own `.release()`-shaped context, kept for its own descriptive type names
3
+ import { createResourceController } from '@symbiote-native/engine';
4
+ export function createManipulatorContextController(manipulate) {
5
+ const controller = createResourceController(manipulate, context => {
6
+ context.release();
7
+ });
8
+ return {
9
+ resolve: controller.resolve,
10
+ flushRelease: controller.flushDispose,
11
+ release: controller.dispose,
12
+ };
13
+ }
@@ -0,0 +1,6 @@
1
+ import type { IImageManipulatorContext, IImageRef } from './index';
2
+ export declare function createImageManipulatorResourceController(): {
3
+ resolve: (source: string | IImageRef) => IImageManipulatorContext;
4
+ flushDispose: () => void;
5
+ dispose: () => void;
6
+ };
@@ -0,0 +1,17 @@
1
+ // Adapts `createManipulatorContextController`'s `.release()`-shaped controller to the
2
+ // `resolve`/`flushDispose`/`dispose` shape every adapter's `createResourceHook` expects
3
+ import { createManipulatorContextController, } from './manipulator-context-controller.js';
4
+ import { manipulate } from './index.js';
5
+ function createManipulatorResourceController(manipulateSource) {
6
+ const controller = createManipulatorContextController(manipulateSource);
7
+ return {
8
+ resolve: controller.resolve,
9
+ flushDispose: controller.flushRelease,
10
+ dispose: controller.release,
11
+ };
12
+ }
13
+ // Bakes in this package's own `manipulate`, so every adapter's `createResourceHook` binds a
14
+ // plain zero-arg factory instead of repeating the same arrow wrapper five times
15
+ export function createImageManipulatorResourceController() {
16
+ return createManipulatorResourceController(manipulate);
17
+ }
@@ -0,0 +1,27 @@
1
+ import { SharedObject, SharedRef } from 'expo-modules-core';
2
+ import type { IImageResult, ISaveOptions } from './types';
3
+ export declare class NativeImageRef extends SharedRef<'image'> {
4
+ width: number;
5
+ height: number;
6
+ saveAsync(options?: ISaveOptions): Promise<IImageResult>;
7
+ }
8
+ export declare class NativeImageManipulatorContext extends SharedObject {
9
+ resize(size: {
10
+ width?: number | null;
11
+ height?: number | null;
12
+ }): NativeImageManipulatorContext;
13
+ rotate(degrees: number): NativeImageManipulatorContext;
14
+ flip(flipType: 'vertical' | 'horizontal'): NativeImageManipulatorContext;
15
+ crop(rect: {
16
+ originX: number;
17
+ originY: number;
18
+ width: number;
19
+ height: number;
20
+ }): NativeImageManipulatorContext;
21
+ reset(): NativeImageManipulatorContext;
22
+ renderAsync(): Promise<NativeImageRef>;
23
+ }
24
+ export type INativeImageManipulatorModule = {
25
+ manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
26
+ };
27
+ export declare const expoImageManipulator: INativeImageManipulatorModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule, SharedObject, SharedRef, } from 'expo-modules-core';
2
+ const EXPO_IMAGE_MANIPULATOR_MODULE_NAME = 'ExpoImageManipulator';
3
+ export const expoImageManipulator = requireNativeModule(EXPO_IMAGE_MANIPULATOR_MODULE_NAME);
@@ -0,0 +1,47 @@
1
+ export declare enum FlipType {
2
+ Vertical = "vertical",
3
+ Horizontal = "horizontal"
4
+ }
5
+ export declare enum SaveFormat {
6
+ JPEG = "jpeg",
7
+ PNG = "png",
8
+ WEBP = "webp"
9
+ }
10
+ export type IActionResize = {
11
+ /** Provide only one of `width`/`height` to preserve the image's ratio. */
12
+ resize: {
13
+ width?: number;
14
+ height?: number;
15
+ };
16
+ };
17
+ export type IActionRotate = {
18
+ /** Degrees, clockwise when positive, counter-clockwise when negative. */
19
+ rotate: number;
20
+ };
21
+ export type IActionFlip = {
22
+ flip: FlipType;
23
+ };
24
+ export type IActionCrop = {
25
+ crop: {
26
+ originX: number;
27
+ originY: number;
28
+ width: number;
29
+ height: number;
30
+ };
31
+ };
32
+ /** @deprecated use `manipulate(source)` instead. */
33
+ export type IAction = IActionResize | IActionRotate | IActionFlip | IActionCrop;
34
+ export type ISaveOptions = {
35
+ base64?: boolean;
36
+ /** `0.0`-`1.0`; `1` is no compression (highest quality). Default `1`. */
37
+ compress?: number;
38
+ /** Default `SaveFormat.JPEG`. */
39
+ format?: SaveFormat;
40
+ };
41
+ export type IImageResult = {
42
+ uri: string;
43
+ width: number;
44
+ height: number;
45
+ /** Set only when `base64` was truthy in `ISaveOptions`. */
46
+ base64?: string;
47
+ };
@@ -0,0 +1,11 @@
1
+ export var FlipType;
2
+ (function (FlipType) {
3
+ FlipType["Vertical"] = "vertical";
4
+ FlipType["Horizontal"] = "horizontal";
5
+ })(FlipType || (FlipType = {}));
6
+ export var SaveFormat;
7
+ (function (SaveFormat) {
8
+ SaveFormat["JPEG"] = "jpeg";
9
+ SaveFormat["PNG"] = "png";
10
+ SaveFormat["WEBP"] = "webp";
11
+ })(SaveFormat || (SaveFormat = {}));
@@ -0,0 +1,5 @@
1
+ import type { IAction, ISaveOptions } from './types';
2
+ export declare function validateArguments(uri: string, actions: IAction[], saveOptions: ISaveOptions): void;
3
+ export declare function validateUri(uri: string): void;
4
+ export declare function validateActions(actions: IAction[]): void;
5
+ export declare function validateSaveOptions({ base64, compress, format, }: ISaveOptions): void;
@@ -0,0 +1,92 @@
1
+ import { FlipType, SaveFormat } from './types.js';
2
+ const SUPPORTED_ACTION_TYPES = ['crop', 'flip', 'rotate', 'resize'];
3
+ export function validateArguments(uri, actions, saveOptions) {
4
+ validateUri(uri);
5
+ validateActions(actions);
6
+ validateSaveOptions(saveOptions);
7
+ }
8
+ export function validateUri(uri) {
9
+ if (typeof uri !== 'string') {
10
+ throw new TypeError('The "uri" argument must be a string');
11
+ }
12
+ }
13
+ export function validateActions(actions) {
14
+ if (!Array.isArray(actions)) {
15
+ throw new TypeError('The "actions" argument must be an array');
16
+ }
17
+ for (const action of actions) {
18
+ if (typeof action !== 'object' || action === null) {
19
+ throw new TypeError('Action must be an object');
20
+ }
21
+ const actionKeys = Object.keys(action);
22
+ if (actionKeys.length !== 1) {
23
+ throw new TypeError(`Single action must contain exactly one transformation: ${SUPPORTED_ACTION_TYPES.join(', ')}`);
24
+ }
25
+ const actionType = actionKeys[0];
26
+ if (!SUPPORTED_ACTION_TYPES.includes(actionType)) {
27
+ throw new TypeError(`Unsupported action type: ${actionType}`);
28
+ }
29
+ if ('crop' in action) {
30
+ validateCropAction(action);
31
+ }
32
+ else if ('flip' in action) {
33
+ validateFlipAction(action);
34
+ }
35
+ else if ('rotate' in action) {
36
+ validateRotateAction(action);
37
+ }
38
+ else if ('resize' in action) {
39
+ validateResizeAction(action);
40
+ }
41
+ }
42
+ }
43
+ function validateCropAction(action) {
44
+ const isValid = typeof action.crop === 'object' &&
45
+ action.crop !== null &&
46
+ typeof action.crop.originX === 'number' &&
47
+ typeof action.crop.originY === 'number' &&
48
+ typeof action.crop.width === 'number' &&
49
+ typeof action.crop.height === 'number';
50
+ if (!isValid) {
51
+ throw new TypeError('Crop action must be an object of shape { originX: number; originY: number; width: number; height: number }');
52
+ }
53
+ }
54
+ function validateFlipAction(action) {
55
+ if (typeof action.flip !== 'string' ||
56
+ ![FlipType.Horizontal, FlipType.Vertical].includes(action.flip)) {
57
+ throw new TypeError(`Unsupported flip type: ${action.flip}`);
58
+ }
59
+ }
60
+ function validateRotateAction(action) {
61
+ if (typeof action.rotate !== 'number') {
62
+ throw new TypeError('Rotation must be a number');
63
+ }
64
+ }
65
+ function validateResizeAction(action) {
66
+ const isValid = typeof action.resize === 'object' &&
67
+ action.resize !== null &&
68
+ (typeof action.resize.width === 'number' ||
69
+ action.resize.width === undefined) &&
70
+ (typeof action.resize.height === 'number' ||
71
+ action.resize.height === undefined);
72
+ if (!isValid) {
73
+ throw new TypeError('Resize action must be an object of shape { width?: number; height?: number }');
74
+ }
75
+ }
76
+ export function validateSaveOptions({ base64, compress, format, }) {
77
+ if (base64 !== undefined && typeof base64 !== 'boolean') {
78
+ throw new TypeError('The "base64" argument must be a boolean');
79
+ }
80
+ if (compress !== undefined) {
81
+ if (typeof compress !== 'number') {
82
+ throw new TypeError('The "compress" argument must be a number');
83
+ }
84
+ if (compress < 0 || compress > 1) {
85
+ throw new TypeError('The "compress" argument must be a number between 0 and 1');
86
+ }
87
+ }
88
+ const allowedFormats = [SaveFormat.JPEG, SaveFormat.PNG, SaveFormat.WEBP];
89
+ if (format !== undefined && !allowedFormats.includes(format)) {
90
+ throw new TypeError(`The "format" argument must be one of: ${allowedFormats.join(', ')}`);
91
+ }
92
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export { useImageManipulator } from './use-image-manipulator.js';
@@ -0,0 +1,3 @@
1
+ import type { IImageManipulatorContext, IImageRef } from '../core';
2
+ /** React twin of `expo-image-manipulator`'s `useImageManipulator` */
3
+ export declare function useImageManipulator(source: string | IImageRef): IImageManipulatorContext;
@@ -0,0 +1,7 @@
1
+ import { createResourceHook } from '@symbiote-native/react';
2
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
3
+ const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
4
+ /** React twin of `expo-image-manipulator`'s `useImageManipulator` */
5
+ export function useImageManipulator(source) {
6
+ return useManipulatorResource(source);
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export { useImageManipulator } from './use-image-manipulator.js';
@@ -0,0 +1,4 @@
1
+ import type { Accessor } from 'solid-js';
2
+ import type { IImageManipulatorContext, IImageRef } from '../core';
3
+ /** Solid twin of `expo-image-manipulator`'s `useImageManipulator` */
4
+ export declare function useImageManipulator(source: Accessor<string | IImageRef>): Accessor<IImageManipulatorContext>;
@@ -0,0 +1,7 @@
1
+ import { createResourceHook } from '@symbiote-native/solid';
2
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
3
+ const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
4
+ /** Solid twin of `expo-image-manipulator`'s `useImageManipulator` */
5
+ export function useImageManipulator(source) {
6
+ return useManipulatorResource(() => [source()]);
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator.svelte';
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export { useImageManipulator } from './use-image-manipulator.svelte';
@@ -0,0 +1,5 @@
1
+ import type { IImageManipulatorContext, IImageRef } from '../core';
2
+ export type IUseImageManipulatorResult = {
3
+ readonly current: IImageManipulatorContext;
4
+ };
5
+ export declare function useImageManipulator(getSource: () => string | IImageRef): IUseImageManipulatorResult;
@@ -0,0 +1,7 @@
1
+ // Svelte twin of `expo-image-manipulator`'s `useImageManipulator`
2
+ import { createResourceHook } from '@symbiote-native/svelte/runes/create-resource-hook';
3
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
4
+ const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
5
+ export function useImageManipulator(getSource) {
6
+ return useManipulatorResource(() => [getSource()]);
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { useImageManipulator } from './use-image-manipulator';
@@ -0,0 +1,2 @@
1
+ export * from '../core/index.js';
2
+ export { useImageManipulator } from './use-image-manipulator.js';
@@ -0,0 +1,4 @@
1
+ import { type ComputedRef, type MaybeRefOrGetter } from '@vue/runtime-core';
2
+ import type { IImageManipulatorContext, IImageRef } from '../core';
3
+ /** Vue twin of `expo-image-manipulator`'s `useImageManipulator` */
4
+ export declare function useImageManipulator(source: MaybeRefOrGetter<string | IImageRef>): ComputedRef<IImageManipulatorContext>;
@@ -0,0 +1,8 @@
1
+ import { toValue, } from '@vue/runtime-core';
2
+ import { createResourceHook } from '@symbiote-native/vue';
3
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller.js';
4
+ const useManipulatorResource = createResourceHook(createImageManipulatorResourceController);
5
+ /** Vue twin of `expo-image-manipulator`'s `useImageManipulator` */
6
+ export function useImageManipulator(source) {
7
+ return useManipulatorResource(() => [toValue(source)]);
8
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { injectImageManipulator } from './inject-image-manipulator';
@@ -0,0 +1,3 @@
1
+ export * from '../core';
2
+ export { injectImageManipulator } from './inject-image-manipulator';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Signal } from '@angular/core';
2
+ import type { IImageManipulatorContext, IImageRef } from '../core';
3
+ export declare function injectImageManipulator(source: () => string | IImageRef): Signal<IImageManipulatorContext>;
@@ -0,0 +1,9 @@
1
+ // Angular twin of `useImageManipulator`, `injectX` shape matching
2
+ // `@symbiote-native/navigation`'s `injectLinkingIntegration`
3
+ import { createResourceHook } from '@symbiote-native/angular';
4
+ import { createImageManipulatorResourceController } from '../core/manipulator-resource-controller';
5
+ const injectManipulatorResource = createResourceHook(createImageManipulatorResourceController);
6
+ export function injectImageManipulator(source) {
7
+ return injectManipulatorResource(() => [source()]);
8
+ }
9
+ //# sourceMappingURL=inject-image-manipulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-image-manipulator.js","sourceRoot":"","sources":["../../src/angular/inject-image-manipulator.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,6DAA6D;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,wCAAwC,EAAE,MAAM,yCAAyC,CAAC;AAEnG,MAAM,yBAAyB,GAAG,kBAAkB,CAClD,wCAAwC,CACzC,CAAC;AAEF,MAAM,UAAU,sBAAsB,CACpC,MAAgC;IAEhC,OAAO,yBAAyB,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { NativeImageManipulatorContext, NativeImageRef } from './native-module';
2
+ import type { IAction, IImageResult, ISaveOptions } from './types';
3
+ export type { NativeImageManipulatorContext as IImageManipulatorContext };
4
+ export type { NativeImageRef as IImageRef };
5
+ /** Loads `source` and starts a new chainable manipulation context. */
6
+ export declare function manipulate(source: string | NativeImageRef): NativeImageManipulatorContext;
7
+ /**
8
+ * @deprecated replaced by `manipulate(source)` / the returned context's chainable methods.
9
+ */
10
+ export declare function manipulateAsync(uri: string, actions?: IAction[], saveOptions?: ISaveOptions): Promise<IImageResult>;