jazz-tools 0.16.5 → 0.17.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 (170) hide show
  1. package/.svelte-kit/__package__/index.d.ts +1 -0
  2. package/.svelte-kit/__package__/index.d.ts.map +1 -1
  3. package/.svelte-kit/__package__/index.js +1 -0
  4. package/.svelte-kit/__package__/media/image.svelte +131 -0
  5. package/.svelte-kit/__package__/media/image.svelte.d.ts +10 -0
  6. package/.svelte-kit/__package__/media/image.svelte.d.ts.map +1 -0
  7. package/.svelte-kit/__package__/media/index.d.ts +2 -0
  8. package/.svelte-kit/__package__/media/index.d.ts.map +1 -0
  9. package/.svelte-kit/__package__/media/index.js +1 -0
  10. package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts +2 -0
  11. package/.svelte-kit/__package__/tests/media/image.svelte.test.d.ts.map +1 -0
  12. package/.svelte-kit/__package__/tests/media/image.svelte.test.js +430 -0
  13. package/.svelte-kit/__package__/tests/testUtils.d.ts +11 -0
  14. package/.svelte-kit/__package__/tests/testUtils.d.ts.map +1 -0
  15. package/.svelte-kit/__package__/tests/testUtils.js +17 -0
  16. package/.svelte-kit/__package__/tests/types.d.ts +3 -0
  17. package/.turbo/turbo-build.log +44 -48
  18. package/CHANGELOG.md +28 -0
  19. package/dist/{chunk-H3BIFFQG.js → chunk-2SH44VLX.js} +35 -40
  20. package/dist/chunk-2SH44VLX.js.map +1 -0
  21. package/dist/index.js +1 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/inspector/{custom-element-TUXKXSZU.js → custom-element-I7L56H6B.js} +3 -5
  24. package/dist/inspector/{custom-element-TUXKXSZU.js.map → custom-element-I7L56H6B.js.map} +1 -1
  25. package/dist/inspector/index.js +2 -4
  26. package/dist/inspector/index.js.map +1 -1
  27. package/dist/inspector/register-custom-element.js +1 -1
  28. package/dist/inspector/viewer/co-plain-text-view.d.ts +1 -1
  29. package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
  30. package/dist/inspector/viewer/role-display.d.ts.map +1 -1
  31. package/dist/media/chunk-BBSS3NEY.js +211 -0
  32. package/dist/media/chunk-BBSS3NEY.js.map +1 -0
  33. package/dist/media/create-image.d.ts +48 -0
  34. package/dist/media/create-image.d.ts.map +1 -0
  35. package/dist/media/create-image.test.d.ts +2 -0
  36. package/dist/media/create-image.test.d.ts.map +1 -0
  37. package/dist/media/index.browser.d.ts +15 -0
  38. package/dist/media/index.browser.d.ts.map +1 -0
  39. package/dist/media/index.browser.js +113 -0
  40. package/dist/media/index.browser.js.map +1 -0
  41. package/dist/media/index.d.ts +53 -0
  42. package/dist/media/index.d.ts.map +1 -0
  43. package/dist/media/index.js +13 -0
  44. package/dist/media/index.js.map +1 -0
  45. package/dist/media/index.native.d.ts +17 -0
  46. package/dist/media/index.native.d.ts.map +1 -0
  47. package/dist/media/index.native.js +126 -0
  48. package/dist/media/index.native.js.map +1 -0
  49. package/dist/media/utils.d.ts +17 -0
  50. package/dist/media/utils.d.ts.map +1 -0
  51. package/dist/media/utils.test.d.ts +2 -0
  52. package/dist/media/utils.test.d.ts.map +1 -0
  53. package/dist/react/index.d.ts +1 -2
  54. package/dist/react/index.d.ts.map +1 -1
  55. package/dist/react/index.js +176 -59
  56. package/dist/react/index.js.map +1 -1
  57. package/dist/react/media/image.d.ts +62 -0
  58. package/dist/react/media/image.d.ts.map +1 -0
  59. package/dist/react/ssr.d.ts.map +1 -1
  60. package/dist/react/ssr.js.map +1 -1
  61. package/dist/react/tests/media/image.test.d.ts +2 -0
  62. package/dist/react/tests/media/image.test.d.ts.map +1 -0
  63. package/dist/react/tests/testUtils.d.ts.map +1 -1
  64. package/dist/react-core/auth/PassphraseAuth.d.ts +1 -1
  65. package/dist/react-core/auth/PassphraseAuth.d.ts.map +1 -1
  66. package/dist/react-core/index.js +1 -3
  67. package/dist/react-core/index.js.map +1 -1
  68. package/dist/react-core/tests/testUtils.d.ts.map +1 -1
  69. package/dist/react-core/tests/useDemoAuth.test.d.ts +2 -0
  70. package/dist/react-core/tests/useDemoAuth.test.d.ts.map +1 -0
  71. package/dist/react-native-core/index.d.ts +1 -1
  72. package/dist/react-native-core/index.d.ts.map +1 -1
  73. package/dist/react-native-core/index.js +84 -66
  74. package/dist/react-native-core/index.js.map +1 -1
  75. package/dist/react-native-core/media/image.d.ts +93 -0
  76. package/dist/react-native-core/media/image.d.ts.map +1 -0
  77. package/dist/react-native-core/testing.d.ts +2 -0
  78. package/dist/react-native-core/testing.d.ts.map +1 -0
  79. package/dist/svelte/index.d.ts +1 -0
  80. package/dist/svelte/index.d.ts.map +1 -1
  81. package/dist/svelte/index.js +1 -0
  82. package/dist/svelte/media/image.svelte +131 -0
  83. package/dist/svelte/media/image.svelte.d.ts +10 -0
  84. package/dist/svelte/media/image.svelte.d.ts.map +1 -0
  85. package/dist/svelte/media/index.d.ts +2 -0
  86. package/dist/svelte/media/index.d.ts.map +1 -0
  87. package/dist/svelte/media/index.js +1 -0
  88. package/dist/svelte/tests/media/image.svelte.test.d.ts +2 -0
  89. package/dist/svelte/tests/media/image.svelte.test.d.ts.map +1 -0
  90. package/dist/svelte/tests/media/image.svelte.test.js +430 -0
  91. package/dist/svelte/tests/testUtils.d.ts +11 -0
  92. package/dist/svelte/tests/testUtils.d.ts.map +1 -0
  93. package/dist/svelte/tests/testUtils.js +17 -0
  94. package/dist/svelte/tests/types.d.ts +3 -0
  95. package/dist/testing.js +1 -1
  96. package/dist/testing.js.map +1 -1
  97. package/dist/tools/coValues/coFeed.d.ts +15 -0
  98. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  99. package/dist/tools/coValues/deepLoading.d.ts +10 -10
  100. package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
  101. package/dist/tools/coValues/extensions/imageDef.d.ts +3 -9
  102. package/dist/tools/coValues/extensions/imageDef.d.ts.map +1 -1
  103. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +1 -0
  104. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  105. package/dist/tools/index.d.ts +1 -1
  106. package/dist/tools/index.d.ts.map +1 -1
  107. package/dist/tools/testing.d.ts.map +1 -1
  108. package/package.json +12 -12
  109. package/src/inspector/viewer/co-plain-text-view.tsx +1 -5
  110. package/src/inspector/viewer/co-stream-view.tsx +1 -1
  111. package/src/inspector/viewer/role-display.tsx +4 -1
  112. package/src/{browser-media-images/index.test.browser.ts → media/create-image.test.ts} +146 -24
  113. package/src/media/create-image.ts +180 -0
  114. package/src/media/index.browser.ts +150 -0
  115. package/src/media/index.native.ts +153 -0
  116. package/src/media/index.ts +61 -0
  117. package/src/media/utils.test.ts +327 -0
  118. package/src/media/utils.ts +202 -0
  119. package/src/react/index.ts +1 -2
  120. package/src/react/media/image.tsx +210 -0
  121. package/src/react/ssr.ts +1 -3
  122. package/src/react/tests/media/image.test.tsx +588 -0
  123. package/src/react/tests/testUtils.tsx +2 -10
  124. package/src/react-core/auth/PassphraseAuth.tsx +1 -5
  125. package/src/react-core/tests/testUtils.tsx +2 -10
  126. package/src/react-native-core/index.ts +1 -1
  127. package/src/react-native-core/media/image.tsx +159 -0
  128. package/src/svelte/index.ts +1 -0
  129. package/src/svelte/media/image.svelte +131 -0
  130. package/src/svelte/media/index.ts +1 -0
  131. package/src/svelte/tests/media/image.svelte.test.ts +583 -0
  132. package/src/svelte/tests/testUtils.ts +33 -0
  133. package/src/svelte/tests/types.d.ts +3 -0
  134. package/src/tools/coValues/coFeed.ts +40 -7
  135. package/src/tools/coValues/deepLoading.ts +46 -32
  136. package/src/tools/coValues/extensions/imageDef.ts +3 -49
  137. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +6 -0
  138. package/src/tools/index.ts +0 -1
  139. package/src/tools/testing.ts +3 -1
  140. package/src/tools/tests/coList.test.ts +1 -1
  141. package/src/tools/tests/coMap.record.test-d.ts +105 -0
  142. package/src/tools/tests/coMap.record.test.ts +48 -2
  143. package/src/tools/tests/coMap.test-d.ts +50 -0
  144. package/src/tools/tests/coOptional.test.ts +3 -1
  145. package/tsconfig.json +1 -0
  146. package/tsup.config.ts +4 -9
  147. package/vitest.config.ts +14 -21
  148. package/dist/browser-media-images/index.d.ts +0 -9
  149. package/dist/browser-media-images/index.d.ts.map +0 -1
  150. package/dist/browser-media-images/index.js +0 -72
  151. package/dist/browser-media-images/index.js.map +0 -1
  152. package/dist/browser-media-images/index.test.browser.d.ts +0 -2
  153. package/dist/browser-media-images/index.test.browser.d.ts.map +0 -1
  154. package/dist/chunk-H3BIFFQG.js.map +0 -1
  155. package/dist/react/media.d.ts +0 -24
  156. package/dist/react/media.d.ts.map +0 -1
  157. package/dist/react-native-core/media.d.ts +0 -24
  158. package/dist/react-native-core/media.d.ts.map +0 -1
  159. package/dist/react-native-media-images/index.d.ts +0 -7
  160. package/dist/react-native-media-images/index.d.ts.map +0 -1
  161. package/dist/react-native-media-images/index.js +0 -177
  162. package/dist/react-native-media-images/index.js.map +0 -1
  163. package/dist/tools/tests/imageDef.test.d.ts +0 -2
  164. package/dist/tools/tests/imageDef.test.d.ts.map +0 -1
  165. package/src/browser-media-images/index.ts +0 -131
  166. package/src/react/media.tsx +0 -74
  167. package/src/react/scratch.tsx +0 -50
  168. package/src/react-native-core/media.tsx +0 -79
  169. package/src/react-native-media-images/index.ts +0 -238
  170. package/src/tools/tests/imageDef.test.ts +0 -278
