@symbiote-native/image 0.0.0-stage → 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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -2
  3. package/build/angular/image/index.d.ts +20 -0
  4. package/build/angular/image/index.js +113 -0
  5. package/build/angular/image-background/index.d.ts +14 -0
  6. package/build/angular/image-background/index.js +109 -0
  7. package/build/angular/index.d.ts +4 -0
  8. package/build/angular/index.js +6 -0
  9. package/build/angular/inject-image.d.ts +8 -0
  10. package/build/angular/inject-image.js +20 -0
  11. package/build/core/constants.d.ts +3 -0
  12. package/build/core/constants.js +4 -0
  13. package/build/core/hash-strings.d.ts +5 -0
  14. package/build/core/hash-strings.js +22 -0
  15. package/build/core/image-api.d.ts +18 -0
  16. package/build/core/image-api.js +30 -0
  17. package/build/core/image-background.d.ts +11 -0
  18. package/build/core/image-background.js +29 -0
  19. package/build/core/image-loader.d.ts +20 -0
  20. package/build/core/image-loader.js +42 -0
  21. package/build/core/image-ref-loader.d.ts +4 -0
  22. package/build/core/image-ref-loader.js +10 -0
  23. package/build/core/image-view-events.d.ts +9 -0
  24. package/build/core/image-view-events.js +34 -0
  25. package/build/core/image-view.d.ts +14 -0
  26. package/build/core/image-view.js +113 -0
  27. package/build/core/index.d.ts +10 -0
  28. package/build/core/index.js +7 -0
  29. package/build/core/native-module.d.ts +27 -0
  30. package/build/core/native-module.js +3 -0
  31. package/build/core/native-style.d.ts +19 -0
  32. package/build/core/native-style.js +65 -0
  33. package/build/core/props.d.ts +11 -0
  34. package/build/core/props.js +87 -0
  35. package/build/core/sources.d.ts +8 -0
  36. package/build/core/sources.js +61 -0
  37. package/build/core/types.d.ts +186 -0
  38. package/build/core/types.js +1 -0
  39. package/build/react/image-background.d.ts +7 -0
  40. package/build/react/image-background.js +7 -0
  41. package/build/react/image.d.ts +8 -0
  42. package/build/react/image.js +6 -0
  43. package/build/react/index.d.ts +6 -0
  44. package/build/react/index.js +5 -0
  45. package/build/react/use-image.d.ts +8 -0
  46. package/build/react/use-image.js +20 -0
  47. package/build/solid/image-background.d.ts +7 -0
  48. package/build/solid/image-background.js +4 -0
  49. package/build/solid/image.d.ts +45 -0
  50. package/build/solid/image.js +4 -0
  51. package/build/solid/index.d.ts +5 -0
  52. package/build/solid/index.js +5 -0
  53. package/build/solid/use-image.d.ts +8 -0
  54. package/build/solid/use-image.js +16 -0
  55. package/build/svelte/image-background-props.d.ts +5 -0
  56. package/build/svelte/image-background-props.js +1 -0
  57. package/build/svelte/image-background.svelte +10 -0
  58. package/build/svelte/image-background.svelte.d.ts +4 -0
  59. package/build/svelte/image.svelte +26 -0
  60. package/build/svelte/image.svelte.d.ts +10 -0
  61. package/build/svelte/index.d.ts +6 -0
  62. package/build/svelte/index.js +5 -0
  63. package/build/svelte/use-image.svelte.d.ts +10 -0
  64. package/build/svelte/use-image.svelte.js +26 -0
  65. package/build/vue/image-background.d.ts +3 -0
  66. package/build/vue/image-background.js +12 -0
  67. package/build/vue/image.d.ts +2 -0
  68. package/build/vue/image.js +4 -0
  69. package/build/vue/index.d.ts +4 -0
  70. package/build/vue/index.js +5 -0
  71. package/build/vue/use-image.d.ts +8 -0
  72. package/build/vue/use-image.js +18 -0
  73. package/build-ngc/angular/image/index.d.ts +23 -0
  74. package/build-ngc/angular/image/index.js +72 -0
  75. package/build-ngc/angular/image/index.js.map +1 -0
  76. package/build-ngc/angular/image-background/index.d.ts +17 -0
  77. package/build-ngc/angular/image-background/index.js +70 -0
  78. package/build-ngc/angular/image-background/index.js.map +1 -0
  79. package/build-ngc/angular/index.d.ts +4 -0
  80. package/build-ngc/angular/index.js +7 -0
  81. package/build-ngc/angular/index.js.map +1 -0
  82. package/build-ngc/angular/inject-image.d.ts +8 -0
  83. package/build-ngc/angular/inject-image.js +23 -0
  84. package/build-ngc/angular/inject-image.js.map +1 -0
  85. package/build-ngc/core/constants.d.ts +3 -0
  86. package/build-ngc/core/constants.js +5 -0
  87. package/build-ngc/core/constants.js.map +1 -0
  88. package/build-ngc/core/hash-strings.d.ts +5 -0
  89. package/build-ngc/core/hash-strings.js +23 -0
  90. package/build-ngc/core/hash-strings.js.map +1 -0
  91. package/build-ngc/core/image-api.d.ts +18 -0
  92. package/build-ngc/core/image-api.js +31 -0
  93. package/build-ngc/core/image-api.js.map +1 -0
  94. package/build-ngc/core/image-background.d.ts +11 -0
  95. package/build-ngc/core/image-background.js +30 -0
  96. package/build-ngc/core/image-background.js.map +1 -0
  97. package/build-ngc/core/image-loader.d.ts +20 -0
  98. package/build-ngc/core/image-loader.js +43 -0
  99. package/build-ngc/core/image-loader.js.map +1 -0
  100. package/build-ngc/core/image-ref-loader.d.ts +4 -0
  101. package/build-ngc/core/image-ref-loader.js +11 -0
  102. package/build-ngc/core/image-ref-loader.js.map +1 -0
  103. package/build-ngc/core/image-view-events.d.ts +9 -0
  104. package/build-ngc/core/image-view-events.js +35 -0
  105. package/build-ngc/core/image-view-events.js.map +1 -0
  106. package/build-ngc/core/image-view.d.ts +14 -0
  107. package/build-ngc/core/image-view.js +114 -0
  108. package/build-ngc/core/image-view.js.map +1 -0
  109. package/build-ngc/core/index.d.ts +10 -0
  110. package/build-ngc/core/index.js +8 -0
  111. package/build-ngc/core/index.js.map +1 -0
  112. package/build-ngc/core/native-module.d.ts +27 -0
  113. package/build-ngc/core/native-module.js +4 -0
  114. package/build-ngc/core/native-module.js.map +1 -0
  115. package/build-ngc/core/native-style.d.ts +19 -0
  116. package/build-ngc/core/native-style.js +66 -0
  117. package/build-ngc/core/native-style.js.map +1 -0
  118. package/build-ngc/core/props.d.ts +11 -0
  119. package/build-ngc/core/props.js +88 -0
  120. package/build-ngc/core/props.js.map +1 -0
  121. package/build-ngc/core/sources.d.ts +8 -0
  122. package/build-ngc/core/sources.js +62 -0
  123. package/build-ngc/core/sources.js.map +1 -0
  124. package/build-ngc/core/types.d.ts +186 -0
  125. package/build-ngc/core/types.js +2 -0
  126. package/build-ngc/core/types.js.map +1 -0
  127. package/native-link.json +13 -0
  128. package/package.json +145 -3
  129. package/src/angular/image/index.ts +79 -0
  130. package/src/angular/image-background/index.ts +72 -0
  131. package/src/angular/index.ts +7 -0
  132. package/src/angular/inject-image.ts +29 -0
  133. package/src/core/constants.ts +6 -0
  134. package/src/core/hash-strings.ts +29 -0
  135. package/src/core/image-api.ts +77 -0
  136. package/src/core/image-background.ts +47 -0
  137. package/src/core/image-loader.ts +72 -0
  138. package/src/core/image-ref-loader.ts +16 -0
  139. package/src/core/image-view-events.ts +47 -0
  140. package/src/core/image-view.ts +173 -0
  141. package/src/core/index.ts +50 -0
  142. package/src/core/native-module.ts +44 -0
  143. package/src/core/native-style.ts +96 -0
  144. package/src/core/props.ts +123 -0
  145. package/src/core/sources.ts +75 -0
  146. package/src/core/types.ts +275 -0
  147. package/src/react/image-background.ts +16 -0
  148. package/src/react/image.ts +17 -0
  149. package/src/react/index.ts +8 -0
  150. package/src/react/use-image.ts +29 -0
  151. package/src/solid/image-background.ts +12 -0
  152. package/src/solid/image.ts +9 -0
  153. package/src/solid/index.ts +7 -0
  154. package/src/solid/use-image.ts +30 -0
  155. package/src/svelte/image-background-props.ts +6 -0
  156. package/src/svelte/image-background.svelte +10 -0
  157. package/src/svelte/image.svelte +26 -0
  158. package/src/svelte/index.ts +8 -0
  159. package/src/svelte/svelte-ambient.d.ts +3 -0
  160. package/src/svelte/svelte-compile.test-helper.ts +6 -0
  161. package/src/svelte/use-image.svelte.ts +38 -0
  162. package/src/vue/image-background.ts +17 -0
  163. package/src/vue/image.ts +5 -0
  164. package/src/vue/index.ts +6 -0
  165. package/src/vue/use-image.ts +38 -0
