@massimo-cassandro/minimo 1.14.4 → 1.14.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.14.4",
3
+ "version": "1.14.6",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -5,10 +5,13 @@ import { classnames } from './classnames.js';
5
5
  /**
6
6
  * @typedef {Object} PictureSizeEntry
7
7
  * @property {number} [from] - Breakpoint (px) the size applies from (`width >=` media condition); omitted in the (single) mobile-first default entry.
8
- * @property {number | [number, number]} size - Rendered width (px, height computed with `ratio`) or explicit `[width, height]` pair (px).
8
+ * @property {number | [number, number]} size - Rendered width (px, height computed with `ratio`, if set) or explicit `[width, height]` pair (px).
9
9
  */
10
10
 
11
11
  // TODO aggiungere compatibilità con immagini Unsplash basate su imgix
12
+ // TODO parametri viewer: le convenzioni attuali (`bb=WxH`, `bb=Wx` per il resize width-only,
13
+ // `q`, `fd`, `f`) sono legate al viewer interno; prevedere un meccanismo di astrazione
14
+ // per altri servizi con parametri diversi (es. imgix/Unsplash)
12
15
 
13
16
  /**
14
17
  * Creates a `picture` domBuilder item for an image rendered through the viewer.
@@ -20,7 +23,9 @@ import { classnames } from './classnames.js';
20
23
  * `(width >= …)` media condition (range syntax).
21
24
  *
22
25
  * @example
23
- * // fixed-width thumb (250px, 16:9 ratio, 1x and 2x candidates)
26
+ * // fixed-width thumb (250px, 1x and 2x candidates); with no `ratio` nor explicit
27
+ * // heights, the viewer gets a width-only resize (bb=250x) and the `img` has no
28
+ * // `height` attribute
24
29
  * buildPictureTag({ baseSrc: 'https://img-viewer.example.com/abc123', alt: '…' });
25
30
  * // sizes="250px", srcset="… 250w, … 500w"
26
31
  *
@@ -55,15 +60,25 @@ import { classnames } from './classnames.js';
55
60
  * // dev environment (no avif) and above-the-fold img (no loading="lazy")
56
61
  * buildPictureTag({ baseSrc, devMode: true, lazy: false });
57
62
  *
63
+ * @example
64
+ * // onLoad callback: reveal the image once it has finished loading
65
+ * buildPictureTag({
66
+ * baseSrc,
67
+ * imgExtraClass: 'img-loading',
68
+ * onLoad: img => img.classList.remove('img-loading'),
69
+ * });
70
+ *
58
71
  * @param {Object} args
59
72
  * @param {string} args.baseSrc - Image base URL (viewer endpoint); its query params are preserved.
60
73
  * @param {(number | [number, number] | PictureSizeEntry)[]} [args.sizes=[250]] - Rendered image sizes, in any order. Each entry is a `{from, size}` object (see {@link PictureSizeEntry}); a bare number or `[width, height]` pair is a shorthand for the mobile-first default entry (the one without `from`), expected exactly once.
61
- * @param {number} [args.ratio=16/9] - Default aspect ratio (width / height, as in the CSS `aspect-ratio` property) used to compute the height of numeric sizes.
74
+ * @param {number|null} [args.ratio=null] - Default aspect ratio (width / height, as in the CSS `aspect-ratio` property) used to compute the height of numeric sizes. When null, no height is computed: the height-related output (`height` attribute, height in the `bb` param) is omitted and the viewer preserves the image's own ratio.
62
75
  * @param {number} [args.dpr=2] - Maximum pixel density (integer): `srcset` candidates are generated for every density from 1 to `dpr`.
63
- * @param {[string, string|number][]} [args.img_params=[['q','60'],['fd',1]]] - Query params (`[name, value]` pairs) added to every generated URL.
76
+ * @param {[string, string|number][]} [args.img_params=[['q','60']]] - Query params (`[name, value]` pairs) added to every generated URL.
64
77
  * @param {string[]} [args.formats=['avif','webp','pjpg']] - Image formats: the last one is used for the `img` fallback element, the others for the `source` elements.
65
78
  * @param {boolean} [args.devMode=false] - When true, `avif` is excluded from `formats` (not supported by the local dev environment).
66
79
  * @param {boolean} [args.lazy=true] - When true, the `img` element gets `loading="lazy"`.
80
+ * @param {'high'|'low'|null} [args.fetchpriority=null] - `fetchpriority` attribute for the `img` element: use `high` for the LCP/above-the-fold image (typically with `lazy: false`). When null the attribute is omitted (browser default `auto`).
81
+ * @param {((img: HTMLImageElement) => void) | null} [args.onLoad=null] - Called once the image has finished loading, receiving the `img` element (called immediately if the image is already complete).
67
82
  * @param {boolean} [args.condition=true] - When false, the function returns `null` without building anything.
68
83
  * @param {boolean} [args.legacyMediaSyntax=false] - When true, the `sizes` media conditions use `(min-width: …)` instead of the range syntax `(width >= …)`, for older browsers (pre Chrome/Edge 104, Firefox 102, Safari 16.4).
69
84
  * @param {string|null} [args.pictureExtraClass=null] - Extra class(es) for the `picture` element.
@@ -76,12 +91,14 @@ import { classnames } from './classnames.js';
76
91
  export function buildPictureTag({
77
92
  baseSrc,
78
93
  sizes = [250],
79
- ratio = 16/9,
94
+ ratio = null,
80
95
  dpr = 2,
81
96
  img_params = [ ['q', '60'] ],
82
97
  formats = ['avif', 'webp', 'pjpg'],
83
98
  devMode = false,
84
99
  lazy = true,
100
+ fetchpriority = null,
101
+ onLoad = null,
85
102
  condition = true,
86
103
  legacyMediaSyntax = false,
87
104
  pictureExtraClass = null,
@@ -107,14 +124,14 @@ export function buildPictureTag({
107
124
  const dprList = Array.from({length: dpr}, (_, i) => i + 1);
108
125
 
109
126
  // entry di `sizes` normalizzate in oggetti {from, width, height}
110
- // (per le size numeriche l'altezza è calcolata con `ratio`),
127
+ // (per le size numeriche l'altezza è calcolata con `ratio`, se presente, altrimenti è null),
111
128
  // ordinate per breakpoint, con il default (from = null) per primo
112
129
  const sizeEntries = sizes.map(entry => {
113
130
  if(typeof entry === 'object' && !Array.isArray(entry)) {
114
- const [width, height] = Array.isArray(entry.size)? entry.size : [entry.size, Math.floor(entry.size / ratio)];
131
+ const [width, height] = Array.isArray(entry.size)? entry.size : [entry.size, ratio == null? null : Math.floor(entry.size / ratio)];
115
132
  return { from: entry.from ?? null, width, height };
116
133
  }
117
- const [width, height] = Array.isArray(entry)? entry : [entry, Math.floor(entry / ratio)];
134
+ const [width, height] = Array.isArray(entry)? entry : [entry, ratio == null? null : Math.floor(entry / ratio)];
118
135
  return { from: null, width, height };
119
136
  }).sort((a, b) => (a.from ?? 0) - (b.from ?? 0));
120
137
 
@@ -133,7 +150,7 @@ export function buildPictureTag({
133
150
  // dimensioni dei candidati srcset (una per ogni densità da 1 a `dpr`),
134
151
  // senza duplicati (per larghezza), in ordine crescente
135
152
  ,srcsetSizes = sizeEntries
136
- .flatMap(({width, height}) => dprList.map(d => /** @type {[number, number]} */ ([width * d, height * d])))
153
+ .flatMap(({width, height}) => dprList.map(d => /** @type {[number, number|null]} */ ([width * d, height == null? null : height * d])))
137
154
  .filter((pair, idx, arr) => arr.findIndex(p => p[0] === pair[0]) === idx)