@@ -0,0 +1,159 @@
1
+ import { FileStream, ImageDefinition } from "jazz-tools";
2
+ import { highestResAvailable } from "jazz-tools/media";
3
+ import { forwardRef, useEffect, useMemo, useState } from "react";
4
+ import { Image as RNImage, ImageProps as RNImageProps } from "react-native";
5
+ import { useCoState } from "../hooks.js";
6
+
7
+ export type ImageProps = Omit<RNImageProps, "width" | "height" | "source"> & {
8
+ /** The ID of the ImageDefinition to display */
9
+ imageId: string;
10
+ /**
11
+ * Width of the image. Can be a number or "original" to use the original image width.
12
+ * When set to "original", the component will calculate the appropriate height to maintain aspect ratio.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // Fixed width, auto-calculated height
17
+ * <Image imageId="123" width={600} />
18
+ *
19
+ * // Original width
20
+ * <Image imageId="123" width="original" />
21
+ * ```
22
+ */
23
+ width?: number | "original";
24
+ /**
25
+ * Height of the image. Can be a number or "original" to use the original image height.
26
+ * When set to "original", the component will calculate the appropriate width to maintain aspect ratio.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // Fixed height, auto-calculated width
31
+ * <Image imageId="123" height={400} />
32
+ *
33
+ * // Original height
34
+ * <Image imageId="123" height="original" />
35
+ * ```
36
+ */
37
+ height?: number | "original";
38
+ };
39
+
40
+ /**
41
+ * A React Native Image component that integrates with Jazz's ImageDefinition system.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * import { Image } from "jazz-tools/react-native";
46
+ * import { StyleSheet } from "react-native";
47
+ *
48
+ * function ProfilePicture({ imageId }) {
49
+ * return (
50
+ * <Image
51
+ * imageId={imageId}
52
+ * style={styles.profilePic}
53
+ * width={100}
54
+ * height={100}
55
+ * resizeMode="cover"
56
+ * />
57
+ * );
58
+ * }
59
+ *
60
+ * const styles = StyleSheet.create({
61
+ * profilePic: {
62
+ * borderRadius: 50,
63
+ * }
64
+ * });
65
+ * ```
66
+ */
67
+ export const Image = forwardRef<RNImage, ImageProps>(function Image(
68
+ { imageId, width, height, ...props },
69
+ ref,
70
+ ) {
71
+ const image = useCoState(ImageDefinition, imageId);
72
+ const [src, setSrc] = useState<string | undefined>(image?.placeholderDataURL);
73
+
74
+ const dimensions: { width: number | undefined; height: number | undefined } =
75
+ useMemo(() => {
76
+ const originalWidth = image?.originalSize?.[0];
77
+ const originalHeight = image?.originalSize?.[1];
78
+
79
+ // Both width and height are "original"
80
+ if (width === "original" && height === "original") {
81
+ return { width: originalWidth, height: originalHeight };
82
+ }
83
+
84
+ // Width is "original", height is a number
85
+ if (width === "original" && typeof height === "number") {
86
+ if (originalWidth && originalHeight) {
87
+ return {
88
+ width: Math.round((height * originalWidth) / originalHeight),
89
+ height,
90
+ };
91
+ }
92
+ return { width: undefined, height };
93
+ }
94
+
95
+ // Height is "original", width is a number
96
+ if (height === "original" && typeof width === "number") {
97
+ if (originalWidth && originalHeight) {
98
+ return {
99
+ width,
100
+ height: Math.round((width * originalHeight) / originalWidth),
101
+ };
102
+ }
103
+ return { width, height: undefined };
104
+ }
105
+
106
+ // In all other cases, use the property value:
107
+ return {
108
+ width: width === "original" ? originalWidth : width,
109
+ height: height === "original" ? originalHeight : height,
110
+ };
111
+ }, [image?.originalSize, width, height]);
112
+
113
+ useEffect(() => {
114
+ if (!image) return;
115
+
116
+ let lastBestImage: FileStream | string | undefined =
117
+ image.placeholderDataURL;
118
+
119
+ const unsub = image.subscribe({}, (update) => {
120
+ if (lastBestImage === undefined && update.placeholderDataURL) {
121
+ setSrc(update.placeholderDataURL);
122
+ lastBestImage = update.placeholderDataURL;
123
+ }
124
+
125
+ const bestImage = highestResAvailable(
126
+ update,
127
+ dimensions.width || dimensions.height || 9999,
128
+ dimensions.height || dimensions.width || 9999,
129
+ );
130
+
131
+ if (!bestImage) return;
132
+
133
+ if (lastBestImage === bestImage.image) return;
134
+
135
+ const url = bestImage.image.asBase64({ dataURL: true });
136
+
137
+ if (url) {
138
+ setSrc(url);
139
+ lastBestImage = bestImage.image;
140
+ }
141
+ });
142
+
143
+ return unsub;
144
+ }, [image]);
145
+
146
+ if (!image) {
147
+ return null;
148
+ }
149
+
150
+ return (
151
+ <RNImage
152
+ ref={ref}
153
+ source={{ uri: src }}
154
+ width={dimensions.width}
155
+ height={dimensions.height}
156
+ {...props}
157
+ />
158
+ );
159
+ });
@@ -3,3 +3,4 @@ export * from "./auth/index.js";
3
3
  export * from "./jazz.svelte.js";