@@ -0,0 +1,275 @@
1
+ import type {
2
+ IAccessibilityProps,
3
+ IAriaProps,
4
+ IResponderProps,
5
+ } from '@symbiote-native/components';
6
+ import type {
7
+ IColorValue,
8
+ IStyleProp,
9
+ ITextStyle,
10
+ IViewStyle,
11
+ } from '@symbiote-native/engine';
12
+ import type { SharedRef } from 'expo-modules-core';
13
+ import type { SFSymbol } from 'sf-symbols-typescript';
14
+
15
+ export type IImageSource = {
16
+ /** `http(s)`, `file`, `data`, an asset URI and the `sf:/` and `blurhash:/` schemes */
17
+ uri?: string;
18
+ /** Sent with the request */
19
+ headers?: Record<string, string>;
20
+ /** Used for layout while the image loads, and the size of a hash placeholder */
21
+ width?: number | null;
22
+ height?: number | null;
23
+ /** A Blurhash string, the source becomes a `blurhash:/` uri */
24
+ blurhash?: string;
25
+ /** A Thumbhash string, the source becomes a `thumbhash:/` uri */
26
+ thumbhash?: string;
27
+ /** The key of the cache entry, the uri by default */
28
+ cacheKey?: string;
29
+ /** Web only */
30
+ webMaxViewportWidth?: number;
31
+ /** Plays the image as an animation, detected from the content by default */
32
+ isAnimated?: boolean;
33
+ };
34
+
35
+ export type IImageContentFit =
36
+ 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
37
+
38
+ export type IImageDecodeFormat = 'argb' | 'rgb';
39
+
40
+ export type IImageResizeMode =
41
+ 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
42
+
43
+ export type IImageContentPositionValue =
44
+ number | string | `${number}%` | `${number}` | 'center';
45
+
46
+ export type IImageContentPositionString =
47
+ | 'center'
48
+ | 'top'
49
+ | 'right'
50
+ | 'bottom'
51
+ | 'left'
52
+ | 'top center'
53
+ | 'top right'
54
+ | 'top left'
55
+ | 'right center'
56
+ | 'right top'
57
+ | 'right bottom'
58
+ | 'bottom center'
59
+ | 'bottom right'
60
+ | 'bottom left'
61
+ | 'left center'
62
+ | 'left top'
63
+ | 'left bottom';
64
+
65
+ /** Two edges, the native side takes only this form */
66
+ export type IImageContentPositionObject =
67
+ | { top?: IImageContentPositionValue; right?: IImageContentPositionValue }
68
+ | { top?: IImageContentPositionValue; left?: IImageContentPositionValue }
69
+ | { bottom?: IImageContentPositionValue; right?: IImageContentPositionValue }
70
+ | { bottom?: IImageContentPositionValue; left?: IImageContentPositionValue };
71
+
72
+ export type IImageContentPosition =
73
+ IImageContentPositionObject | IImageContentPositionString;
74
+
75
+ export type IImageTransition = {
76
+ /** In milliseconds */
77
+ duration?: number;
78
+ timing?: 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear';
79
+ effect?:
80
+ | 'cross-dissolve'
81
+ | 'flip-from-top'
82
+ | 'flip-from-right'
83
+ | 'flip-from-bottom'
84
+ | 'flip-from-left'
85
+ | 'curl-up'
86
+ | 'curl-down'
87
+ | 'sf:replace'
88
+ | 'sf:down-up'
89
+ | 'sf:up-up'
90
+ | 'sf:off-up'
91
+ | null;
92
+ };
93
+
94
+ export type ISfSymbolEffectType =
95
+ | 'bounce'
96
+ | 'bounce/up'
97
+ | 'bounce/down'
98
+ | 'pulse'
99
+ | 'variable-color'
100
+ | 'variable-color/iterative'
101
+ | 'variable-color/cumulative'
102
+ | 'scale'
103
+ | 'scale/up'
104
+ | 'scale/down'
105
+ | 'appear'
106
+ | 'disappear'
107
+ | 'wiggle'
108
+ | 'rotate'
109
+ | 'breathe'
110
+ | 'draw/on'
111
+ | 'draw/off';
112
+
113
+ export type ISfSymbolEffectObject = {
114
+ effect: ISfSymbolEffectType;
115
+ /** `-1` repeats forever */
116
+ repeat?: number;
117
+ scope?: 'by-layer' | 'whole-symbol' | null;
118
+ };
119
+
120
+ /** iOS 17+ for an SF Symbol source */
121
+ export type ISfSymbolEffect =
122
+ | ISfSymbolEffectType
123
+ | ISfSymbolEffectObject
124
+ | (ISfSymbolEffectType | ISfSymbolEffectObject)[];
125
+
126
+ export type IImageLoadEventData = {
127
+ cacheType: 'none' | 'disk' | 'memory';
128
+ source: {
129
+ url: string;
130
+ width: number;
131
+ height: number;
132
+ mediaType: string | null;
133
+ isAnimated?: boolean;
134
+ };
135
+ };
136
+
137
+ export type IImageProgressEventData = { loaded: number; total: number };
138
+
139
+ export type IImageErrorEventData = { error: string };
140
+
141
+ export type IImagePrefetchOptions = {
142
+ /** `memory-disk` by default */
143
+ cachePolicy?: 'disk' | 'memory-disk' | 'memory';
144
+ headers?: Record<string, string>;
145
+ };
146
+
147
+ export type IImageCacheConfig = {
148
+ /** In bytes */
149
+ maxDiskSize?: number;
150
+ /** In bytes */
151
+ maxMemoryCost?: number;
152
+ maxMemoryCount?: number;
153
+ };
154
+
155
+ export type IImageLoadOptions = {
156
+ maxWidth?: number;
157
+ maxHeight?: number;
158
+ tintColor?: IColorValue | number;
159
+ /** `retry` loads again */
160
+ onError?(error: Error, retry: () => void): void;
161
+ };
162
+
163
+ /** A reference to a native image, an image view takes it as its source */
164
+ export declare class ImageRef extends SharedRef<'image'> {
165
+ readonly width: number;
166
+ readonly height: number;
167
+ readonly scale: number;
168
+ readonly mediaType: string | null;
169
+ readonly isAnimated?: boolean;
170
+ /** The id the native view looks the shared object up by */
171
+ readonly __expo_shared_object_id__: number;
172
+ }
173
+
174
+ export type IImageStyle = IViewStyle & {
175
+ tintColor?: IColorValue;
176
+ resizeMode?: IImageResizeMode;
177
+ /** With `color` and `fontSize` for an SF Symbol */
178
+ fontWeight?: ITextStyle['fontWeight'];
179
+ color?: IColorValue;
180
+ fontSize?: number;
181
+ };
182
+
183
+ export type IImageSources =
184
+ | IImageSource
185
+ | `sf:${SFSymbol}`
186
+ | (string & {})
187
+ | number
188
+ | IImageSource[]
189
+ | string[]
190
+ | SharedRef<'image'>
191
+ | null;
192
+
193
+ export type IImageViewProps = IAccessibilityProps &
194
+ IAriaProps &
195
+ IResponderProps & {
196
+ style?: IStyleProp<IImageStyle>;
197
+ /** A uri, an asset, a source object, a list of them or an `ImageRef` */
198
+ source?: IImageSources;
199
+ /** Shown while `source` loads */
200
+ placeholder?:
201
+ | IImageSource
202
+ | string
203
+ | number
204
+ | IImageSource[]
205
+ | string[]
206
+ | SharedRef<'image'>
207
+ | null;
208
+ /** `cover` by default, `contain` for an SF Symbol */
209
+ contentFit?: IImageContentFit;
210
+ placeholderContentFit?: IImageContentFit;
211
+ /** `center` by default */
212
+ contentPosition?: IImageContentPosition;
213
+ /** A number is the duration of a cross dissolve */
214
+ transition?: IImageTransition | number | null;
215
+ blurRadius?: number;
216
+ tintColor?: IColorValue | null;
217
+ priority?: 'low' | 'normal' | 'high' | null;
218
+ loading?: 'lazy' | 'eager';
219
+ cachePolicy?: 'none' | 'disk' | 'memory' | 'memory-disk' | null;
220
+ /** How the image follows a changing view size */
221
+ responsivePolicy?: 'live' | 'initial' | 'static';
222
+ /** Lets a recycled list cell tell its images apart */
223
+ recyclingKey?: string | null;
224
+ /** Plays an animated image on its own, `true` by default */
225
+ autoplay?: boolean;
226
+ /** An effect of an SF Symbol source, iOS 17+ */
227
+ sfEffect?: ISfSymbolEffect | null;
228
+ onLoadStart?: () => void;
229
+ onLoad?: (event: IImageLoadEventData) => void;
230
+ onProgress?: (event: IImageProgressEventData) => void;
231
+ onError?: (event: IImageErrorEventData) => void;
232
+ onLoadEnd?: () => void;
233
+ onDisplay?: () => void;
234
+ /** @deprecated Use `placeholder` */
235
+ defaultSource?: IImageSource | null;
236
+ /** @deprecated Use `placeholder` */
237
+ loadingIndicatorSource?: IImageSource | null;
238
+ /** @deprecated Use `contentFit` */
239
+ resizeMode?: IImageResizeMode;
240
+ /** @deprecated Use `transition` */
241
+ fadeDuration?: number;
242
+ focusable?: boolean;
243
+ accessible?: boolean;
244
+ accessibilityLabel?: string;
245
+ /** The accessibility label when none is given */
246
+ alt?: string;
247
+ /** iOS only, lets the user select text in the image */
248
+ enableLiveTextInteraction?: boolean;
249
+ /** Downscales a larger image to the view, `true` by default */
250
+ allowDownscaling?: boolean;
251
+ /** Android only, `argb` by default */
252
+ decodeFormat?: IImageDecodeFormat;
253
+ /** iOS only */
254
+ useAppleWebpCodec?: boolean;
255
+ /** Android only */
256
+ enforceEarlyResizing?: boolean;
257
+ /** iOS only */
258
+ preferHighDynamicRange?: boolean;
259
+ /** Web only */
260
+ draggable?: boolean;
261
+ testID?: string;
262
+ nativeID?: string;
263
+ };
264
+
265
+ /** What a ref to the view hands out */
266
+ export type IImageViewHandle = {
267
+ /** Starts an animated image */
268
+ startAnimating(): Promise<void>;
269
+ stopAnimating(): Promise<void>;
270
+ /** Keeps the resource from being reloaded */
271
+ lockResourceAsync(): Promise<void>;
272
+ unlockResourceAsync(): Promise<void>;
273
+ /** Reloads the resource, the lock is ignored */
274
+ reloadAsync(): Promise<void>;
275
+ };
@@ -0,0 +1,16 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import { descriptorToReactWithChildren } from '@symbiote-native/react';
3
+ import { renderImageBackground } from '../core';
4
+ import type { IImageBackgroundProps } from '../core';
5
+
6
+ export type IImageBackgroundReactProps = IImageBackgroundProps & {
7
+ children?: ReactNode;
8
+ };
9
+
10
+ /** React twin of `expo-image`'s `ImageBackground`, children paint over the image */
11
+ export function ImageBackground(
12
+ props: IImageBackgroundReactProps,
13
+ ): ReactElement {
14
+ const { children, ...rest } = props;
15
+ return descriptorToReactWithChildren(renderImageBackground(rest), children);
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { ReactElement, Ref } from 'react';
2
+ import { useNativeViewController } from '@symbiote-native/react';
3
+ import { createImageView } from '../core';
4
+ import type { IImageViewHandle, IImageViewProps } from '../core';
5
+
6
+ export type IImageReactProps = IImageViewProps & {
7
+ ref?: Ref<IImageViewHandle>;
8
+ className?: string;
9
+ };
10
+
11
+ /** React twin of `expo-image`'s `Image` */
12
+ export function Image({
13
+ ref,
14
+ ...props
15
+ }: IImageReactProps): ReactElement | null {
16
+ return useNativeViewController(ref, createImageView, props);
17
+ }
@@ -0,0 +1,8 @@
1
+ // @symbiote-native/image/react: the image view, its cache and hash functions on a shared core
2
+
3
+ export { Image } from './image';
4
+ export type { IImageReactProps } from './image';
5
+ export { ImageBackground } from './image-background';
6
+ export type { IImageBackgroundReactProps } from './image-background';
7
+ export { useImage } from './use-image';
8
+ export * from '../core';
@@ -0,0 +1,29 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import type { DependencyList } from 'react';
3
+ import { createImageRefLoader, resolveSource } from '../core';
4
+ import type { IImageLoadOptions, IImageSource, ImageRef } from '../core';
5
+
6
+ /**
7
+ * Loads an image into a native `ImageRef`, `null` until the first one has loaded
8
+ *
9
+ * It loads again when the uri of the source or an item of `dependencies` changes
10
+ */
11
+ export function useImage(
12
+ source: IImageSource | string | number,
13
+ options: IImageLoadOptions = {},
14
+ dependencies: DependencyList = [],
15
+ ): ImageRef | null {
16
+ const [image, setImage] = useState<ImageRef | null>(null);
17
+ // The effect reads the options of the latest render, not the ones it started with
18
+ const latestOptions = useRef(options);
19
+ latestOptions.current = options;
20
+ const key = resolveSource(source)?.uri;
21
+
22
+ useEffect(() => {
23
+ const loader = createImageRefLoader(setImage, () => latestOptions.current);
24
+ loader.load(source);
25
+ return loader.cancel;
26
+ }, [key, ...dependencies]);
27
+
28
+ return image;
29
+ }
@@ -0,0 +1,12 @@
1
+ import type { JSX } from 'solid-js';
2
+ import { defineDescriptorComponent } from '@symbiote-native/solid';
3
+ import { renderImageBackground } from '../core';
4
+ import type { IImageBackgroundProps } from '../core';
5
+
6
+ export type IImageBackgroundSolidProps = IImageBackgroundProps & {
7
+ children?: JSX.Element;
8
+ };
9
+
10
+ /** Solid twin of `expo-image`'s `ImageBackground`, children paint over the image */
11
+ export const ImageBackground =
12
+ defineDescriptorComponent<IImageBackgroundSolidProps>(renderImageBackground);
@@ -0,0 +1,9 @@
1
+ import { defineNativeViewComponent } from '@symbiote-native/solid';
2
+ import { createImageView } from '../core';
3
+ import type { IImageViewHandle, IImageViewProps } from '../core';
4
+
5
+ /** Solid twin of `expo-image`'s `Image`, `ref` gets the handle of the view */
6
+ export const Image = defineNativeViewComponent<
7
+ IImageViewHandle,
8
+ IImageViewProps
9
+ >(createImageView);
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/image/solid: the image view, its cache and hash functions on a shared core
2
+
3
+ export { Image } from './image';
4
+ export { ImageBackground } from './image-background';
5
+ export type { IImageBackgroundSolidProps } from './image-background';
6
+ export { useImage } from './use-image';
7
+ export * from '../core';
@@ -0,0 +1,30 @@
1
+ import { createEffect, createSignal, on, onCleanup } from 'solid-js';
2
+ import type { Accessor } from 'solid-js';
3
+ import { createImageRefLoader, resolveSource } from '../core';
4
+ import type { IImageLoadOptions, IImageSource, ImageRef } from '../core';
5
+
6
+ /**
7
+ * Solid twin of `expo-image`'s `useImage`, `null` until the first image has loaded
8
+ *
9
+ * It loads again when the uri of the source or a `dependencies` accessor changes
10
+ */
11
+ export function useImage(
12
+ source: Accessor<IImageSource | string | number>,
13
+ options: Accessor<IImageLoadOptions> = () => ({}),
14
+ dependencies: Accessor<unknown>[] = [],
15
+ ): Accessor<ImageRef | null> {
16
+ const [image, setImage] = createSignal<ImageRef | null>(null);
17
+
18
+ createEffect(
19
+ on(
20
+ () => [resolveSource(source())?.uri, ...dependencies.map(read => read())],
21
+ () => {
22
+ const loader = createImageRefLoader(setImage, options);
23
+ loader.load(source());
24
+ onCleanup(loader.cancel);
25
+ },
26
+ ),
27
+ );
28
+
29
+ return image;
30
+ }
@@ -0,0 +1,6 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { IImageBackgroundProps } from '../core';
3
+
4
+ export type IImageBackgroundSvelteProps = IImageBackgroundProps & {
5
+ children?: Snippet;
6
+ };
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import DescriptorHost from '@symbiote-native/svelte/descriptor-host';
3
+ import { renderImageBackground } from '../core';
4
+ import type { IImageBackgroundSvelteProps } from './image-background-props';
5
+
6
+ const { children, ...rest }: IImageBackgroundSvelteProps = $props();
7
+ const descriptor = $derived(renderImageBackground({ ...rest }));
8
+ </script>
9
+
10
+ <DescriptorHost {descriptor} {children} />
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ // The native view name is mixed-case, which a literal tag would parse as a component reference,
3
+ // so the tag is dynamic
4
+ import { hostProps } from '@symbiote-native/svelte/native-view-bridge';
5
+ import { useNativeViewController } from '@symbiote-native/svelte/runes/use-native-view-controller';
6
+ import { createImageView } from '../core';
7
+ import type { IImageViewProps } from '../core';
8
+
9
+ const props: IImageViewProps = $props();
10
+ const view = useNativeViewController(createImageView, () => ({ ...props }));
11
+
12
+ // The functions of the view are instance members, `bind:this` reaches them
13
+ export const startAnimating = view.handle.startAnimating;
14
+ export const stopAnimating = view.handle.stopAnimating;
15
+ export const lockResourceAsync = view.handle.lockResourceAsync;
16
+ export const unlockResourceAsync = view.handle.unlockResourceAsync;
17
+ export const reloadAsync = view.handle.reloadAsync;
18
+ </script>
19
+
20
+ {#if view.descriptor}
21
+ <svelte:element
22
+ this={view.descriptor.type}
23
+ {@attach view.attachHost}
24
+ {@attach hostProps(view.descriptor.props)}
25
+ />
26
+ {/if}
@@ -0,0 +1,8 @@
1
+ // @symbiote-native/image/svelte: the image view, its cache and hash functions on a shared core
2
+
3
+ export { default as Image } from './image.svelte';
4
+ export { default as ImageBackground } from './image-background.svelte';
5
+ export type { IImageBackgroundSvelteProps } from './image-background-props';
6
+ export { useImage } from './use-image.svelte';
7
+ export type { IUseImageResult } from './use-image.svelte';
8
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // Pulls in svelte's ambient globals ($state/$effect/...) for this package's tsc program
2
+ // This package has no value import from 'svelte', so a triple-slash reference does it explicitly
3
+ /// <reference types="svelte" />
@@ -0,0 +1,6 @@
1
+ // Re-exports the existing harness instead of another copy
2
+ export {
3
+ createSvelteHarness,
4
+ loadComponent,
5
+ type ISvelteHarness,
6
+ } from '../../../sqlite/src/svelte/svelte-compile.test-helper';
@@ -0,0 +1,38 @@
1
+ import { untrack } from 'svelte';
2
+ import { createImageRefLoader, resolveSource } from '../core';
3
+ import type { IImageLoadOptions, IImageSource, ImageRef } from '../core';
4
+
5
+ export type IUseImageResult = {
6
+ readonly current: ImageRef | null;
7
+ };
8
+
9
+ /**
10
+ * Svelte twin of `expo-image`'s `useImage`, boxed getter shape of the other hooks
11
+ *
12
+ * It loads again when the uri of the source or a value read by `getDependencies` changes
13
+ */
14
+ export function useImage(
15
+ getSource: () => IImageSource | string | number,
16
+ getOptions: () => IImageLoadOptions = () => ({}),
17
+ getDependencies: () => unknown[] = () => [],
18
+ ): IUseImageResult {
19
+ let image = $state.raw<ImageRef | null>(null);
20
+ const key = $derived(resolveSource(getSource())?.uri);
21
+
22
+ $effect(() => {
23
+ // Read so the effect tracks them, the source itself is not a trigger
24
+ void key;
25
+ getDependencies();
26
+ const loader = createImageRefLoader(loaded => {
27
+ image = loaded;
28
+ }, getOptions);
29
+ untrack(() => loader.load(getSource()));
30
+ return loader.cancel;
31
+ });
32
+
33
+ return {
34
+ get current(): ImageRef | null {
35
+ return image;
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,17 @@
1
+ import { defineComponent, type VNode } from '@vue/runtime-core';
2
+ import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
3
+ import { renderImageBackground } from '../core';
4
+
5
+ /** Vue twin of `expo-image`'s `ImageBackground`, the default slot paints over the image */
6
+ export const ImageBackground = defineComponent({
7
+ name: 'ImageBackground',
8
+ // Every attribute is a prop of the image or the view, nothing leaks onto the wrapper
9
+ inheritAttrs: false,
10
+ setup(_props, { attrs, slots }) {
11
+ return (): VNode =>
12
+ descriptorToVue(
13
+ renderImageBackground(normalizeVueAttrs(attrs)),
14
+ slots.default?.() ?? [],
15
+ );
16
+ },
17
+ });
@@ -0,0 +1,5 @@
1
+ import { defineNativeViewComponent } from '@symbiote-native/vue';
2
+ import { createImageView } from '../core';
3
+
4
+ /** Vue twin of `expo-image`'s `Image`, `ref` gets the handle of the view */
5
+ export const Image = defineNativeViewComponent('Image', createImageView);
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/image/vue: the image view, its cache and hash functions on a shared core
2
+
3
+ export { Image } from './image';
4
+ export { ImageBackground } from './image-background';
5
+ export { useImage } from './use-image';
6
+ export * from '../core';
@@ -0,0 +1,38 @@
1
+ import { shallowRef, toValue, watch } from '@vue/runtime-core';
2
+ import type {
3
+ MaybeRefOrGetter,
4
+ ShallowRef,
5
+ WatchSource,
6
+ } from '@vue/runtime-core';
7
+ import { createImageRefLoader, resolveSource } from '../core';
8
+ import type { IImageLoadOptions, IImageSource, ImageRef } from '../core';
9
+
10
+ /**
11
+ * Vue twin of `expo-image`'s `useImage`, `null` until the first image has loaded
12
+ *
13
+ * It loads again when the uri of the source or a watched `dependencies` source changes
14
+ */
15
+ export function useImage(
16
+ source: MaybeRefOrGetter<IImageSource | string | number>,
17
+ options: MaybeRefOrGetter<IImageLoadOptions> = {},
18
+ dependencies: WatchSource[] = [],
19
+ ): Readonly<ShallowRef<ImageRef | null>> {
20
+ const image = shallowRef<ImageRef | null>(null);
21
+
22
+ watch(
23
+ [() => resolveSource(toValue(source))?.uri, ...dependencies],
24
+ (_next, _previous, onCleanup) => {
25
+ const loader = createImageRefLoader(
26
+ loaded => {
27
+ image.value = loaded;
28
+ },
29
+ () => toValue(options),
30
+ );
31
+ loader.load(toValue(source));
32
+ onCleanup(loader.cancel);
33
+ },
34
+ { immediate: true },
35
+ );
36
+
37
+ return image;
38
+ }