@massimo-cassandro/minimo 1.14.5 → 1.15.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.
package/index.js CHANGED
@@ -11,6 +11,7 @@ export { escapeHTML, unescapeHTML } from './src/utilities/escape-html.js';
11
11
  export { generatePwd } from './src/utilities/generate-pwd.js';
12
12
  export { getCookie, setCookie } from './src/utilities/cookies.js';
13
13
  export { linkedDates } from './src/utilities/linked-dates.js';
14
+ export { parseFileSize } from './src/utilities/parse-file-size.js';
14
15
  export { randomId } from './src/utilities/random-id.js';
15
16
  export { relativeDate } from './src/utilities/relative-date.js';
16
17
  export { sentenceCase } from './src/utilities/sentence-case.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.14.5",
3
+ "version": "1.15.0",
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,24 @@ 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`).
67
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).
68
82
  * @param {boolean} [args.condition=true] - When false, the function returns `null` without building anything.
69
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).
@@ -77,12 +91,13 @@ import { classnames } from './classnames.js';
77
91
  export function buildPictureTag({
78
92
  baseSrc,
79
93
  sizes = [250],
80
- ratio = 16/9,
94
+ ratio = null,
81
95
  dpr = 2,
82
96
  img_params = [ ['q', '60'] ],
83
97
  formats = ['avif', 'webp', 'pjpg'],
84
98
  devMode = false,
85
99
  lazy = true,
100
+ fetchpriority = null,
86
101
  onLoad = null,
87
102
  condition = true,
88
103
  legacyMediaSyntax = false,
@@ -109,14 +124,14 @@ export function buildPictureTag({
109
124
  const dprList = Array.from({length: dpr}, (_, i) => i + 1);
110
125
 
111
126
  // entry di `sizes` normalizzate in oggetti {from, width, height}
112
- // (per le size numeriche l'altezza è calcolata con `ratio`),
127
+ // (per le size numeriche l'altezza è calcolata con `ratio`, se presente, altrimenti è null),
113
128
  // ordinate per breakpoint, con il default (from = null) per primo
114
129
  const sizeEntries = sizes.map(entry => {
115
130
  if(typeof entry === 'object' && !Array.isArray(entry)) {
116
- 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)];
117
132
  return { from: entry.from ?? null, width, height };
118
133
  }
119
- 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)];
120
135
  return { from: null, width, height };
121
136
  }).sort((a, b) => (a.from ?? 0) - (b.from ?? 0));
122
137
 
@@ -135,7 +150,7 @@ export function buildPictureTag({
135
150
  // dimensioni dei candidati srcset (una per ogni densità da 1 a `dpr`),
136
151
  // senza duplicati (per larghezza), in ordine crescente
137
152
  ,srcsetSizes = sizeEntries
138
- .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])))
139
154
  .filter((pair, idx, arr) => arr.findIndex(p => p[0] === pair[0]) === idx)
140
155
  .sort((a, b) => a[0] - b[0])
141
156
 
@@ -186,7 +201,8 @@ export function buildPictureTag({
186
201
  searchParams.set('f', fmt);
187
202
 
188
203
  srcsetSizes.forEach(([w, h]) => {
189
- 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 ?? ''}`);
190
206
  base_url.search = searchParams.toString();
191
207
  srcsetArray.push(`${base_url.toString()} ${w}w`);
192
208
  });
@@ -204,6 +220,8 @@ export function buildPictureTag({
204
220
  width: imgWidth,
205
221
  height: imgHeight,
206
222
  loading: lazy? 'lazy' : null,
223
+ decoding: 'async',
224
+ fetchpriority: fetchpriority,
207
225
  ...imgExtraAttrs
208
226
  },
209
227
  callback: onLoad == null? undefined : el => {
@@ -0,0 +1,32 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Converts a file size in bytes to a human readable string,
5
+ * using the most appropriate unit (bytes, KB, MB or GB).
6
+ *
7
+ * @example
8
+ * parseFileSize(1536); // "1.5 KB"
9
+ * parseFileSize(10485760); // "10 MB"
10
+ *
11
+ * @param {number} bytes - file size in bytes
12
+ * @param {number} [decimals=1] - max number of decimal digits
13
+ * @returns {string} formatted size (e.g. "1.5 MB")
14
+ */
15
+ export function parseFileSize(bytes, decimals = 1) {
16
+
17
+ if(typeof bytes !== 'number' || isNaN(bytes) || bytes < 0) {
18
+ return '';
19
+ }
20
+
21
+ const units = ['bytes', 'KB', 'MB', 'GB'],
22
+ k = 1024;
23
+
24
+ const exp = bytes === 0
25
+ ? 0
26
+ : Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
27
+
28
+ const value = bytes / Math.pow(k, exp);
29
+
30
+ // \u202f = narrow no-break space
31
+ return `${parseFloat(value.toFixed(decimals))}\u202f${units[exp]}`;
32
+ }