@ecopages/image-processor 0.2.0-alpha.4 → 0.2.0-alpha.41
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/README.md +41 -151
- package/package.json +7 -7
- package/src/bun-plugins.d.ts +2 -2
- package/src/bun-plugins.js +1 -1
- package/src/component/html.d.ts +1 -1
- package/src/component/html.js +1 -1
- package/src/component/react.d.ts +1 -1
- package/src/component/react.js +1 -1
- package/src/constants.d.ts +1 -1
- package/src/image-plugins.d.ts +2 -2
- package/src/image-plugins.js +1 -1
- package/src/image-processor.d.ts +2 -2
- package/src/image-processor.js +3 -3
- package/src/image-renderer.d.ts +1 -1
- package/src/image-renderer.js +2 -2
- package/src/image-utils.d.ts +2 -2
- package/src/image-utils.js +1 -1
- package/src/index.d.ts +3 -3
- package/src/index.js +3 -3
- package/src/plugin.d.ts +23 -5
- package/src/plugin.js +132 -21
- package/CHANGELOG.md +0 -23
- package/src/bun-plugins.ts +0 -59
- package/src/component/html.ts +0 -15
- package/src/component/react.ts +0 -20
- package/src/constants.ts +0 -7
- package/src/image-plugins.ts +0 -59
- package/src/image-processor.ts +0 -201
- package/src/image-renderer.ts +0 -427
- package/src/image-utils.ts +0 -128
- package/src/index.ts +0 -3
- package/src/plugin.ts +0 -336
- package/src/types.ts +0 -45
- package/src/utils.ts +0 -30
package/src/image-renderer.ts
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ImageRenderer
|
|
3
|
-
* @module
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { DEFAULT_LAYOUT } from './constants';
|
|
7
|
-
import { ImageUtils } from './image-utils';
|
|
8
|
-
import type { ImageSpecifications, ImageVariant } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Image layout options
|
|
12
|
-
*/
|
|
13
|
-
export type ImageLayout = 'fixed' | 'constrained' | 'full-width';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* ImageProps
|
|
17
|
-
* This interface represents the properties that can be passed to the image element
|
|
18
|
-
*/
|
|
19
|
-
export type EcoImageProps = ImageSpecifications & {
|
|
20
|
-
/**
|
|
21
|
-
* width of the image as defined in the component
|
|
22
|
-
*/
|
|
23
|
-
width?: number;
|
|
24
|
-
/**
|
|
25
|
-
* height of the image as defined in the component
|
|
26
|
-
*/
|
|
27
|
-
height?: number;
|
|
28
|
-
/**
|
|
29
|
-
* The alt text for the image
|
|
30
|
-
*/
|
|
31
|
-
aspectRatio?: string;
|
|
32
|
-
/**
|
|
33
|
-
* If true, the image will be loaded eagerly
|
|
34
|
-
*/
|
|
35
|
-
priority?: boolean;
|
|
36
|
-
/**
|
|
37
|
-
* The layout of the image
|
|
38
|
-
* @default "constrained"
|
|
39
|
-
*/
|
|
40
|
-
layout?: ImageLayout;
|
|
41
|
-
/**
|
|
42
|
-
* If true, the image will not have any styles applied to it
|
|
43
|
-
*/
|
|
44
|
-
unstyled?: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* Specifies a fixed image variant from the configuration.
|
|
47
|
-
* This should match one of the variant labels defined in your image optimization config.
|
|
48
|
-
* When set, the image will use this specific variant instead of responsive sizes.
|
|
49
|
-
*/
|
|
50
|
-
staticVariant?: string;
|
|
51
|
-
/**
|
|
52
|
-
* Optional additional attributes to be added to the image element
|
|
53
|
-
*/
|
|
54
|
-
[additionalAttributes: string]: any;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* CollectedAttributes
|
|
59
|
-
* This interface represents the attributes that are collected by the image renderer
|
|
60
|
-
* These attributes are used to generate the attributes for the image element
|
|
61
|
-
*/
|
|
62
|
-
export interface CollectedAttributes {
|
|
63
|
-
width?: number;
|
|
64
|
-
height?: number;
|
|
65
|
-
loading: HTMLImageElement['loading'];
|
|
66
|
-
fetchpriority: HTMLImageElement['fetchPriority'];
|
|
67
|
-
decoding: HTMLImageElement['decoding'];
|
|
68
|
-
src: string;
|
|
69
|
-
srcset?: string;
|
|
70
|
-
sizes?: string;
|
|
71
|
-
styles: [string, string][];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* GenerateAttributesResult
|
|
76
|
-
* This interface represents the result of the generateAttributes method
|
|
77
|
-
*/
|
|
78
|
-
export interface GenerateAttributesResult {
|
|
79
|
-
fetchpriority: HTMLImageElement['fetchPriority'];
|
|
80
|
-
loading: HTMLImageElement['loading'];
|
|
81
|
-
decoding: HTMLImageElement['decoding'];
|
|
82
|
-
src: string;
|
|
83
|
-
srcset?: string;
|
|
84
|
-
sizes?: string;
|
|
85
|
-
width?: number;
|
|
86
|
-
height?: number;
|
|
87
|
-
style?: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* GenerateAttributesResultJsx
|
|
92
|
-
* This interface represents the result of the generateAttributes method as JSX
|
|
93
|
-
*/
|
|
94
|
-
export interface GenerateAttributesResultJsx {
|
|
95
|
-
fetchPriority: HTMLImageElement['fetchPriority'];
|
|
96
|
-
loading: HTMLImageElement['loading'];
|
|
97
|
-
decoding: HTMLImageElement['decoding'];
|
|
98
|
-
src: string;
|
|
99
|
-
srcSet?: string;
|
|
100
|
-
sizes?: string;
|
|
101
|
-
width?: number;
|
|
102
|
-
height?: number;
|
|
103
|
-
style?: React.CSSProperties;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* IImageRenderer
|
|
108
|
-
* This interface represents the image renderer in charge of generating the attributes for the image element
|
|
109
|
-
*/
|
|
110
|
-
export interface IImageRenderer {
|
|
111
|
-
/**
|
|
112
|
-
* This method generates the attributes for the image element based on the provided props
|
|
113
|
-
* This is the main method that should be used to generate the attributes for the image element
|
|
114
|
-
* @param props
|
|
115
|
-
*/
|
|
116
|
-
generateAttributes(props: EcoImageProps): GenerateAttributesResult | null;
|
|
117
|
-
/**
|
|
118
|
-
* This method generates the attributes for the image element based on the provided props as JSX
|
|
119
|
-
* @param props
|
|
120
|
-
*/
|
|
121
|
-
generateAttributesJsx(props: EcoImageProps): GenerateAttributesResultJsx | null;
|
|
122
|
-
/**
|
|
123
|
-
* This method generates the image element based on the provided props as a string
|
|
124
|
-
* @param props
|
|
125
|
-
*/
|
|
126
|
-
renderToString(props: EcoImageProps): string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export class LayoutAttributesManager {
|
|
130
|
-
static shouldIncludeWidthHeight(layout: ImageLayout): boolean {
|
|
131
|
-
return layout === 'fixed';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
static filterDimensionAttributes(
|
|
135
|
-
props: Pick<EcoImageProps, 'width' | 'height' | 'layout'>,
|
|
136
|
-
): Pick<EcoImageProps, 'width' | 'height'> {
|
|
137
|
-
const layout = props.layout || 'constrained';
|
|
138
|
-
|
|
139
|
-
if (!LayoutAttributesManager.shouldIncludeWidthHeight(layout)) {
|
|
140
|
-
return {};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
...(props.width && { width: props.width }),
|
|
145
|
-
...(props.height && { height: props.height }),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
static getEffectiveDimensions(
|
|
150
|
-
props: EcoImageProps,
|
|
151
|
-
variants?: Array<{ width: number; height: number }>,
|
|
152
|
-
): { width?: number; height?: number } {
|
|
153
|
-
const mainVariant = variants?.[0];
|
|
154
|
-
const layout = props.layout || DEFAULT_LAYOUT;
|
|
155
|
-
|
|
156
|
-
if (layout === 'constrained' && props.width && !props.height) {
|
|
157
|
-
return { width: props.width };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const effectiveWidth = props.width || mainVariant?.width;
|
|
161
|
-
let effectiveHeight = props.height || mainVariant?.height;
|
|
162
|
-
|
|
163
|
-
if (props.aspectRatio && effectiveWidth) {
|
|
164
|
-
const [aspectWidth, aspectHeight] = props.aspectRatio.split('/').map(Number);
|
|
165
|
-
effectiveHeight = Math.round((effectiveWidth * aspectHeight) / aspectWidth);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return { width: effectiveWidth, height: effectiveHeight };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* ImageRenderer
|
|
174
|
-
* This class is responsible for generating the attributes for the image element
|
|
175
|
-
*/
|
|
176
|
-
export class ImageRenderer implements IImageRenderer {
|
|
177
|
-
private originalProps?: EcoImageProps;
|
|
178
|
-
|
|
179
|
-
private readonly internalProps = [
|
|
180
|
-
'attributes',
|
|
181
|
-
'variants',
|
|
182
|
-
'layout',
|
|
183
|
-
'staticVariant',
|
|
184
|
-
'aspectRatio',
|
|
185
|
-
'unstyled',
|
|
186
|
-
'priority',
|
|
187
|
-
'width',
|
|
188
|
-
'height',
|
|
189
|
-
'cacheKey',
|
|
190
|
-
];
|
|
191
|
-
|
|
192
|
-
generateAttributes(props: EcoImageProps): GenerateAttributesResult | null {
|
|
193
|
-
this.originalProps = props;
|
|
194
|
-
const collected = this.collectAttributes(props);
|
|
195
|
-
if (!collected) return null;
|
|
196
|
-
|
|
197
|
-
const { styles, width, height, ...rest } = collected;
|
|
198
|
-
const dimensions = LayoutAttributesManager.filterDimensionAttributes({
|
|
199
|
-
width,
|
|
200
|
-
height,
|
|
201
|
-
layout: props.layout,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const htmlAttributes = this.getHtmlAttributes(props);
|
|
205
|
-
const generatedStyles = styles ? this.generateStyleString(styles) : '';
|
|
206
|
-
const userStyles = props.style || '';
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
...htmlAttributes,
|
|
210
|
-
...rest,
|
|
211
|
-
...dimensions,
|
|
212
|
-
style: userStyles ? `${generatedStyles};${userStyles}` : generatedStyles,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private getHtmlAttributes(props: EcoImageProps): Record<string, any> {
|
|
217
|
-
return Object.fromEntries(
|
|
218
|
-
Object.entries(props).filter(
|
|
219
|
-
([key]) => !this.internalProps.includes(key) && key !== 'attributes' && key !== 'variants',
|
|
220
|
-
),
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
generateAttributesJsx(props: EcoImageProps): GenerateAttributesResultJsx | null {
|
|
225
|
-
this.originalProps = props;
|
|
226
|
-
const collected = this.collectAttributes(props);
|
|
227
|
-
if (!collected) return null;
|
|
228
|
-
|
|
229
|
-
const { styles, fetchpriority, loading, decoding, src, srcset, sizes, width, height } = collected;
|
|
230
|
-
|
|
231
|
-
const dimensions = LayoutAttributesManager.filterDimensionAttributes({
|
|
232
|
-
width,
|
|
233
|
-
height,
|
|
234
|
-
layout: props.layout,
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const validHtmlAttributes = Object.fromEntries(
|
|
238
|
-
Object.entries(props).filter(([key]) => !this.internalProps.includes(key)),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const generatedStyles = styles ? this.createCamelCaseKeysOnStyle(styles) : {};
|
|
242
|
-
const userStyles = typeof props.style === 'object' ? props.style : {};
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
fetchPriority: fetchpriority,
|
|
246
|
-
loading,
|
|
247
|
-
decoding,
|
|
248
|
-
src,
|
|
249
|
-
srcSet: srcset,
|
|
250
|
-
sizes,
|
|
251
|
-
...dimensions,
|
|
252
|
-
...validHtmlAttributes,
|
|
253
|
-
style: { ...generatedStyles, ...userStyles },
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
renderToString({ attributes, variants, ...rest }: EcoImageProps): string {
|
|
258
|
-
this.originalProps = { attributes, variants, ...rest };
|
|
259
|
-
const derivedAttributes = this.generateAttributes(this.originalProps);
|
|
260
|
-
|
|
261
|
-
if (!derivedAttributes) return '';
|
|
262
|
-
|
|
263
|
-
const stringifiedAttributes = this.stringifyAttributes(derivedAttributes);
|
|
264
|
-
|
|
265
|
-
return `<img ${stringifiedAttributes} />`;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
private collectAttributes(props: EcoImageProps): CollectedAttributes | null {
|
|
269
|
-
const { variants, priority, layout = DEFAULT_LAYOUT, unstyled, staticVariant } = props;
|
|
270
|
-
|
|
271
|
-
const priorityAttributes = this.getPriorityAttributes(priority);
|
|
272
|
-
|
|
273
|
-
if (!variants || variants.length === 0) {
|
|
274
|
-
return this.handleNoVariants(props, priorityAttributes);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const mainVariant = this.getMainVariant(variants, staticVariant);
|
|
278
|
-
if (!mainVariant) return null;
|
|
279
|
-
|
|
280
|
-
const dimensions = this.calculateEffectiveDimensions(props, mainVariant);
|
|
281
|
-
const styles = this.calculateStyles({
|
|
282
|
-
dimensions,
|
|
283
|
-
layout,
|
|
284
|
-
attributes: props.attributes,
|
|
285
|
-
unstyled,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const imageAttributes = this.buildImageAttributes(mainVariant, dimensions, props, priorityAttributes, styles);
|
|
289
|
-
|
|
290
|
-
return imageAttributes;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private getPriorityAttributes(
|
|
294
|
-
priority?: boolean,
|
|
295
|
-
): Pick<GenerateAttributesResult, 'loading' | 'fetchpriority' | 'decoding'> {
|
|
296
|
-
return {
|
|
297
|
-
loading: priority ? 'eager' : 'lazy',
|
|
298
|
-
fetchpriority: priority ? 'high' : 'auto',
|
|
299
|
-
decoding: priority ? 'auto' : 'async',
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private getMainVariant(variants: ImageVariant[], staticVariant?: string): ImageVariant | null {
|
|
304
|
-
if (staticVariant) {
|
|
305
|
-
return variants.find((v) => v.label === staticVariant) || null;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return variants.sort((a, b) => b.width - a.width)[0] || null;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private calculateEffectiveDimensions(
|
|
312
|
-
props: EcoImageProps,
|
|
313
|
-
variant: ImageVariant,
|
|
314
|
-
): { width?: number; height?: number } {
|
|
315
|
-
return LayoutAttributesManager.getEffectiveDimensions(props, [variant]);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private calculateStyles({
|
|
319
|
-
dimensions,
|
|
320
|
-
layout,
|
|
321
|
-
unstyled,
|
|
322
|
-
attributes,
|
|
323
|
-
}: {
|
|
324
|
-
dimensions: { width?: number; height?: number };
|
|
325
|
-
layout: ImageLayout;
|
|
326
|
-
attributes: ImageSpecifications['attributes'];
|
|
327
|
-
unstyled?: boolean;
|
|
328
|
-
}): [string, string][] {
|
|
329
|
-
if (unstyled) return [];
|
|
330
|
-
|
|
331
|
-
return ImageUtils.generateLayoutStyles({
|
|
332
|
-
...dimensions,
|
|
333
|
-
layout,
|
|
334
|
-
aspectRatio: this.originalProps?.aspectRatio,
|
|
335
|
-
attributes,
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private buildImageAttributes(
|
|
340
|
-
variant: ImageVariant,
|
|
341
|
-
dimensions: { width?: number; height?: number },
|
|
342
|
-
props: EcoImageProps,
|
|
343
|
-
priorityAttributes: Pick<GenerateAttributesResult, 'loading' | 'fetchpriority' | 'decoding'>,
|
|
344
|
-
styles: [string, string][],
|
|
345
|
-
): CollectedAttributes {
|
|
346
|
-
const { staticVariant, attributes } = props;
|
|
347
|
-
const useResponsiveImage = !staticVariant;
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
...dimensions,
|
|
351
|
-
...priorityAttributes,
|
|
352
|
-
src: variant.src,
|
|
353
|
-
...(useResponsiveImage
|
|
354
|
-
? {
|
|
355
|
-
srcset: attributes.srcset,
|
|
356
|
-
sizes: attributes.sizes,
|
|
357
|
-
}
|
|
358
|
-
: {}),
|
|
359
|
-
styles,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private handleNoVariants(
|
|
364
|
-
props: EcoImageProps,
|
|
365
|
-
priorityAttributes: Pick<GenerateAttributesResult, 'loading' | 'fetchpriority' | 'decoding'>,
|
|
366
|
-
): CollectedAttributes {
|
|
367
|
-
const { attributes, width, height, layout, unstyled } = props;
|
|
368
|
-
|
|
369
|
-
const dimensions = {
|
|
370
|
-
width,
|
|
371
|
-
height,
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const styles = this.calculateStyles({
|
|
375
|
-
dimensions,
|
|
376
|
-
layout: layout || DEFAULT_LAYOUT,
|
|
377
|
-
attributes,
|
|
378
|
-
unstyled,
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
return {
|
|
382
|
-
...priorityAttributes,
|
|
383
|
-
...dimensions,
|
|
384
|
-
src: attributes.src,
|
|
385
|
-
styles,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
private stringifyAttributes(attributes: GenerateAttributesResult): string {
|
|
389
|
-
const attributePairs: string[] = [];
|
|
390
|
-
|
|
391
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
392
|
-
if (value == null || value === '') continue;
|
|
393
|
-
|
|
394
|
-
if (typeof value === 'boolean') {
|
|
395
|
-
if (value) attributePairs.push(key);
|
|
396
|
-
} else {
|
|
397
|
-
attributePairs.push(`${key}="${value}"`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return attributePairs.join(' ');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
private generateStyleString(styles: [string, string][]): string {
|
|
405
|
-
const styleMap = new Map<string, string>();
|
|
406
|
-
|
|
407
|
-
for (const [key, value] of styles) {
|
|
408
|
-
const camelKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
409
|
-
styleMap.set(camelKey, value);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return Array.from(styleMap.entries())
|
|
413
|
-
.map(([key, value]) => {
|
|
414
|
-
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
415
|
-
return `${cssKey}:${value}`;
|
|
416
|
-
})
|
|
417
|
-
.join(';');
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private createCamelCaseKeysOnStyle(styles: [string, string][]): Record<string, string> {
|
|
421
|
-
return Object.fromEntries(
|
|
422
|
-
styles.map(([key, value]) => [key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()), value]),
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export const renderer = new ImageRenderer();
|
package/src/image-utils.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_LAYOUT } from './constants';
|
|
2
|
-
import type { EcoImageProps } from './image-renderer';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* ImageUtils
|
|
6
|
-
* This class contains utility methods for working with images
|
|
7
|
-
* It contains methods for generating srcset, sizes and styles attributes for the image element
|
|
8
|
-
*/
|
|
9
|
-
export class ImageUtils {
|
|
10
|
-
private static readonly BREAKPOINTS = {
|
|
11
|
-
desktop: 1024,
|
|
12
|
-
tablet: 768,
|
|
13
|
-
} as const;
|
|
14
|
-
|
|
15
|
-
private static readonly VIEWPORT_SIZES = {
|
|
16
|
-
desktop: '70vw',
|
|
17
|
-
tablet: '80vw',
|
|
18
|
-
mobile: '100vw',
|
|
19
|
-
} as const;
|
|
20
|
-
|
|
21
|
-
static readonly DEFAULT_LAYOUT = DEFAULT_LAYOUT;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generates a srcset string from processed image variants using relative paths
|
|
25
|
-
* @param {ImageVariant[]} variants - Array of processed image variants
|
|
26
|
-
* @returns {string} srcset attribute string
|
|
27
|
-
* @private
|
|
28
|
-
*/
|
|
29
|
-
static generateSrcset(variants: Array<{ width: number; src: string }>): string {
|
|
30
|
-
return variants
|
|
31
|
-
.sort((a, b) => b.width - a.width)
|
|
32
|
-
.map(({ src, width }) => `${src} ${width}w`)
|
|
33
|
-
.join(', ');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Generates sizes attribute based on image variants.
|
|
38
|
-
* Sizes are generated based on the variant widths and breakpoints.
|
|
39
|
-
* Here we use a smart approach to generate sizes based on the variant widths.
|
|
40
|
-
* We start with the largest variant and set a min-width condition for its width.
|
|
41
|
-
* Then we add conditions for each variant based on the viewport width.
|
|
42
|
-
* Finally, we add a catch-all for the smallest screens.
|
|
43
|
-
* This approach ensures that the browser will select the correct image variant based on the viewport width.
|
|
44
|
-
* @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#resolution_switching_different_sizes
|
|
45
|
-
* @param {ImageVariant[]} variants - Array of processed image variants
|
|
46
|
-
* @returns {string} sizes attribute string
|
|
47
|
-
* @private
|
|
48
|
-
*/
|
|
49
|
-
static generateSizes(variants: Array<{ width: number }>): string {
|
|
50
|
-
if (!variants?.length) return '';
|
|
51
|
-
|
|
52
|
-
const sortedVariants = [...variants].sort((a, b) => b.width - a.width);
|
|
53
|
-
const [largest, ...rest] = sortedVariants;
|
|
54
|
-
|
|
55
|
-
const conditions = [
|
|
56
|
-
`(min-width: ${largest.width}px) ${largest.width}px`,
|
|
57
|
-
...rest
|
|
58
|
-
.map((variant) => {
|
|
59
|
-
if (variant.width >= ImageUtils.BREAKPOINTS.desktop) {
|
|
60
|
-
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.desktop}`;
|
|
61
|
-
}
|
|
62
|
-
if (variant.width >= ImageUtils.BREAKPOINTS.tablet) {
|
|
63
|
-
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.tablet}`;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
})
|
|
67
|
-
.filter(Boolean),
|
|
68
|
-
ImageUtils.VIEWPORT_SIZES.mobile,
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
return conditions.join(', ');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Generates a styles string based on image layout and config
|
|
76
|
-
* @param {ImageLayout} layout - Image layout type
|
|
77
|
-
* @param {LayoutAttributes} config - Image layout configuration
|
|
78
|
-
* @returns {string} styles string
|
|
79
|
-
* @private
|
|
80
|
-
*/
|
|
81
|
-
static generateLayoutStyles(
|
|
82
|
-
config: Pick<EcoImageProps, 'layout' | 'width' | 'height' | 'aspectRatio' | 'attributes'>,
|
|
83
|
-
): [string, string][] {
|
|
84
|
-
const layout = config.layout || ImageUtils.DEFAULT_LAYOUT;
|
|
85
|
-
const styles: [string, string][] = [['object-fit', 'cover']];
|
|
86
|
-
|
|
87
|
-
if (config.aspectRatio) {
|
|
88
|
-
styles.push(['aspect-ratio', config.aspectRatio]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
switch (layout) {
|
|
92
|
-
case 'fixed':
|
|
93
|
-
if (config.width) {
|
|
94
|
-
styles.push(['width', `${config.width}px`], ['min-width', `${config.width}px`]);
|
|
95
|
-
}
|
|
96
|
-
if (config.height) {
|
|
97
|
-
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
|
|
101
|
-
case 'constrained':
|
|
102
|
-
styles.push(['width', '100%']);
|
|
103
|
-
|
|
104
|
-
if (config.width && config.height) {
|
|
105
|
-
styles.push(['max-width', `${config.width}px`], ['max-height', `${config.height}px`]);
|
|
106
|
-
if (!config.aspectRatio) {
|
|
107
|
-
styles.push(['aspect-ratio', `${config.width}/${config.height}`]);
|
|
108
|
-
}
|
|
109
|
-
} else if (config.height) {
|
|
110
|
-
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
111
|
-
} else if (config.width) {
|
|
112
|
-
styles.push(['max-width', `${config.width}px`]);
|
|
113
|
-
} else if (!config.aspectRatio) {
|
|
114
|
-
styles.push(['aspect-ratio', `${config.attributes.width}/${config.attributes.height}`]);
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
117
|
-
|
|
118
|
-
case 'full-width':
|
|
119
|
-
styles.push(['width', '100%']);
|
|
120
|
-
if (config.height) {
|
|
121
|
-
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return styles;
|
|
127
|
-
}
|
|
128
|
-
}
|
package/src/index.ts
DELETED