@massimo-cassandro/minimo 1.42.3 → 1.43.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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Il mio framework leggero + utilità
4
4
 
5
+ Demo: <https://massimo-cassandro.github.io/minimo/>
6
+
5
7
  ## Installazione
6
8
 
7
9
  ```bash
@@ -16,7 +16,7 @@
16
16
 
17
17
  "@app/*": ["app/*"],
18
18
 
19
- "@minimoSrc/*": ["./node_modules/@massimo-cassandro/minimo/src/*"],
19
+ "@minimoCss/*": ["./node_modules/@massimo-cassandro/minimo/src/css/*"],
20
20
  "@minimoCharts/*": ["./node_modules/@massimo-cassandro/minimo/charts/*"]
21
21
  }
22
22
  },
@@ -39,6 +39,21 @@ if [ -z "$ZSH_VERSION" ]; then
39
39
  exit 1
40
40
  fi
41
41
 
42
+ # Package manager da utilizzare per l'installazione dei pacchetti: 1 = pnpm
43
+ # (default), 2 = npm. Il valore viene usato più sotto (sezione NODE MODULES)
44
+ # per scegliere il comando di installazione.
45
+ read "PM_INPUT?Package manager da utilizzare (1 = pnpm, 2 = npm) [1]: "
46
+ PM_INPUT=${PM_INPUT:-1}
47
+ case "$PM_INPUT" in
48
+ 1) PKG_MANAGER="pnpm" ;;
49
+ 2) PKG_MANAGER="npm" ;;
50
+ *)
51
+ printf "${RED}Errore: inserire 1 (pnpm) oppure 2 (npm).${NC}\n" >&2
52
+ exit 1
53
+ ;;
54
+ esac
55
+ echo -e "${DIM}Package manager: ${PKG_MANAGER}${NC}"
56
+
42
57
  # Porta da utilizzare: l'utente inserisce un numero da 0 a 99 (default 0), che
43
58
  # viene normalizzato a due cifre e usato come suffisso delle porte 80xx e 57xx.
44
59
  # I valori sostituiscono i segnaposto [[port8000]] e [[port5700]] presenti in
@@ -65,9 +80,10 @@ echo -e "${DIM}Porte: ${PORT_8000} (server), ${PORT_5700} (webpack dev server)${
65
80
  # La colonna <pacchetto> può contenere più pacchetti separati da spazio: in tal
66
81
  # caso vengono proposti con un'unica domanda e, se confermati, installati tutti
67
82
  # (uno per uno) con la stessa modalità indicata dalla colonna <dev>.
68
- # La colonna <dev> vale "dev" se il pacchetto va installato con `npm i -D`
69
- # (devDependency), stringa vuota se va installato con `npm i -S` (dependency
70
- # vera, es. peer dependency di minimo usata a runtime).
83
+ # La colonna <dev> vale "dev" se il pacchetto va installato come
84
+ # devDependency (`npm i -D` / `pnpm add -D`), stringa vuota se va installato
85
+ # come dependency vera (`npm i -S` / `pnpm add`, es. peer dependency di minimo
86
+ # usata a runtime).
71
87
  optionalPackages=(
72
88
  "dotenv-webpack | dev | gestione variabili d'ambiente (.env) nel bundle webpack"
73
89
  "@principalstudio/html-webpack-inject-preload | dev | injection dei tag <link rel=preload> nell'html generato"
@@ -286,7 +302,7 @@ echo -e "${GREEN}Files will be installed in: '${FRONTEND_INSTALL_PATH}'${NC}"
286
302
 
287
303
 
288
304
  # package.json va creato dal template PRIMA di installare qualunque pacchetto:
289
- # se non esistesse ancora, i comandi `npm i` più sotto ne genererebbero uno
305
+ # se non esistesse ancora, i comandi di installazione più sotto ne genererebbero uno
290
306
  # "al volo" privo di scripts/metadati del template, e le dipendenze verrebbero
291
307
  # registrate lì; alla successiva install_tree su source_files/root (più sotto)
292
308
  # quel package.json risulterebbe diverso dal template e verrebbe spostato in
@@ -352,14 +368,24 @@ if [ "$DEBUG" = "TRUE" ]; then
352
368
  devDependencies=()
353
369
  fi
354
370
 
371
+ # comandi di installazione in base al package manager scelto a inizio script:
372
+ # pnpm add -D / pnpm add (dependency), npm i -D / npm i -S
373
+ if [ "$PKG_MANAGER" = "pnpm" ]; then
374
+ INSTALL_DEV_CMD=(pnpm add -D)
375
+ INSTALL_DEP_CMD=(pnpm add)
376
+ else
377
+ INSTALL_DEV_CMD=(npm i -D)
378
+ INSTALL_DEP_CMD=(npm i -S)
379
+ fi
380
+
355
381
  for pkg in "${devDependencies[@]}"; do
356
382
  echo -e "${DIM}...installing "$pkg"${NC}"
357
- npm i -D "$pkg"
383
+ "${INSTALL_DEV_CMD[@]}" "$pkg"
358
384
  done
359
385
 
360
386
  for pkg in "${dependencies[@]}"; do
361
387
  echo -e "${DIM}...installing "$pkg"${NC}"
362
- npm i -S "$pkg"
388
+ "${INSTALL_DEP_CMD[@]}" "$pkg"
363
389
  done
364
390
 
365
391
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massimo-cassandro/minimo",
3
- "version": "1.42.3",
3
+ "version": "1.43.0",
4
4
  "description": "Light css framework & utilities",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -22,6 +22,10 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
+ "workspaces": [
26
+ "demo",
27
+ "dev-tools-web-apps"
28
+ ],
25
29
  "bin": {
26
30
  "buildTokens": "dev-tools/design-tokens-builder/build-tokens.mjs",
27
31
  "checkUnresolvedProps": "dev-tools/design-tokens-builder/check-unresolved-custom-props.mjs",
@@ -48,9 +52,9 @@
48
52
  "test sideEffects tree-shaking": "node ./test/sideEffects-tree-shaking/run-test.mjs",
49
53
  "checkJS": "npx tsc --noEmit -p jsconfig.json",
50
54
  "checkJS ./src": "npx tsc --noEmit --allowJs --checkJs --target ES2023 --module ESNext --allowSyntheticDefaultImports --moduleResolution bundler declarations.d.ts types/global.d.ts src/**/*.js",
51
- "npm outdated": "echo '*** ROOT ***' && npm outdated --omit=peer && echo '*** DEMO ***' && npm --prefix ./demo outdated && echo '*** END ***'",
55
+ "npm outdated": "npm outdated --omit=peer",
52
56
  "upd@m": "npx upd@m",
53
- "npm upd": "echo '*** ROOT ***' && npm update && echo '*** DEMO ***' && npm --prefix ./demo update && echo '*** END ***'"
57
+ "npm upd": "npm update"
54
58
  },
55
59
  "license": "MIT",
56
60
  "author": "Massimo Cassandro",
@@ -97,7 +101,7 @@
97
101
  }
98
102
  },
99
103
  "devDependencies": {
100
- "@massimo-cassandro/eslint-config": "^3.0.18",
104
+ "@massimo-cassandro/eslint-config": "^3.0.19",
101
105
  "@types/node": "^26.2.0",
102
106
  "jsdom": "^30.0.1",
103
107
  "style-dictionary": "^5.5.1"
@@ -124,5 +128,7 @@
124
128
  "doc": "docs",
125
129
  "test": "test"
126
130
  },
127
- "keywords": []
131
+ "allowScripts": {
132
+ "fsevents@2.3.3": true
133
+ }
128
134
  }
package/src/index.js CHANGED
@@ -23,6 +23,7 @@ export { ValidationError } from './utilities/validation-error-class.js';
23
23
  // domBuilder helpers
24
24
  export { buildPictureTag } from './utilities/dom-builder-helpers/picture-tag.js';
25
25
  export { buildInput, buildCheckbox, buildTextarea, buildFakeField, buildSelect, buildButton } from './utilities/dom-builder-helpers/form-tags.js';
26
+ export { buildTableTag } from './utilities/dom-builder-helpers/table-tag.js';
26
27
 
27
28
  // components
28
29
  export { dismissAlerts } from './components/dismiss-alerts.js';
package/src/minimo.css CHANGED
@@ -1,48 +1,48 @@
1
1
  /* decommentare per usare i layer. Vedi https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer */
2
2
  /* @layer reset, base, components, utilities, project, overrides; */
3
3
 
4
- @import '@minimoSrc/css/reset.css';
5
- @import '@minimoSrc/css/layout-base.css';
6
- @import '@minimoSrc/css/container.css';
7
-
8
- @import '@minimoSrc/css/forms/forms.css';
9
- @import '@minimoSrc/css/forms/fieldset.css';
10
- @import '@minimoSrc/css/forms/form-collections.css';
11
- @import '@minimoSrc/css/forms/form-group.css';
12
- @import '@minimoSrc/css/forms/input-group.css';
13
- @import '@minimoSrc/css/forms/form-disabled-readonly.css';
14
- @import '@minimoSrc/css/forms/validation.css';
15
- @import '@minimoSrc/css/forms/form-utilities.css';
16
- @import '@minimoSrc/css/forms/switch.css';
17
-
18
- @import '@minimoSrc/css/buttons/buttons.css';
19
- @import '@minimoSrc/css/buttons/buttons-primary.css';
20
- @import '@minimoSrc/css/buttons/buttons-secondary.css';
21
- @import '@minimoSrc/css/buttons/status-buttons.css';
22
- @import '@minimoSrc/css/buttons/btn-close.css';
23
-
24
- @import '@minimoSrc/css/badge.css';
25
- @import '@minimoSrc/css/text.css';
26
- @import '@minimoSrc/css/links.css';
27
- @import '@minimoSrc/css/headings.css';
28
- @import '@minimoSrc/css/alerts.css';
29
- @import '@minimoSrc/css/icons.css';
30
- @import '@minimoSrc/css/svg.css';
31
- @import '@minimoSrc/css/table.css';
32
- @import '@minimoSrc/css/flex.css';
33
- @import '@minimoSrc/css/grid.css';
34
- @import '@minimoSrc/css/details.css';
35
-
36
- @import '@minimoSrc/css/sizes.css';
37
- @import '@minimoSrc/css/display.css';
38
- @import '@minimoSrc/css/margin-padding.css';
39
- /* @import '@minimoSrc/css/margin-padding-media.css'; versione responsive */
40
-
41
- @import '@minimoSrc/css/overflow.css';
42
- @import '@minimoSrc/css/position.css';
43
- @import '@minimoSrc/css/visibility.css';
4
+ @import '@minimoCss/reset.css';
5
+ @import '@minimoCss/layout-base.css';
6
+ @import '@minimoCss/container.css';
7
+
8
+ @import '@minimoCss/forms/forms.css';
9
+ @import '@minimoCss/forms/fieldset.css';
10
+ @import '@minimoCss/forms/form-collections.css';
11
+ @import '@minimoCss/forms/form-group.css';
12
+ @import '@minimoCss/forms/input-group.css';
13
+ @import '@minimoCss/forms/form-disabled-readonly.css';
14
+ @import '@minimoCss/forms/validation.css';
15
+ @import '@minimoCss/forms/form-utilities.css';
16
+ @import '@minimoCss/forms/switch.css';
17
+
18
+ @import '@minimoCss/buttons/buttons.css';
19
+ @import '@minimoCss/buttons/buttons-primary.css';
20
+ @import '@minimoCss/buttons/buttons-secondary.css';
21
+ @import '@minimoCss/buttons/status-buttons.css';
22
+ @import '@minimoCss/buttons/btn-close.css';
23
+
24
+ @import '@minimoCss/badge.css';
25
+ @import '@minimoCss/text.css';
26
+ @import '@minimoCss/links.css';
27
+ @import '@minimoCss/headings.css';
28
+ @import '@minimoCss/alerts.css';
29
+ @import '@minimoCss/icons.css';
30
+ @import '@minimoCss/svg.css';
31
+ @import '@minimoCss/table.css';
32
+ @import '@minimoCss/flex.css';
33
+ @import '@minimoCss/grid.css';
34
+ @import '@minimoCss/details.css';
35
+
36
+ @import '@minimoCss/sizes.css';
37
+ @import '@minimoCss/display.css';
38
+ @import '@minimoCss/margin-padding.css';
39
+ /* @import '@minimoCss/margin-padding-media.css'; versione responsive */
40
+
41
+ @import '@minimoCss/overflow.css';
42
+ @import '@minimoCss/position.css';
43
+ @import '@minimoCss/visibility.css';
44
44
 
45
45
 
46
46
  /* spinner: includere solo il css dello spinner scelto */
47
- @import '@minimoSrc/components/spinner/spinner-circle-basic.css';
47
+ @import '@minimoCss/spinner-circle-basic.css';
48
48
 
@@ -0,0 +1,259 @@
1
+
2
+ /**
3
+ * @typedef {string | number | Node | Function | DomBuilderItem | DomBuilderItem[] | null} TableCellContent
4
+ * Contenuto di una cella: testo, stringa html, `Node`, funzione che restituisce una stringa o un `Node`,
5
+ * singolo oggetto domBuilder o array di oggetti domBuilder.
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} TableCell
10
+ * @property {string | string[] | null} [class=null] - Classname della cella (default: null)
11
+ * @property {TableCellContent} [content=null] - Contenuto della cella: testo, html, `Node`, funzione, oggetto o array domBuilder (default: null)
12
+ * @property {boolean} [heading=false] - La cella è un th? (NB: parametro ignorato nel thead, dove è sempre true) (default: false)
13
+ * @property {Record<string, any>} [attrs] - Attributi opzionali della cella (default: null)
14
+ */
15
+
16
+ /**
17
+ * @typedef {string | number | Node | Function | DomBuilderItem[] | TableCell | null} TableCellInput
18
+ * Cella in forma abbreviata o estesa: stringa, numero, `Node`, funzione o array domBuilder sono interpretati come
19
+ * `content`; un oggetto semplice è un `TableCell` esteso. Per usare un singolo oggetto domBuilder come contenuto
20
+ * indicarlo in forma estesa: `{ content: { tag: ... } }`.
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} TableRow
25
+ * @property {string | string[] | null} [class=null] - Classname del tr (default: null)
26
+ * @property {Record<string, any>} [attrs] - Attributi opzionali del tr (default: null)
27
+ * @property {TableCellInput[]} cells - Celle della riga.
28
+ */
29
+
30
+ /**
31
+ * @typedef {TableCellInput[] | TableRow} TableRowInput
32
+ * Riga in forma abbreviata (array di celle) o estesa (oggetto `TableRow`).
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} TableSection
37
+ * @property {string | string[] | null} [class=null] - Classname della sezione (thead, tbody o tfoot) (default: null)
38
+ * @property {Record<string, any>} [attrs] - Attributi opzionali della sezione (default: null)
39
+ * @property {TableRowInput[]} [rows] - Righe della sezione.
40
+ * @property {TableCellInput[]} [cells] - Scorciatoia per una sezione con una sola riga (ignorata se è presente `rows`).
41
+ */
42
+
43
+ /**
44
+ * @typedef {TableCellInput[] | TableRowInput[] | TableSection} TableSectionInput
45
+ * Sezione in forma abbreviata o estesa:
46
+ * - array piatto di celle → una sola riga
47
+ * - array di righe (ogni riga è un array di celle o un oggetto `{ cells }`)
48
+ * - oggetto `TableSection` con `rows` oppure `cells`
49
+ *
50
+ * NB: un array è interpretato come array di righe se il primo elemento è a sua volta un array
51
+ * o un oggetto con la proprietà `cells`. Per una singola riga il cui primo contenuto è un array domBuilder
52
+ * usare la forma estesa `{ content: [...] }`.
53
+ */
54
+
55
+
56
+ /**
57
+ * Normalizza il contenuto di una cella: un singolo oggetto domBuilder viene racchiuso in un array,
58
+ * dato che `domBuilder` accetta come `content` solo stringhe, numeri, `Node`, funzioni e array.
59
+ *
60
+ * @param {TableCellContent} [content]
61
+ * @returns {string | number | Node | Function | DomBuilderItem[] | null}
62
+ */
63
+ function normalizeCellContent(content = null) {
64
+ if (content != null && typeof content === 'object' && !Array.isArray(content) && !(content instanceof Node)) {
65
+ return [content];
66
+ }
67
+ return content ?? null;
68
+ }
69
+
70
+
71
+ /**
72
+ * @param {unknown} value
73
+ * @returns {boolean} `true` per un oggetto semplice (non `null`, non array, non `Node`).
74
+ */
75
+ function isPlainObject(value) {
76
+ return value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Node);
77
+ }
78
+
79
+
80
+ /**
81
+ * Porta una cella in forma estesa.
82
+ *
83
+ * @param {TableCellInput} cell
84
+ * @returns {TableCell}
85
+ */
86
+ function normalizeCell(cell) {
87
+ return isPlainObject(cell)
88
+ ? /** @type {TableCell} */ (cell)
89
+ : { content: /** @type {TableCellContent} */ (cell) };
90
+ }
91
+
92
+
93
+ /**
94
+ * Porta una riga in forma estesa.
95
+ *
96
+ * @param {TableRowInput} row
97
+ * @returns {TableRow}
98
+ */
99
+ function normalizeRow(row) {
100
+ return Array.isArray(row)
101
+ ? { cells: row }
102
+ : row;
103
+ }
104
+
105
+
106
+ /**
107
+ * Porta una sezione in forma estesa (`{ class, attrs, rows: [{ cells }] }`).
108
+ *
109
+ * @param {TableSectionInput} section
110
+ * @returns {{ class?: string | string[] | null, attrs?: Record<string, any>, rows: TableRow[] }}
111
+ */
112
+ function normalizeSection(section) {
113
+
114
+ if (Array.isArray(section)) {
115
+ const first = section[0];
116
+ const isRowsArray = Array.isArray(first) || (isPlainObject(first) && 'cells' in /** @type {object} */ (first));
117
+
118
+ return {
119
+ rows: isRowsArray
120
+ ? /** @type {TableRowInput[]} */ (section).map(normalizeRow)
121
+ : [{ cells: /** @type {TableCellInput[]} */ (section) }]
122
+ };
123
+ }
124
+
125
+ const { rows, cells, ...rest } = section;
126
+
127
+ return {
128
+ ...rest,
129
+ rows: (rows ?? (cells ? [{ cells }] : [])).map(normalizeRow)
130
+ };
131
+ }
132
+
133
+
134
+ /**
135
+ * Costruisce l'item domBuilder di una sezione della tabella (thead, tbody o tfoot).
136
+ *
137
+ * @param {'thead' | 'tbody' | 'tfoot'} tag
138
+ * @param {TableSectionInput | null} sectionInput
139
+ * @returns {DomBuilderItem | null} `null` se la sezione non è definita.
140
+ */
141
+ function buildSection(tag, sectionInput) {
142
+
143
+ if (sectionInput == null) {
144
+ return null;
145
+ }
146
+
147
+ const section = normalizeSection(sectionInput);
148
+ const isHead = tag === 'thead';
149
+
150
+ return {
151
+ tag,
152
+ class: section.class ?? undefined,
153
+ attrs: section.attrs,
154
+ children: section.rows.map(row => ({
155
+ tag: 'tr',
156
+ class: row.class ?? undefined,
157
+ attrs: row.attrs,
158
+ children: (row.cells ?? []).map(cellInput => {
159
+
160
+ const cell = normalizeCell(cellInput);
161
+ const isTh = isHead || cell.heading === true;
162
+
163
+ return {
164
+ tag: isTh ? 'th' : 'td',
165
+ class: cell.class ?? undefined,
166
+ attrs: {
167
+ // scope: sempre `col` nel thead, `row` per i th di tbody/tfoot (sovrascrivibile da `attrs`)
168
+ ...(isTh && !isHead ? { scope: 'row' } : {}),
169
+ ...(cell.attrs ?? {}),
170
+ ...(isHead ? { scope: 'col' } : {})
171
+ },
172
+ content: normalizeCellContent(cell.content)
173
+ };
174
+ })
175
+ }))
176
+ };
177
+ }
178
+
179
+
180
+ /**
181
+ * Table builder
182
+ * Restituisce un oggetto domBuilder per una tabella.
183
+ *
184
+ * Ogni sezione (`thead`, `tbody`, `tfoot`) accetta sia una forma abbreviata sia una estesa:
185
+ * - cella: stringa, numero, `Node`, funzione o array domBuilder (= `content`), oppure oggetto `{ content, class, attrs, heading }`
186
+ * - riga: array di celle, oppure oggetto `{ cells, class, attrs }`
187
+ * - sezione: array piatto di celle (una sola riga), array di righe, oppure oggetto `{ rows | cells, class, attrs }`
188
+ *
189
+ * @param {Object} args
190
+ * @param {string | string[]} [args.classname='table'] - Classe del tag table (default: 'table')
191
+ * @param {Record<string, any>} [args.attrs] - Oggetto opzionale di attributi del tag table (default: null)
192
+ * @param {TableSectionInput | null} [args.thead=null] - Oggetto opzionale per il tag thead (default: null)
193
+ * @param {TableSectionInput | null} [args.tbody=null] - Oggetto opzionale per il tag tbody (default: null)
194
+ * @param {TableSectionInput | null} [args.tfoot=null] - Oggetto opzionale per il tag tfoot (default: null)
195
+ *
196
+ * @example
197
+ * buildTableTag({
198
+ * classname: 'table', // default: 'table'
199
+ * attrs: { id: 'my-table' }, // default: null
200
+ *
201
+ * // array piatto di celle: una sola riga (caso più comune)
202
+ * thead: ['Nome', 'Email', { content: 'Importo', class: 'text-end' }],
203
+ *
204
+ * // array di righe; ogni riga è un array di celle o un oggetto { cells }
205
+ * tbody: [
206
+ * [
207
+ * { content: 'Mario', heading: true }, // th scope="row"
208
+ * 'mario@x.it', // testo
209
+ * { content: '<strong>10</strong>', class: 'text-end' }, // html
210
+ * { content: { tag: 'a', attrs: { href: '#' }, content: 'dettagli' } } // singolo oggetto domBuilder
211
+ * ],
212
+ * {
213
+ * class: 'is-selected',
214
+ * attrs: { 'data-id': 2 },
215
+ * cells: [
216
+ * 'Anna',
217
+ * 'anna@x.it',
218
+ * '12',
219
+ * [{ tag: 'em', content: 'a' }, { tag: 'em', content: 'b' }] // array domBuilder
220
+ * ]
221
+ * }
222
+ * ],
223
+ *
224
+ * // forma estesa della sezione: { class, attrs, rows | cells }
225
+ * tfoot: { class: 'table-light', cells: ['Totale', '', '', { content: '22', class: 'text-end' }] }
226
+ * // oppure: tfoot: null (default)
227
+ * });
228
+ *
229
+ * @example
230
+ * // thead con più righe (es. colspan nella prima)
231
+ * buildTableTag({
232
+ * thead: [
233
+ * [{ content: 'Contatti', attrs: { colspan: 2 } }, 'Importo'],
234
+ * ['Nome', 'Email', 'Importo']
235
+ * ]
236
+ * });
237
+ *
238
+ * @returns {DomBuilderItem} L'item domBuilder del tag `table`.
239
+ */
240
+ export function buildTableTag({
241
+ classname = 'table',
242
+ attrs,
243
+ thead = null,
244
+ tbody = null,
245
+ tfoot = null
246
+ } = {}) {
247
+
248
+ return {
249
+ tag: 'table',
250
+ class: classname,
251
+ attrs: attrs,
252
+ children: [
253
+ buildSection('thead', thead),
254
+ buildSection('tbody', tbody),
255
+ buildSection('tfoot', tfoot)
256
+ ].filter(/** @returns {item is DomBuilderItem} */ item => item != null)
257
+ };
258
+
259
+ }