4
4
  export * from "./jazz.class.svelte.js";
5
5
  export { useIsAuthenticated } from "./auth/useIsAuthenticated.svelte.js";
6
+ export * from "./media/index.js";
@@ -0,0 +1,131 @@
1
+ <script lang="ts">
2
+ import { ImageDefinition } from "jazz-tools";
3
+ import { highestResAvailable } from "jazz-tools/media";
4
+ import { onDestroy } from "svelte";
5
+ import type { HTMLImgAttributes } from "svelte/elements";
6
+ import { CoState } from "../jazz.class.svelte";
7
+
8
+ interface ImageProps extends Omit<HTMLImgAttributes, "width" | "height"> {
9
+ imageId: string;
10
+ width?: number | "original";
11
+ height?: number | "original";
12
+ }
13
+
14
+ const { imageId, width, height, ...rest }: ImageProps = $props();
15
+
16
+ const imageState = new CoState(ImageDefinition, imageId);
17
+ let lastBestImage: [string, string] | null = null;
18
+
19
+ /**
20
+ * For lazy loading, we use the browser's strategy for images with loading="lazy".
21
+ * We use an empty image, and when the browser triggers the load event, we load the best available image.
22
+ * On page loading, if the image url is already in browser's cache, the load event is triggered immediately.
23
+ * This is why we need to use a different blob url for every image.
24
+ */
25
+ let waitingLazyLoading = $state(rest.loading === "lazy");
26
+ const lazyPlaceholder = $derived.by(() =>
27
+ waitingLazyLoading ? URL.createObjectURL(emptyPixelBlob) : undefined,
28
+ );
29
+
30
+ const dimensions = $derived.by<{
31
+ width: number | undefined;
32
+ height: number | undefined;
33
+ }>(() => {
34
+ const originalWidth = imageState.current?.originalSize?.[0];
35
+ const originalHeight = imageState.current?.originalSize?.[1];
36
+
37
+ // Both width and height are "original"
38
+ if (width === "original" && height === "original") {
39
+ return { width: originalWidth, height: originalHeight };
40
+ }
41
+
42
+ // Width is "original", height is a number
43
+ if (width === "original" && typeof height === "number") {
44
+ if (originalWidth && originalHeight) {
45
+ return {
46
+ width: Math.round((height * originalWidth) / originalHeight),
47
+ height,
48
+ };
49
+ }
50
+ return { width: undefined, height };
51
+ }
52
+
53
+ // Height is "original", width is a number
54
+ if (height === "original" && typeof width === "number") {
55
+ if (originalWidth && originalHeight) {
56
+ return {
57
+ width,
58
+ height: Math.round((width * originalHeight) / originalWidth),
59
+ };
60
+ }
61
+ return { width, height: undefined };
62
+ }
63
+
64
+ // In all other cases, use the property value:
65
+ return {
66
+ width: width === "original" ? originalWidth : width,
67
+ height: height === "original" ? originalHeight : height,
68
+ };
69
+ });
70
+
71
+ const src = $derived.by(() => {
72
+ if (waitingLazyLoading) {
73
+ return lazyPlaceholder;
74
+ }
75
+
76
+ const image = imageState.current;
77
+ if (!image) return undefined;
78
+
79
+ const bestImage = highestResAvailable(
80
+ image,
81
+ dimensions.width || dimensions.height || 9999,
82
+ dimensions.height || dimensions.width || 9999,
83
+ );
84
+
85
+ if (!bestImage) return image.placeholderDataURL;
86
+ if (lastBestImage?.[0] === bestImage.image.id) return lastBestImage?.[1];
87
+
88
+ const blob = bestImage.image.toBlob();
89
+
90
+ if (blob) {
91
+ const url = URL.createObjectURL(blob);
92
+ revokeObjectURL(lastBestImage?.[1]);
93
+ lastBestImage = [bestImage.image.id, url];
94
+ return url;
95
+ }
96
+
97
+ return image.placeholderDataURL;
98
+ });
99
+
100
+ // Cleanup object URL on component destroy
101
+ onDestroy(() => {
102
+ revokeObjectURL(lastBestImage?.[1]);
103
+ });
104
+
105
+ function revokeObjectURL(url: string | undefined) {
106
+ if (url && url.startsWith("blob:")) {
107
+ URL.revokeObjectURL(url);
108
+ }
109
+ }
110
+
111
+ const emptyPixelBlob = new Blob(
112
+ [
113
+ Uint8Array.from(
114
+ atob(
115
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
116
+ ),
117
+ (c) => c.charCodeAt(0),
118
+ ),
119
+ ],
120
+ { type: "image/png" },
121
+ );
122
+ </script>
123
+
124
+ <img
125
+ {src}
126
+ width={dimensions.width}
127
+ height={dimensions.height}
128
+ alt={rest.alt}
129
+ onload={() => {waitingLazyLoading = false}}
130
+ {...rest}
131
+ />
@@ -0,0 +1 @@
1
+ export { default as Image } from "./image.svelte";