@ecopages/image-processor 0.2.0-alpha.1

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.
@@ -0,0 +1,222 @@
1
+ import { DEFAULT_LAYOUT } from "./constants";
2
+ import { ImageUtils } from "./image-utils";
3
+ class LayoutAttributesManager {
4
+ static shouldIncludeWidthHeight(layout) {
5
+ return layout === "fixed";
6
+ }
7
+ static filterDimensionAttributes(props) {
8
+ const layout = props.layout || "constrained";
9
+ if (!LayoutAttributesManager.shouldIncludeWidthHeight(layout)) {
10
+ return {};
11
+ }
12
+ return {
13
+ ...props.width && { width: props.width },
14
+ ...props.height && { height: props.height }
15
+ };
16
+ }
17
+ static getEffectiveDimensions(props, variants) {
18
+ const mainVariant = variants?.[0];
19
+ const layout = props.layout || DEFAULT_LAYOUT;
20
+ if (layout === "constrained" && props.width && !props.height) {
21
+ return { width: props.width };
22
+ }
23
+ const effectiveWidth = props.width || mainVariant?.width;
24
+ let effectiveHeight = props.height || mainVariant?.height;
25
+ if (props.aspectRatio && effectiveWidth) {
26
+ const [aspectWidth, aspectHeight] = props.aspectRatio.split("/").map(Number);
27
+ effectiveHeight = Math.round(effectiveWidth * aspectHeight / aspectWidth);
28
+ }
29
+ return { width: effectiveWidth, height: effectiveHeight };
30
+ }
31
+ }
32
+ class ImageRenderer {
33
+ originalProps;
34
+ internalProps = [
35
+ "attributes",
36
+ "variants",
37
+ "layout",
38
+ "staticVariant",
39
+ "aspectRatio",
40
+ "unstyled",
41
+ "priority",
42
+ "width",
43
+ "height",
44
+ "cacheKey"
45
+ ];
46
+ generateAttributes(props) {
47
+ this.originalProps = props;
48
+ const collected = this.collectAttributes(props);
49
+ if (!collected) return null;
50
+ const { styles, width, height, ...rest } = collected;
51
+ const dimensions = LayoutAttributesManager.filterDimensionAttributes({
52
+ width,
53
+ height,
54
+ layout: props.layout
55
+ });
56
+ const htmlAttributes = this.getHtmlAttributes(props);
57
+ const generatedStyles = styles ? this.generateStyleString(styles) : "";
58
+ const userStyles = props.style || "";
59
+ return {
60
+ ...htmlAttributes,
61
+ ...rest,
62
+ ...dimensions,
63
+ style: userStyles ? `${generatedStyles};${userStyles}` : generatedStyles
64
+ };
65
+ }
66
+ getHtmlAttributes(props) {
67
+ return Object.fromEntries(
68
+ Object.entries(props).filter(
69
+ ([key]) => !this.internalProps.includes(key) && key !== "attributes" && key !== "variants"
70
+ )
71
+ );
72
+ }
73
+ generateAttributesJsx(props) {
74
+ this.originalProps = props;
75
+ const collected = this.collectAttributes(props);
76
+ if (!collected) return null;
77
+ const { styles, fetchpriority, loading, decoding, src, srcset, sizes, width, height } = collected;
78
+ const dimensions = LayoutAttributesManager.filterDimensionAttributes({
79
+ width,
80
+ height,
81
+ layout: props.layout
82
+ });
83
+ const validHtmlAttributes = Object.fromEntries(
84
+ Object.entries(props).filter(([key]) => !this.internalProps.includes(key))
85
+ );
86
+ const generatedStyles = styles ? this.createCamelCaseKeysOnStyle(styles) : {};
87
+ const userStyles = typeof props.style === "object" ? props.style : {};
88
+ return {
89
+ fetchPriority: fetchpriority,
90
+ loading,
91
+ decoding,
92
+ src,
93
+ srcSet: srcset,
94
+ sizes,
95
+ ...dimensions,
96
+ ...validHtmlAttributes,
97
+ style: { ...generatedStyles, ...userStyles }
98
+ };
99
+ }
100
+ renderToString({ attributes, variants, ...rest }) {
101
+ this.originalProps = { attributes, variants, ...rest };
102
+ const derivedAttributes = this.generateAttributes(this.originalProps);
103
+ if (!derivedAttributes) return "";
104
+ const stringifiedAttributes = this.stringifyAttributes(derivedAttributes);
105
+ return `<img ${stringifiedAttributes} />`;
106
+ }
107
+ collectAttributes(props) {
108
+ const { variants, priority, layout = DEFAULT_LAYOUT, unstyled, staticVariant } = props;
109
+ const priorityAttributes = this.getPriorityAttributes(priority);
110
+ if (!variants || variants.length === 0) {
111
+ return this.handleNoVariants(props, priorityAttributes);
112
+ }
113
+ const mainVariant = this.getMainVariant(variants, staticVariant);
114
+ if (!mainVariant) return null;
115
+ const dimensions = this.calculateEffectiveDimensions(props, mainVariant);
116
+ const styles = this.calculateStyles({
117
+ dimensions,
118
+ layout,
119
+ attributes: props.attributes,
120
+ unstyled
121
+ });
122
+ const imageAttributes = this.buildImageAttributes(mainVariant, dimensions, props, priorityAttributes, styles);
123
+ return imageAttributes;
124
+ }
125
+ getPriorityAttributes(priority) {
126
+ return {
127
+ loading: priority ? "eager" : "lazy",
128
+ fetchpriority: priority ? "high" : "auto",
129
+ decoding: priority ? "auto" : "async"
130
+ };
131
+ }
132
+ getMainVariant(variants, staticVariant) {
133
+ if (staticVariant) {
134
+ return variants.find((v) => v.label === staticVariant) || null;
135
+ }
136
+ return variants.sort((a, b) => b.width - a.width)[0] || null;
137
+ }
138
+ calculateEffectiveDimensions(props, variant) {
139
+ return LayoutAttributesManager.getEffectiveDimensions(props, [variant]);
140
+ }
141
+ calculateStyles({
142
+ dimensions,
143
+ layout,
144
+ unstyled,
145
+ attributes
146
+ }) {
147
+ if (unstyled) return [];
148
+ return ImageUtils.generateLayoutStyles({
149
+ ...dimensions,
150
+ layout,
151
+ aspectRatio: this.originalProps?.aspectRatio,
152
+ attributes
153
+ });
154
+ }
155
+ buildImageAttributes(variant, dimensions, props, priorityAttributes, styles) {
156
+ const { staticVariant, attributes } = props;
157
+ const useResponsiveImage = !staticVariant;
158
+ return {
159
+ ...dimensions,
160
+ ...priorityAttributes,
161
+ src: variant.src,
162
+ ...useResponsiveImage ? {
163
+ srcset: attributes.srcset,
164
+ sizes: attributes.sizes
165
+ } : {},
166
+ styles
167
+ };
168
+ }
169
+ handleNoVariants(props, priorityAttributes) {
170
+ const { attributes, width, height, layout, unstyled } = props;
171
+ const dimensions = {
172
+ width,
173
+ height
174
+ };
175
+ const styles = this.calculateStyles({
176
+ dimensions,
177
+ layout: layout || DEFAULT_LAYOUT,
178
+ attributes,
179
+ unstyled
180
+ });
181
+ return {
182
+ ...priorityAttributes,
183
+ ...dimensions,
184
+ src: attributes.src,
185
+ styles
186
+ };
187
+ }
188
+ stringifyAttributes(attributes) {
189
+ const attributePairs = [];
190
+ for (const [key, value] of Object.entries(attributes)) {
191
+ if (value == null || value === "") continue;
192
+ if (typeof value === "boolean") {
193
+ if (value) attributePairs.push(key);
194
+ } else {
195
+ attributePairs.push(`${key}="${value}"`);
196
+ }
197
+ }
198
+ return attributePairs.join(" ");
199
+ }
200
+ generateStyleString(styles) {
201
+ const styleMap = /* @__PURE__ */ new Map();
202
+ for (const [key, value] of styles) {
203
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
204
+ styleMap.set(camelKey, value);
205
+ }
206
+ return Array.from(styleMap.entries()).map(([key, value]) => {
207
+ const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
208
+ return `${cssKey}:${value}`;
209
+ }).join(";");
210
+ }
211
+ createCamelCaseKeysOnStyle(styles) {
212
+ return Object.fromEntries(
213
+ styles.map(([key, value]) => [key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()), value])
214
+ );
215
+ }
216
+ }
217
+ const renderer = new ImageRenderer();
218
+ export {
219
+ ImageRenderer,
220
+ LayoutAttributesManager,
221
+ renderer
222
+ };
@@ -0,0 +1,427 @@
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();
@@ -0,0 +1,45 @@
1
+ import type { EcoImageProps } from './image-renderer';
2
+ /**
3
+ * ImageUtils
4
+ * This class contains utility methods for working with images
5
+ * It contains methods for generating srcset, sizes and styles attributes for the image element
6
+ */
7
+ export declare class ImageUtils {
8
+ private static readonly BREAKPOINTS;
9
+ private static readonly VIEWPORT_SIZES;
10
+ static readonly DEFAULT_LAYOUT: import("./image-renderer").ImageLayout;
11
+ /**
12
+ * Generates a srcset string from processed image variants using relative paths
13
+ * @param {ImageVariant[]} variants - Array of processed image variants
14
+ * @returns {string} srcset attribute string
15
+ * @private
16
+ */
17
+ static generateSrcset(variants: Array<{
18
+ width: number;
19
+ src: string;
20
+ }>): string;
21
+ /**
22
+ * Generates sizes attribute based on image variants.
23
+ * Sizes are generated based on the variant widths and breakpoints.
24
+ * Here we use a smart approach to generate sizes based on the variant widths.
25
+ * We start with the largest variant and set a min-width condition for its width.
26
+ * Then we add conditions for each variant based on the viewport width.
27
+ * Finally, we add a catch-all for the smallest screens.
28
+ * This approach ensures that the browser will select the correct image variant based on the viewport width.
29
+ * @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#resolution_switching_different_sizes
30
+ * @param {ImageVariant[]} variants - Array of processed image variants
31
+ * @returns {string} sizes attribute string
32
+ * @private
33
+ */
34
+ static generateSizes(variants: Array<{
35
+ width: number;
36
+ }>): string;
37
+ /**
38
+ * Generates a styles string based on image layout and config
39
+ * @param {ImageLayout} layout - Image layout type
40
+ * @param {LayoutAttributes} config - Image layout configuration
41
+ * @returns {string} styles string
42
+ * @private
43
+ */
44
+ static generateLayoutStyles(config: Pick<EcoImageProps, 'layout' | 'width' | 'height' | 'aspectRatio' | 'attributes'>): [string, string][];
45
+ }