138
155
  .sort((a, b) => a[0] - b[0])
139
156
 
@@ -184,7 +201,8 @@ export function buildPictureTag({
184
201
  searchParams.set('f', fmt);
185
202
 
186
203
  srcsetSizes.forEach(([w, h]) => {
187
- searchParams.set('bb', `${w}x${h}`);
204
+ // TODO senza altezza `bb=<w>x` vale per questo viewer: prevedere altre convenzioni (es. imgix)
205
+ searchParams.set('bb', `${w}x${h ?? ''}`);
188
206
  base_url.search = searchParams.toString();
189
207
  srcsetArray.push(`${base_url.toString()} ${w}w`);
190
208
  });
@@ -202,8 +220,18 @@ export function buildPictureTag({
202
220
  width: imgWidth,
203
221
  height: imgHeight,
204
222
  loading: lazy? 'lazy' : null,
223
+ decoding: 'async',
224
+ fetchpriority: fetchpriority,
205
225
  ...imgExtraAttrs
206
226
  },
227
+ callback: onLoad == null? undefined : el => {
228
+ const img = /** @type {HTMLImageElement} */ (el);
229
+ if(img.complete) {
230
+ onLoad(img);
231
+ } else {
232
+ img.addEventListener('load', () => onLoad(img), {once: true});
233
+ }
234
+ },
207
235
  // TODO classe popover, se attivato
208
236
  // callback: options.addPopover
209
237
  // ? el => el.addEventListener('click', () => zoomPopoverEl.togglePopover())