@pylonsync/react 0.22.22 → 0.22.25
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/dist/Image.d.ts +17 -1
- package/package.json +4 -4
- package/src/Image.test.ts +31 -0
- package/src/Image.tsx +36 -20
package/dist/Image.d.ts
CHANGED
|
@@ -33,7 +33,8 @@ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElemen
|
|
|
33
33
|
quality?: PylonImageQuality;
|
|
34
34
|
/**
|
|
35
35
|
* Override the candidate widths used in `srcset`. By default we
|
|
36
|
-
* emit
|
|
36
|
+
* emit every allowed width from 256px up to 2x of `width` (rounded
|
|
37
|
+
* down to an allowed width), plus the 1x width.
|
|
37
38
|
*/
|
|
38
39
|
widths?: number[];
|
|
39
40
|
/**
|
|
@@ -54,4 +55,19 @@ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElemen
|
|
|
54
55
|
*/
|
|
55
56
|
unoptimized?: boolean;
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* srcset widths for an image displayed at `width` CSS px.
|
|
60
|
+
*
|
|
61
|
+
* Every allowed width from MIN_CANDIDATE up to the largest one that does
|
|
62
|
+
* not exceed 2x, plus the 1x width. The browser then picks by `sizes` and
|
|
63
|
+
* device pixel ratio, so a small slot gets a small file.
|
|
64
|
+
*
|
|
65
|
+
* The 2x candidate rounds DOWN. Rounding up turned a 1200px image's 2x
|
|
66
|
+
* (2400) into a 3840px request, larger than most sources and several
|
|
67
|
+
* times the bytes.
|
|
68
|
+
*/
|
|
69
|
+
export declare function imageCandidateWidths(width: number, widths?: number[]): {
|
|
70
|
+
widths: number[];
|
|
71
|
+
oneX: number;
|
|
72
|
+
};
|
|
57
73
|
export declare function Image({ src, width, height, alt, quality, widths, sizes, priority, unoptimized, className, style, ...rest }: ImageProps): React.JSX.Element;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.22.
|
|
6
|
+
"version": "0.22.25",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"prepack": "bun run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@pylonsync/realtime": "0.22.
|
|
18
|
-
"@pylonsync/sdk": "0.22.
|
|
19
|
-
"@pylonsync/sync": "0.22.
|
|
17
|
+
"@pylonsync/realtime": "0.22.25",
|
|
18
|
+
"@pylonsync/sdk": "0.22.25",
|
|
19
|
+
"@pylonsync/sync": "0.22.25"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"react": ">=19.0.0"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { imageCandidateWidths } from "./Image";
|
|
3
|
+
|
|
4
|
+
test("2x rounds down to an allowed width instead of jumping to 3840", () => {
|
|
5
|
+
const { widths, oneX } = imageCandidateWidths(1200);
|
|
6
|
+
expect(oneX).toBe(1200);
|
|
7
|
+
expect(Math.max(...widths)).toBe(2048);
|
|
8
|
+
expect(widths).not.toContain(3840);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("offers smaller widths so `sizes` can pick a small file", () => {
|
|
12
|
+
const { widths } = imageCandidateWidths(452);
|
|
13
|
+
expect(widths).toEqual([256, 384, 640, 750, 828]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("the 1x width is always offered, even when it is above 2x of a tiny image", () => {
|
|
17
|
+
const { widths, oneX } = imageCandidateWidths(20);
|
|
18
|
+
expect(oneX).toBe(32);
|
|
19
|
+
expect(widths).toContain(32);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("very large images cap at 3840", () => {
|
|
23
|
+
const { widths, oneX } = imageCandidateWidths(5000);
|
|
24
|
+
expect(oneX).toBe(3840);
|
|
25
|
+
expect(Math.max(...widths)).toBe(3840);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("explicit widths are used as given, deduplicated and sorted", () => {
|
|
29
|
+
expect(imageCandidateWidths(500, [1080, 640, 640]).widths).toEqual([640, 1080]);
|
|
30
|
+
expect(imageCandidateWidths(500, [1080, 640]).oneX).toBe(640);
|
|
31
|
+
});
|
package/src/Image.tsx
CHANGED
|
@@ -65,7 +65,8 @@ export interface ImageProps
|
|
|
65
65
|
quality?: PylonImageQuality;
|
|
66
66
|
/**
|
|
67
67
|
* Override the candidate widths used in `srcset`. By default we
|
|
68
|
-
* emit
|
|
68
|
+
* emit every allowed width from 256px up to 2x of `width` (rounded
|
|
69
|
+
* down to an allowed width), plus the 1x width.
|
|
69
70
|
*/
|
|
70
71
|
widths?: number[];
|
|
71
72
|
/**
|
|
@@ -115,6 +116,36 @@ function optimizedUrl(src: string, width: number, quality: number): string {
|
|
|
115
116
|
return `/_pylon/image?src=${encodeURIComponent(src)}&w=${w}&q=${q}`;
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
/** Smallest ladder width offered in a default srcset. */
|
|
120
|
+
const MIN_CANDIDATE = 256;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* srcset widths for an image displayed at `width` CSS px.
|
|
124
|
+
*
|
|
125
|
+
* Every allowed width from MIN_CANDIDATE up to the largest one that does
|
|
126
|
+
* not exceed 2x, plus the 1x width. The browser then picks by `sizes` and
|
|
127
|
+
* device pixel ratio, so a small slot gets a small file.
|
|
128
|
+
*
|
|
129
|
+
* The 2x candidate rounds DOWN. Rounding up turned a 1200px image's 2x
|
|
130
|
+
* (2400) into a 3840px request, larger than most sources and several
|
|
131
|
+
* times the bytes.
|
|
132
|
+
*/
|
|
133
|
+
export function imageCandidateWidths(
|
|
134
|
+
width: number,
|
|
135
|
+
widths?: number[],
|
|
136
|
+
): { widths: number[]; oneX: number } {
|
|
137
|
+
if (widths && widths.length > 0) {
|
|
138
|
+
const list = Array.from(new Set(widths.map((w) => Math.round(w)))).sort((a, b) => a - b);
|
|
139
|
+
return { widths: list, oneX: list.find((w) => w >= width) ?? list[list.length - 1] };
|
|
140
|
+
}
|
|
141
|
+
const oneX = DEFAULT_WIDTHS.find((w) => w >= width) ?? MAX_WIDTH;
|
|
142
|
+
const twoXCap = Math.min(width * 2, MAX_WIDTH);
|
|
143
|
+
const list = DEFAULT_WIDTHS.filter(
|
|
144
|
+
(w) => w >= Math.min(MIN_CANDIDATE, oneX) && w <= Math.max(oneX, twoXCap),
|
|
145
|
+
);
|
|
146
|
+
return { widths: list, oneX };
|
|
147
|
+
}
|
|
148
|
+
|
|
118
149
|
export function Image({
|
|
119
150
|
src,
|
|
120
151
|
width,
|
|
@@ -148,25 +179,10 @@ export function Image({
|
|
|
148
179
|
);
|
|
149
180
|
}
|
|
150
181
|
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// server's allowed-widths set so we never get back a 400.
|
|
156
|
-
const candidates = (() => {
|
|
157
|
-
if (widths && widths.length > 0) {
|
|
158
|
-
return Array.from(new Set(widths.map((w) => Math.round(w)))).sort(
|
|
159
|
-
(a, b) => a - b,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
const oneX = DEFAULT_WIDTHS.find((w) => w >= width) ?? MAX_WIDTH;
|
|
163
|
-
const targetTwoX = Math.min(width * 2, MAX_WIDTH);
|
|
164
|
-
const twoX = DEFAULT_WIDTHS.find((w) => w >= targetTwoX) ?? MAX_WIDTH;
|
|
165
|
-
return Array.from(new Set([oneX, twoX])).sort((a, b) => a - b);
|
|
166
|
-
})();
|
|
167
|
-
|
|
168
|
-
const baseSrc = optimizedUrl(src, candidates[candidates.length - 1], quality);
|
|
169
|
-
const srcSet = candidates
|
|
182
|
+
const candidates = imageCandidateWidths(width, widths);
|
|
183
|
+
// The 1x candidate is the fallback for browsers that ignore srcset.
|
|
184
|
+
const baseSrc = optimizedUrl(src, candidates.oneX, quality);
|
|
185
|
+
const srcSet = candidates.widths
|
|
170
186
|
.map((w) => `${optimizedUrl(src, w, quality)} ${w}w`)
|
|
171
187
|
.join(", ");
|
|
172
188
|
|