@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.
- package/LICENSE +21 -0
- package/README.md +260 -2
- package/build/angular/image/index.d.ts +20 -0
- package/build/angular/image/index.js +113 -0
- package/build/angular/image-background/index.d.ts +14 -0
- package/build/angular/image-background/index.js +109 -0
- package/build/angular/index.d.ts +4 -0
- package/build/angular/index.js +6 -0
- package/build/angular/inject-image.d.ts +8 -0
- package/build/angular/inject-image.js +20 -0
- package/build/core/constants.d.ts +3 -0
- package/build/core/constants.js +4 -0
- package/build/core/hash-strings.d.ts +5 -0
- package/build/core/hash-strings.js +22 -0
- package/build/core/image-api.d.ts +18 -0
- package/build/core/image-api.js +30 -0
- package/build/core/image-background.d.ts +11 -0
- package/build/core/image-background.js +29 -0
- package/build/core/image-loader.d.ts +20 -0
- package/build/core/image-loader.js +42 -0
- package/build/core/image-ref-loader.d.ts +4 -0
- package/build/core/image-ref-loader.js +10 -0
- package/build/core/image-view-events.d.ts +9 -0
- package/build/core/image-view-events.js +34 -0
- package/build/core/image-view.d.ts +14 -0
- package/build/core/image-view.js +113 -0
- package/build/core/index.d.ts +10 -0
- package/build/core/index.js +7 -0
- package/build/core/native-module.d.ts +27 -0
- package/build/core/native-module.js +3 -0
- package/build/core/native-style.d.ts +19 -0
- package/build/core/native-style.js +65 -0
- package/build/core/props.d.ts +11 -0
- package/build/core/props.js +87 -0
- package/build/core/sources.d.ts +8 -0
- package/build/core/sources.js +61 -0
- package/build/core/types.d.ts +186 -0
- package/build/core/types.js +1 -0
- package/build/react/image-background.d.ts +7 -0
- package/build/react/image-background.js +7 -0
- package/build/react/image.d.ts +8 -0
- package/build/react/image.js +6 -0
- package/build/react/index.d.ts +6 -0
- package/build/react/index.js +5 -0
- package/build/react/use-image.d.ts +8 -0
- package/build/react/use-image.js +20 -0
- package/build/solid/image-background.d.ts +7 -0
- package/build/solid/image-background.js +4 -0
- package/build/solid/image.d.ts +45 -0
- package/build/solid/image.js +4 -0
- package/build/solid/index.d.ts +5 -0
- package/build/solid/index.js +5 -0
- package/build/solid/use-image.d.ts +8 -0
- package/build/solid/use-image.js +16 -0
- package/build/svelte/image-background-props.d.ts +5 -0
- package/build/svelte/image-background-props.js +1 -0
- package/build/svelte/image-background.svelte +10 -0
- package/build/svelte/image-background.svelte.d.ts +4 -0
- package/build/svelte/image.svelte +26 -0
- package/build/svelte/image.svelte.d.ts +10 -0
- package/build/svelte/index.d.ts +6 -0
- package/build/svelte/index.js +5 -0
- package/build/svelte/use-image.svelte.d.ts +10 -0
- package/build/svelte/use-image.svelte.js +26 -0
- package/build/vue/image-background.d.ts +3 -0
- package/build/vue/image-background.js +12 -0
- package/build/vue/image.d.ts +2 -0
- package/build/vue/image.js +4 -0
- package/build/vue/index.d.ts +4 -0
- package/build/vue/index.js +5 -0
- package/build/vue/use-image.d.ts +8 -0
- package/build/vue/use-image.js +18 -0
- package/build-ngc/angular/image/index.d.ts +23 -0
- package/build-ngc/angular/image/index.js +72 -0
- package/build-ngc/angular/image/index.js.map +1 -0
- package/build-ngc/angular/image-background/index.d.ts +17 -0
- package/build-ngc/angular/image-background/index.js +70 -0
- package/build-ngc/angular/image-background/index.js.map +1 -0
- package/build-ngc/angular/index.d.ts +4 -0
- package/build-ngc/angular/index.js +7 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/inject-image.d.ts +8 -0
- package/build-ngc/angular/inject-image.js +23 -0
- package/build-ngc/angular/inject-image.js.map +1 -0
- package/build-ngc/core/constants.d.ts +3 -0
- package/build-ngc/core/constants.js +5 -0
- package/build-ngc/core/constants.js.map +1 -0
- package/build-ngc/core/hash-strings.d.ts +5 -0
- package/build-ngc/core/hash-strings.js +23 -0
- package/build-ngc/core/hash-strings.js.map +1 -0
- package/build-ngc/core/image-api.d.ts +18 -0
- package/build-ngc/core/image-api.js +31 -0
- package/build-ngc/core/image-api.js.map +1 -0
- package/build-ngc/core/image-background.d.ts +11 -0
- package/build-ngc/core/image-background.js +30 -0
- package/build-ngc/core/image-background.js.map +1 -0
- package/build-ngc/core/image-loader.d.ts +20 -0
- package/build-ngc/core/image-loader.js +43 -0
- package/build-ngc/core/image-loader.js.map +1 -0
- package/build-ngc/core/image-ref-loader.d.ts +4 -0
- package/build-ngc/core/image-ref-loader.js +11 -0
- package/build-ngc/core/image-ref-loader.js.map +1 -0
- package/build-ngc/core/image-view-events.d.ts +9 -0
- package/build-ngc/core/image-view-events.js +35 -0
- package/build-ngc/core/image-view-events.js.map +1 -0
- package/build-ngc/core/image-view.d.ts +14 -0
- package/build-ngc/core/image-view.js +114 -0
- package/build-ngc/core/image-view.js.map +1 -0
- package/build-ngc/core/index.d.ts +10 -0
- package/build-ngc/core/index.js +8 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +27 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/native-style.d.ts +19 -0
- package/build-ngc/core/native-style.js +66 -0
- package/build-ngc/core/native-style.js.map +1 -0
- package/build-ngc/core/props.d.ts +11 -0
- package/build-ngc/core/props.js +88 -0
- package/build-ngc/core/props.js.map +1 -0
- package/build-ngc/core/sources.d.ts +8 -0
- package/build-ngc/core/sources.js +62 -0
- package/build-ngc/core/sources.js.map +1 -0
- package/build-ngc/core/types.d.ts +186 -0
- package/build-ngc/core/types.js +2 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +13 -0
- package/package.json +145 -3
- package/src/angular/image/index.ts +79 -0
- package/src/angular/image-background/index.ts +72 -0
- package/src/angular/index.ts +7 -0
- package/src/angular/inject-image.ts +29 -0
- package/src/core/constants.ts +6 -0
- package/src/core/hash-strings.ts +29 -0
- package/src/core/image-api.ts +77 -0
- package/src/core/image-background.ts +47 -0
- package/src/core/image-loader.ts +72 -0
- package/src/core/image-ref-loader.ts +16 -0
- package/src/core/image-view-events.ts +47 -0
- package/src/core/image-view.ts +173 -0
- package/src/core/index.ts +50 -0
- package/src/core/native-module.ts +44 -0
- package/src/core/native-style.ts +96 -0
- package/src/core/props.ts +123 -0
- package/src/core/sources.ts +75 -0
- package/src/core/types.ts +275 -0
- package/src/react/image-background.ts +16 -0
- package/src/react/image.ts +17 -0
- package/src/react/index.ts +8 -0
- package/src/react/use-image.ts +29 -0
- package/src/solid/image-background.ts +12 -0
- package/src/solid/image.ts +9 -0
- package/src/solid/index.ts +7 -0
- package/src/solid/use-image.ts +30 -0
- package/src/svelte/image-background-props.ts +6 -0
- package/src/svelte/image-background.svelte +10 -0
- package/src/svelte/image.svelte +26 -0
- package/src/svelte/index.ts +8 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +6 -0
- package/src/svelte/use-image.svelte.ts +38 -0
- package/src/vue/image-background.ts +17 -0
- package/src/vue/image.ts +5 -0
- package/src/vue/index.ts +6 -0
- package/src/vue/use-image.ts +38 -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
CHANGED
|
@@ -1,3 +1,261 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @symbiote-native/image
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/) for **every**
|
|
4
|
+
[SymbioteNative](../../README.md) adapter: React, Vue, Svelte, Solid and Angular. An image view
|
|
5
|
+
with caching, placeholders, transitions and SF Symbols, plus the cache and hash functions.
|
|
6
|
+
|
|
7
|
+
Built the same way as [`@symbiote-native/clipboard`](../clipboard)'s `ClipboardPasteButton`, an
|
|
8
|
+
`expo-modules-core` native view reached through `requireNativeViewManager` (see the
|
|
9
|
+
`symbiote-expo-native-module` project skill: why `expo-modules-core` is depended on directly and never
|
|
10
|
+
the `expo` meta-package, why the upstream JS is hand-ported into `core/`, and how autolinking finds the
|
|
11
|
+
native module). One render function in `core/` returns a descriptor and every adapter only turns it into
|
|
12
|
+
its own element.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
**New app:**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx @symbiote-native/cli new my-app --image
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Existing SymbioteNative app:**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @symbiote-native/cli add --image
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Either way: installs `@symbiote-native/image` and wires the native autolinking automatically,
|
|
29
|
+
see [`@symbiote-native/cli`](../cli).
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @symbiote-native/image
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Depends on `expo-image` and `expo-modules-core` directly (regular dependencies, pinned to exact
|
|
39
|
+
versions, since this package's `core/` is hand-ported against one specific native API shape).
|
|
40
|
+
Never install `expo-image` yourself, and never add the `expo` package to this project: it bundles
|
|
41
|
+
its own Metro/Babel pipeline that conflicts with this project's own.
|
|
42
|
+
|
|
43
|
+
### Required one-time step: native autolinking wiring
|
|
44
|
+
|
|
45
|
+
`expo-image`'s native code is discovered by `expo-modules-autolinking`, not by RN's own
|
|
46
|
+
`react-native.config.cjs` mechanism. Wire it into the native host app **once**, it covers this package
|
|
47
|
+
and every other `expo-modules-core` package with zero further changes. The steps and the
|
|
48
|
+
mechanics behind them are in the `symbiote-expo-native-module` skill and in
|
|
49
|
+
[`@symbiote-native/network`](../network)'s README; the reference app is `examples/expo-react`.
|
|
50
|
+
|
|
51
|
+
### Android KSP
|
|
52
|
+
|
|
53
|
+
`expo-image` reads a `kspVersion` from the app's root `build.gradle`. `native-link.json` declares
|
|
54
|
+
`android.requiresKsp`, so the `expo-modules-link` postinstall writes the right version there from Expo's
|
|
55
|
+
table for the app's Kotlin version.
|
|
56
|
+
|
|
57
|
+
</details>
|
|
58
|
+
|
|
59
|
+
## Shape
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
src/core/ image-view.ts - createImageView(getNode): render, the view handle and the events.
|
|
63
|
+
props.ts (normalization, deprecation warnings), sources.ts, native-style.ts,
|
|
64
|
+
image-background.ts, image-api.ts (the statics as functions), image-loader.ts and
|
|
65
|
+
image-ref-loader.ts (useImage's core), hash-strings.ts, types.ts.
|
|
66
|
+
src/react/ @symbiote-native/image/react - Image, ImageBackground, useImage
|
|
67
|
+
src/vue/ @symbiote-native/image/vue - Image, ImageBackground, useImage (a shallow ref)
|
|
68
|
+
src/svelte/ @symbiote-native/image/svelte - Image, ImageBackground, useImage ({ current })
|
|
69
|
+
src/solid/ @symbiote-native/image/solid - Image, ImageBackground, useImage (an accessor)
|
|
70
|
+
src/angular/ @symbiote-native/image/angular - ExpoImage, ExpoImageBackground, injectImage (a signal)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Angular's selectors are `ExpoImage` and `ExpoImageBackground` because `Image` and `ImageBackground` are
|
|
74
|
+
already tags of the adapter.
|
|
75
|
+
|
|
76
|
+
## Use it
|
|
77
|
+
|
|
78
|
+
### React
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { Image, useImage } from '@symbiote-native/image/react';
|
|
82
|
+
|
|
83
|
+
const image = useImage('https://picsum.photos/1000/800', { maxWidth: 800 });
|
|
84
|
+
|
|
85
|
+
<Image
|
|
86
|
+
source="https://picsum.photos/400"
|
|
87
|
+
placeholder="blurhash:/L6PZfSi_.AyE_3t7t7R**0o#DgR4"
|
|
88
|
+
contentFit="cover"
|
|
89
|
+
transition={200}
|
|
90
|
+
style={{ width: 200, height: 200 }}
|
|
91
|
+
/>;
|
|
92
|
+
{image && <Image source={image} style={{ width: 200, height: 160 }} />}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Vue
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import { Image, useImage } from '@symbiote-native/image/vue';
|
|
100
|
+
|
|
101
|
+
const image = useImage('https://picsum.photos/1000/800', { maxWidth: 800 });
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<Image
|
|
106
|
+
source="https://picsum.photos/400"
|
|
107
|
+
placeholder="blurhash:/L6PZfSi_.AyE_3t7t7R**0o#DgR4"
|
|
108
|
+
contentFit="cover"
|
|
109
|
+
:transition="200"
|
|
110
|
+
:style="{ width: 200, height: 200 }"
|
|
111
|
+
/>
|
|
112
|
+
<Image v-if="image" :source="image" :style="{ width: 200, height: 160 }" />
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`image` is a shallow ref, so a template reads it unwrapped and a script reads `image.value`.
|
|
117
|
+
|
|
118
|
+
### Angular
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { Component } from '@angular/core';
|
|
122
|
+
import { ExpoImage, injectImage } from '@symbiote-native/image/angular';
|
|
123
|
+
|
|
124
|
+
@Component({
|
|
125
|
+
standalone: true,
|
|
126
|
+
imports: [ExpoImage],
|
|
127
|
+
template: `
|
|
128
|
+
<ExpoImage
|
|
129
|
+
source="https://picsum.photos/400"
|
|
130
|
+
placeholder="blurhash:/L6PZfSi_.AyE_3t7t7R**0o#DgR4"
|
|
131
|
+
contentFit="cover"
|
|
132
|
+
[transition]="200"
|
|
133
|
+
[style]="{ width: 200, height: 200 }"
|
|
134
|
+
/>
|
|
135
|
+
@if (image(); as ref) {
|
|
136
|
+
<ExpoImage [source]="ref" [style]="{ width: 200, height: 160 }" />
|
|
137
|
+
}
|
|
138
|
+
`,
|
|
139
|
+
})
|
|
140
|
+
export class Photo {
|
|
141
|
+
readonly image = injectImage(() => 'https://picsum.photos/1000/800', () => ({ maxWidth: 800 }));
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The selector is `ExpoImage`, because `Image` is already a tag of the adapter. `image` is a signal.
|
|
146
|
+
|
|
147
|
+
### Svelte
|
|
148
|
+
|
|
149
|
+
```svelte
|
|
150
|
+
<script lang="ts">
|
|
151
|
+
import { Image, useImage } from '@symbiote-native/image/svelte';
|
|
152
|
+
|
|
153
|
+
const image = useImage(() => 'https://picsum.photos/1000/800', () => ({ maxWidth: 800 }));
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<Image
|
|
157
|
+
source="https://picsum.photos/400"
|
|
158
|
+
placeholder="blurhash:/L6PZfSi_.AyE_3t7t7R**0o#DgR4"
|
|
159
|
+
contentFit="cover"
|
|
160
|
+
transition={200}
|
|
161
|
+
style={{ width: 200, height: 200 }}
|
|
162
|
+
/>
|
|
163
|
+
{#if image.current}
|
|
164
|
+
<Image source={image.current} style={{ width: 200, height: 160 }} />
|
|
165
|
+
{/if}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Solid
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { Show } from 'solid-js';
|
|
172
|
+
import { Image, useImage } from '@symbiote-native/image/solid';
|
|
173
|
+
|
|
174
|
+
const image = useImage(() => 'https://picsum.photos/1000/800', () => ({ maxWidth: 800 }));
|
|
175
|
+
|
|
176
|
+
<Image
|
|
177
|
+
source="https://picsum.photos/400"
|
|
178
|
+
placeholder="blurhash:/L6PZfSi_.AyE_3t7t7R**0o#DgR4"
|
|
179
|
+
contentFit="cover"
|
|
180
|
+
transition={200}
|
|
181
|
+
style={{ width: 200, height: 200 }}
|
|
182
|
+
/>;
|
|
183
|
+
<Show when={image()}>{ref => <Image source={ref()} style={{ width: 200, height: 160 }} />}</Show>;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## API
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
<Image source placeholder? contentFit? contentPosition? transition? cachePolicy? recyclingKey? priority?
|
|
190
|
+
tintColor? allowDownscaling? autoplay? alt? onLoadStart? onProgress? onLoad? onError? onDisplay? />
|
|
191
|
+
<ImageBackground imageStyle? ...Image props>children paint over the image</ImageBackground>
|
|
192
|
+
|
|
193
|
+
useImage(source, options?, dependencies?): ImageRef | null // per adapter, see Shape
|
|
194
|
+
// view handle: startAnimating, stopAnimating, lockResourceAsync, unlockResourceAsync, reloadAsync
|
|
195
|
+
|
|
196
|
+
prefetchImages(urls: string | string[], options?: CachePolicy | { cachePolicy?, headers? }): Promise<boolean>
|
|
197
|
+
clearMemoryCache(): Promise<boolean> clearDiskCache(): Promise<boolean> // true on success
|
|
198
|
+
getCachePathAsync(cacheKey): Promise<string | null> // null when not cached
|
|
199
|
+
writeToCacheAsync(source: string | ImageRef, cacheKey): Promise<void>
|
|
200
|
+
readFromCacheAsync(cacheKey): Promise<ImageRef | null>
|
|
201
|
+
configureCache(config): void
|
|
202
|
+
loadImageAsync(source, options?): Promise<ImageRef>
|
|
203
|
+
generateBlurhashAsync(source: string | ImageRef, numberOfComponents: [number, number] | { width, height }): Promise<string | null>
|
|
204
|
+
generateThumbhashAsync(source: string | ImageRef): Promise<string>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The statics of upstream's `Image` class are plain functions here. Ported from upstream's `Image.tsx`,
|
|
208
|
+
`ImageBackground.tsx` and `useImage.ts`.
|
|
209
|
+
|
|
210
|
+
## Notes
|
|
211
|
+
|
|
212
|
+
- **It is not React Native's `Image`.** The default fit is `contentFit="cover"`. The old `resizeMode`,
|
|
213
|
+
`fadeDuration` and `defaultSource` props still work but log a warning once, and `resizeMode:
|
|
214
|
+
'repeat'` is not supported at all. Use `contentFit`, `transition` and `placeholder`.
|
|
215
|
+
- **Caching is on by default.** `cachePolicy` is `disk`. `memory` and `memory-disk` keep decoded
|
|
216
|
+
images in RAM, which the system purges quickly under pressure.
|
|
217
|
+
- **A new source keeps the old picture until the new one loads**, and `transition` blends them. That
|
|
218
|
+
is the point of the library, and it is why recycling lists need `recyclingKey`.
|
|
219
|
+
- **`require('./a.png')` works.** Asset numbers go through Metro's asset registry before reaching
|
|
220
|
+
native, the same as for any image view.
|
|
221
|
+
- **SVG is decoded by the system on iOS.** An elliptical arc command with more than one parameter
|
|
222
|
+
set and packed flags can draw distorted or not at all. Simplify the path if one looks wrong.
|
|
223
|
+
- **Colors go through `processColor` by hand**, so `tintColor` and the style colors accept names,
|
|
224
|
+
hex and `rgb()`.
|
|
225
|
+
- **Not ported.** The web build, the RSC render tests of upstream (snapshot tests of a server renderer) and the `expo-observe` integration.
|
|
226
|
+
- **Not ported.** The web build, upstream's RSC snapshot tests and the `expo-observe` integration.
|
|
227
|
+
|
|
228
|
+
## Common questions
|
|
229
|
+
|
|
230
|
+
- **A list shows the previous row's picture.** The view keeps the old image until the new one is
|
|
231
|
+
ready. Give each image a `recyclingKey` (the item id). A `key` instead destroys the view and
|
|
232
|
+
defeats recycling.
|
|
233
|
+
- **Memory climbs with many images.** Show thumbnails sized to the box, keep `cachePolicy="disk"`,
|
|
234
|
+
leave `allowDownscaling` on, use `useImage` with `maxWidth`, call `clearMemoryCache()` on memory
|
|
235
|
+
pressure.
|
|
236
|
+
- **Animated WebP freezes or is slow on iOS.** Apple's decoder is slow on some files. Test or
|
|
237
|
+
re-encode them, and control playback with `autoplay={false}` and `startAnimating()`.
|
|
238
|
+
- **Android `NoSuchMethodError`.** `expo-image` and `expo-modules-core` must come from matching
|
|
239
|
+
releases. This package pins both, never install either yourself.
|
|
240
|
+
- **The placeholder flickers.** Set `placeholderContentFit` to the same value as `contentFit`.
|
|
241
|
+
- **`resizeMode`, `fadeDuration`, `defaultSource`.** Still work, warn once, `repeat` is unsupported.
|
|
242
|
+
Use `contentFit`, `transition`, `placeholder`.
|
|
243
|
+
- **Angular names.** `ExpoImage` and `ExpoImageBackground`.
|
|
244
|
+
|
|
245
|
+
Sources: [Expo docs: Image](https://docs.expo.dev/versions/latest/sdk/image/),
|
|
246
|
+
[expo/expo#21211](https://github.com/expo/expo/issues/21211),
|
|
247
|
+
[expo/expo#26781](https://github.com/expo/expo/issues/26781),
|
|
248
|
+
[expo/expo#24557](https://github.com/expo/expo/issues/24557),
|
|
249
|
+
[expo/expo#25920](https://github.com/expo/expo/issues/25920),
|
|
250
|
+
[expo/expo#37229](https://github.com/expo/expo/issues/37229).
|
|
251
|
+
|
|
252
|
+
## Test it
|
|
253
|
+
|
|
254
|
+
No device is needed for the logic. The core tests (`src/core/*.test.ts`) replace `expo-modules-core`
|
|
255
|
+
(`requireNativeViewManager`, `Platform`) and assert the descriptor a render function returns. The
|
|
256
|
+
adapter tests (`src/{react,vue,solid,angular}/**/*.test.*`, `vitest`) render the real component over the
|
|
257
|
+
recording Fabric with an injected view config and assert the committed payload, and Svelte's
|
|
258
|
+
`*.smoke.test.ts` compile the `.svelte` files and mount them. Painting itself is verified on a device in
|
|
259
|
+
the six `examples/expo-*` canary apps (`examples/expo-react`, `examples/expo-vue-sfc`,
|
|
260
|
+
`examples/expo-vue-tsx`, `examples/expo-svelte`, `examples/expo-solid`, `examples/expo-angular`), see the
|
|
261
|
+
parent [README](../../README.md).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NativeViewBase } from '@symbiote-native/angular';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
import type { IStyleProp } from '@symbiote-native/engine';
|
|
4
|
+
import type { IImageStyle, IImageViewProps } from '../../core';
|
|
5
|
+
declare const IMAGE_INPUTS: readonly ["source", "placeholder", "contentFit", "placeholderContentFit", "contentPosition", "transition", "blurRadius", "tintColor", "priority", "loading", "cachePolicy", "responsivePolicy", "recyclingKey", "autoplay", "sfEffect", "onLoadStart", "onLoad", "onProgress", "onError", "onLoadEnd", "onDisplay", "defaultSource", "loadingIndicatorSource", "resizeMode", "fadeDuration", "alt", "enableLiveTextInteraction", "allowDownscaling", "decodeFormat", "useAppleWebpCodec", "enforceEarlyResizing", "preferHighDynamicRange", "draggable"];
|
|
6
|
+
export interface ExpoImage extends Partial<Pick<IImageViewProps, (typeof IMAGE_INPUTS)[number]>> {
|
|
7
|
+
}
|
|
8
|
+
/** Angular twin of `expo-image`'s `Image`, `Image` itself is a primitive tag of the adapter */
|
|
9
|
+
export declare class ExpoImage extends NativeViewBase {
|
|
10
|
+
style?: IStyleProp<IImageStyle>;
|
|
11
|
+
private readonly view;
|
|
12
|
+
readonly startAnimating: () => Promise<void>;
|
|
13
|
+
readonly stopAnimating: () => Promise<void>;
|
|
14
|
+
readonly lockResourceAsync: () => Promise<void>;
|
|
15
|
+
readonly unlockResourceAsync: () => Promise<void>;
|
|
16
|
+
readonly reloadAsync: () => Promise<void>;
|
|
17
|
+
protected readonly propNames: readonly ["source", "placeholder", "contentFit", "placeholderContentFit", "contentPosition", "transition", "blurRadius", "tintColor", "priority", "loading", "cachePolicy", "responsivePolicy", "recyclingKey", "autoplay", "sfEffect", "onLoadStart", "onLoad", "onProgress", "onError", "onLoadEnd", "onDisplay", "defaultSource", "loadingIndicatorSource", "resizeMode", "fadeDuration", "alt", "enableLiveTextInteraction", "allowDownscaling", "decodeFormat", "useAppleWebpCodec", "enforceEarlyResizing", "preferHighDynamicRange", "draggable"];
|
|
18
|
+
protected renderView(props: object): IDescriptor | null;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
36
|
+
import { DescriptorOutlet, NativeViewBase } from '@symbiote-native/angular';
|
|
37
|
+
import { createImageView } from '../../core/index.js';
|
|
38
|
+
// One list is the inputs of the component and the props it hands to the native view
|
|
39
|
+
const IMAGE_INPUTS = [
|
|
40
|
+
'source',
|
|
41
|
+
'placeholder',
|
|
42
|
+
'contentFit',
|
|
43
|
+
'placeholderContentFit',
|
|
44
|
+
'contentPosition',
|
|
45
|
+
'transition',
|
|
46
|
+
'blurRadius',
|
|
47
|
+
'tintColor',
|
|
48
|
+
'priority',
|
|
49
|
+
'loading',
|
|
50
|
+
'cachePolicy',
|
|
51
|
+
'responsivePolicy',
|
|
52
|
+
'recyclingKey',
|
|
53
|
+
'autoplay',
|
|
54
|
+
'sfEffect',
|
|
55
|
+
'onLoadStart',
|
|
56
|
+
'onLoad',
|
|
57
|
+
'onProgress',
|
|
58
|
+
'onError',
|
|
59
|
+
'onLoadEnd',
|
|
60
|
+
'onDisplay',
|
|
61
|
+
'defaultSource',
|
|
62
|
+
'loadingIndicatorSource',
|
|
63
|
+
'resizeMode',
|
|
64
|
+
'fadeDuration',
|
|
65
|
+
'alt',
|
|
66
|
+
'enableLiveTextInteraction',
|
|
67
|
+
'allowDownscaling',
|
|
68
|
+
'decodeFormat',
|
|
69
|
+
'useAppleWebpCodec',
|
|
70
|
+
'enforceEarlyResizing',
|
|
71
|
+
'preferHighDynamicRange',
|
|
72
|
+
'draggable',
|
|
73
|
+
];
|
|
74
|
+
/** Angular twin of `expo-image`'s `Image`, `Image` itself is a primitive tag of the adapter */
|
|
75
|
+
let ExpoImage = (() => {
|
|
76
|
+
let _classDecorators = [Component({
|
|
77
|
+
selector: 'ExpoImage',
|
|
78
|
+
standalone: true,
|
|
79
|
+
imports: [DescriptorOutlet],
|
|
80
|
+
inputs: [...IMAGE_INPUTS],
|
|
81
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
82
|
+
template: `@if (descriptor; as node) {
|
|
83
|
+
<symbiote-descriptor-outlet [node]="node" />
|
|
84
|
+
}`,
|
|
85
|
+
})];
|
|
86
|
+
let _classDescriptor;
|
|
87
|
+
let _classExtraInitializers = [];
|
|
88
|
+
let _classThis;
|
|
89
|
+
let _classSuper = NativeViewBase;
|
|
90
|
+
var ExpoImage = class extends _classSuper {
|
|
91
|
+
static { _classThis = this; }
|
|
92
|
+
static {
|
|
93
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
94
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
95
|
+
ExpoImage = _classThis = _classDescriptor.value;
|
|
96
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
97
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
98
|
+
}
|
|
99
|
+
view = createImageView(() => this.hostNode());
|
|
100
|
+
// The functions of the view are members of the component, a `@ViewChild` reaches them
|
|
101
|
+
startAnimating = this.view.handle.startAnimating;
|
|
102
|
+
stopAnimating = this.view.handle.stopAnimating;
|
|
103
|
+
lockResourceAsync = this.view.handle.lockResourceAsync;
|
|
104
|
+
unlockResourceAsync = this.view.handle.unlockResourceAsync;
|
|
105
|
+
reloadAsync = this.view.handle.reloadAsync;
|
|
106
|
+
propNames = IMAGE_INPUTS;
|
|
107
|
+
renderView(props) {
|
|
108
|
+
return this.view.render(props);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return ExpoImage = _classThis;
|
|
112
|
+
})();
|
|
113
|
+
export { ExpoImage };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NativeViewBase } from '@symbiote-native/angular';
|
|
2
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
3
|
+
import type { IStyleProp } from '@symbiote-native/engine';
|
|
4
|
+
import type { IImageBackgroundProps, IImageStyle } from '../../core';
|
|
5
|
+
declare const IMAGE_BACKGROUND_INPUTS: readonly ["source", "placeholder", "contentFit", "placeholderContentFit", "contentPosition", "transition", "blurRadius", "tintColor", "priority", "loading", "cachePolicy", "responsivePolicy", "recyclingKey", "autoplay", "sfEffect", "onLoadStart", "onLoad", "onProgress", "onError", "onLoadEnd", "onDisplay", "defaultSource", "loadingIndicatorSource", "resizeMode", "fadeDuration", "alt", "enableLiveTextInteraction", "allowDownscaling", "decodeFormat", "useAppleWebpCodec", "enforceEarlyResizing", "preferHighDynamicRange", "draggable", "imageStyle"];
|
|
6
|
+
export interface ExpoImageBackground extends Partial<Pick<IImageBackgroundProps, (typeof IMAGE_BACKGROUND_INPUTS)[number]>> {
|
|
7
|
+
}
|
|
8
|
+
/** Angular twin of `expo-image`'s `ImageBackground`, the content paints over the image */
|
|
9
|
+
export declare class ExpoImageBackground extends NativeViewBase {
|
|
10
|
+
style?: IStyleProp<IImageStyle>;
|
|
11
|
+
protected readonly propNames: readonly ["source", "placeholder", "contentFit", "placeholderContentFit", "contentPosition", "transition", "blurRadius", "tintColor", "priority", "loading", "cachePolicy", "responsivePolicy", "recyclingKey", "autoplay", "sfEffect", "onLoadStart", "onLoad", "onProgress", "onError", "onLoadEnd", "onDisplay", "defaultSource", "loadingIndicatorSource", "resizeMode", "fadeDuration", "alt", "enableLiveTextInteraction", "allowDownscaling", "decodeFormat", "useAppleWebpCodec", "enforceEarlyResizing", "preferHighDynamicRange", "draggable", "imageStyle"];
|
|
12
|
+
protected renderView(props: object): IDescriptor;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
36
|
+
import { DescriptorHost, NativeViewBase } from '@symbiote-native/angular';
|
|
37
|
+
import { renderImageBackground } from '../../core/index.js';
|
|
38
|
+
// One list is the inputs of the component and the props it hands to the image
|
|
39
|
+
const IMAGE_BACKGROUND_INPUTS = [
|
|
40
|
+
'source',
|
|
41
|
+
'placeholder',
|
|
42
|
+
'contentFit',
|
|
43
|
+
'placeholderContentFit',
|
|
44
|
+
'contentPosition',
|
|
45
|
+
'transition',
|
|
46
|
+
'blurRadius',
|
|
47
|
+
'tintColor',
|
|
48
|
+
'priority',
|
|
49
|
+
'loading',
|
|
50
|
+
'cachePolicy',
|
|
51
|
+
'responsivePolicy',
|
|
52
|
+
'recyclingKey',
|
|
53
|
+
'autoplay',
|
|
54
|
+
'sfEffect',
|
|
55
|
+
'onLoadStart',
|
|
56
|
+
'onLoad',
|
|
57
|
+
'onProgress',
|
|
58
|
+
'onError',
|
|
59
|
+
'onLoadEnd',
|
|
60
|
+
'onDisplay',
|
|
61
|
+
'defaultSource',
|
|
62
|
+
'loadingIndicatorSource',
|
|
63
|
+
'resizeMode',
|
|
64
|
+
'fadeDuration',
|
|
65
|
+
'alt',
|
|
66
|
+
'enableLiveTextInteraction',
|
|
67
|
+
'allowDownscaling',
|
|
68
|
+
'decodeFormat',
|
|
69
|
+
'useAppleWebpCodec',
|
|
70
|
+
'enforceEarlyResizing',
|
|
71
|
+
'preferHighDynamicRange',
|
|
72
|
+
'draggable',
|
|
73
|
+
'imageStyle',
|
|
74
|
+
];
|
|
75
|
+
/** Angular twin of `expo-image`'s `ImageBackground`, the content paints over the image */
|
|
76
|
+
let ExpoImageBackground = (() => {
|
|
77
|
+
let _classDecorators = [Component({
|
|
78
|
+
selector: 'ExpoImageBackground',
|
|
79
|
+
standalone: true,
|
|
80
|
+
imports: [DescriptorHost],
|
|
81
|
+
inputs: [...IMAGE_BACKGROUND_INPUTS],
|
|
82
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
83
|
+
template: `@if (descriptor; as node) {
|
|
84
|
+
<symbiote-descriptor-host [node]="node"
|
|
85
|
+
><ng-content
|
|
86
|
+
/></symbiote-descriptor-host>
|
|
87
|
+
}`,
|
|
88
|
+
})];
|
|
89
|
+
let _classDescriptor;
|
|
90
|
+
let _classExtraInitializers = [];
|
|
91
|
+
let _classThis;
|
|
92
|
+
let _classSuper = NativeViewBase;
|
|
93
|
+
var ExpoImageBackground = class extends _classSuper {
|
|
94
|
+
static { _classThis = this; }
|
|
95
|
+
static {
|
|
96
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
97
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
98
|
+
ExpoImageBackground = _classThis = _classDescriptor.value;
|
|
99
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
100
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
101
|
+
}
|
|
102
|
+
propNames = IMAGE_BACKGROUND_INPUTS;
|
|
103
|
+
renderView(props) {
|
|
104
|
+
return renderImageBackground(props);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return ExpoImageBackground = _classThis;
|
|
108
|
+
})();
|
|
109
|
+
export { ExpoImageBackground };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @symbiote-native/image/angular: the image view, its cache and hash functions on a shared core
|
|
2
|
+
// The selectors are `ExpoImage*`, `Image` and `ImageBackground` are tags of the adapter
|
|
3
|
+
export { ExpoImage } from './image/index.js';
|
|
4
|
+
export { ExpoImageBackground } from './image-background/index.js';
|
|
5
|
+
export { injectImage } from './inject-image.js';
|
|
6
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Signal } from '@angular/core';
|
|
2
|
+
import type { IImageLoadOptions, IImageSource, ImageRef } from '../core';
|
|
3
|
+
/**
|
|
4
|
+
* Angular twin of `useImage`, `null` until the first image has loaded
|
|
5
|
+
*
|
|
6
|
+
* It loads again when the uri of the source or a signal read by `dependencies` changes
|
|
7
|
+
*/
|
|
8
|
+
export declare function injectImage(source: () => IImageSource | string | number, options?: () => IImageLoadOptions, dependencies?: () => unknown[]): Signal<ImageRef | null>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { computed, effect, signal, untracked } from '@angular/core';
|
|
2
|
+
import { createImageRefLoader, resolveSource } from '../core/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Angular twin of `useImage`, `null` until the first image has loaded
|
|
5
|
+
*
|
|
6
|
+
* It loads again when the uri of the source or a signal read by `dependencies` changes
|
|
7
|
+
*/
|
|
8
|
+
export function injectImage(source, options = () => ({}), dependencies = () => []) {
|
|
9
|
+
const image = signal(null);
|
|
10
|
+
const key = computed(() => resolveSource(source())?.uri);
|
|
11
|
+
effect(onCleanup => {
|
|
12
|
+
// Read so the effect tracks them, the source itself is not a trigger
|
|
13
|
+
key();
|
|
14
|
+
dependencies();
|
|
15
|
+
const loader = createImageRefLoader(loaded => image.set(loaded), options);
|
|
16
|
+
untracked(() => loader.load(source()));
|
|
17
|
+
onCleanup(loader.cancel);
|
|
18
|
+
});
|
|
19
|
+
return image.asReadonly();
|
|
20
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IImageSource } from './types';
|
|
2
|
+
/** `blurhash:/<hash>/<width>/<height>` or `<hash>/<width>/<height>` */
|
|
3
|
+
export declare function resolveBlurhashString(str: string): IImageSource;
|
|
4
|
+
/** `thumbhash:/<hash>` or `<hash>` */
|
|
5
|
+
export declare function resolveThumbhashString(str: string): IImageSource;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const DEFAULT_HASH_SIZE = 16;
|
|
2
|
+
function hashToUri(type, hash) {
|
|
3
|
+
const encoded = encodeURI(hash).replace(/#/g, '%23').replace(/\?/g, '%3F');
|
|
4
|
+
return `${type}:/${encoded}`;
|
|
5
|
+
}
|
|
6
|
+
/** `blurhash:/<hash>/<width>/<height>` or `<hash>/<width>/<height>` */
|
|
7
|
+
export function resolveBlurhashString(str) {
|
|
8
|
+
const [hash = '', width = '', height = ''] = str
|
|
9
|
+
.replace(/^blurhash:\//, '')
|
|
10
|
+
.split('/');
|
|
11
|
+
return {
|
|
12
|
+
uri: hashToUri('blurhash', hash),
|
|
13
|
+
width: parseInt(width, 10) || DEFAULT_HASH_SIZE,
|
|
14
|
+
height: parseInt(height, 10) || DEFAULT_HASH_SIZE,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** `thumbhash:/<hash>` or `<hash>` */
|
|
18
|
+
export function resolveThumbhashString(str) {
|
|
19
|
+
// A slash at the start of the hash would break the uri path
|
|
20
|
+
const hash = str.replace(/^thumbhash:\//, '').replace(/\//g, '\\');
|
|
21
|
+
return { uri: hashToUri('thumbhash', hash) };
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IImageCacheConfig, IImageLoadOptions, IImagePrefetchOptions, IImageSource, ImageRef } from './types';
|
|
2
|
+
type IPrefetchOptions = IImagePrefetchOptions['cachePolicy'] | IImagePrefetchOptions;
|
|
3
|
+
/** The caching policy as a string, or with request headers as an object */
|
|
4
|
+
export declare function prefetchImages(urls: string | string[], options?: IPrefetchOptions): Promise<boolean>;
|
|
5
|
+
export declare const clearMemoryCache: () => Promise<boolean>;
|
|
6
|
+
export declare const clearDiskCache: () => Promise<boolean>;
|
|
7
|
+
export declare const getCachePathAsync: (cacheKey: string) => Promise<string | null>;
|
|
8
|
+
export declare const writeToCacheAsync: (source: string | ImageRef, cacheKey: string) => Promise<void>;
|
|
9
|
+
export declare const readFromCacheAsync: (cacheKey: string) => Promise<ImageRef | null>;
|
|
10
|
+
export declare const configureCache: (config: IImageCacheConfig) => void;
|
|
11
|
+
export declare const generateBlurhashAsync: (source: string | ImageRef, numberOfComponents: [number, number] | {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
}) => Promise<string | null>;
|
|
15
|
+
export declare const generateThumbhashAsync: (source: string | ImageRef) => Promise<string>;
|
|
16
|
+
/** Loads the image into a native `ImageRef`, the caller releases it */
|
|
17
|
+
export declare function loadImageAsync(source: IImageSource | string | number, options?: IImageLoadOptions): Promise<ImageRef>;
|
|
18
|
+
export {};
|