@massimo-cassandro/minimo 1.33.0 → 1.33.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.
- package/README.md +2 -1
- package/dev-tools/starter-kit/source_files/{CLAUDE-frontend.md → app/CLAUDE.md} +1 -1
- package/dev-tools/starter-kit/source_files/app/assets/api_htaccess +20 -0
- package/dev-tools/starter-kit/source_files/{layout-critical.css → app/css/layout.critical.css} +1 -1
- package/dev-tools/starter-kit/source_files/app/css/layout.css +0 -0
- package/dev-tools/starter-kit/source_files/app/index.js +1 -0
- package/dev-tools/starter-kit/source_files/root/webpack-config-modules/inline-critical-css.mjs +41 -0
- package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/purgecss-setup.mjs +1 -1
- package/dev-tools/starter-kit/{webpack.config.mjs → source_files/root/webpack.config.mjs} +50 -24
- package/dev-tools/starter-kit/starter-install.sh +141 -83
- package/dev-tools/stylelint-config/test/test.module.scss +9 -0
- package/package.json +1 -1
- package/snippets/php/iviewer.php +488 -0
- package/snippets/twig-symfony/ViewerController.php +549 -0
- package/src/custom-properties.css +29 -0
- package/src/minimo.css +3 -0
- package/src/utilities/dom-builder/dom-builder.js +9 -5
- package/src/web-components/json-table/json-table-component.js +387 -38
- package/src/web-components/json-table/json-table-component.module.css +190 -0
- package/src/web-components/json-table/json-table.minimo.tokens.mjs +195 -0
- package/src/web-components/json-table/readme.md +603 -82
- package/src/web-components/json-table/src/cell-content.js +136 -0
- package/src/web-components/json-table/src/content-utils.js +103 -0
- package/src/web-components/json-table/src/data-types.js +280 -0
- package/src/web-components/json-table/src/defaults.js +377 -58
- package/src/web-components/json-table/src/get-data.js +105 -10
- package/src/web-components/json-table/src/icons.js +80 -0
- package/src/web-components/json-table/src/info-section.js +85 -50
- package/src/web-components/json-table/src/main-builder.js +175 -52
- package/src/web-components/json-table/src/pagination.js +253 -0
- package/src/web-components/json-table/src/parse-cols.js +113 -0
- package/src/web-components/json-table/src/parse-rows.js +81 -0
- package/src/web-components/json-table/src/resolve-params.js +102 -32
- package/src/web-components/json-table/src/search.js +71 -0
- package/src/web-components/json-table/src/sorting.js +111 -0
- package/src/web-components/json-table/src/table-body.js +109 -0
- package/src/web-components/json-table/src/table-builder.js +152 -0
- package/src/web-components/json-table/src/table-tfoot.js +56 -0
- package/src/web-components/json-table/src/table-thead.js +183 -0
- package/src/web-components/json-table/src/update-info.js +43 -24
- package/src/web-components/s-datatable-component/{docs.md → readme.md} +5 -3
- package/src/web-components/s-datatable-component/s-datatable-component.js +1 -1
- package/types/global.d.ts +17 -3
- package/dev-tools/starter-kit/source_files/app_index.js +0 -1
- package/snippets/config-templates/_htaccess +0 -69
- package/snippets/config-templates/robots.txt +0 -5
- package/snippets/config-templates/rollup.config.mjs +0 -115
- /package/dev-tools/starter-kit/source_files/{_root_robots.txt → app/assets/robots.txt} +0 -0
- /package/dev-tools/starter-kit/source_files/{_root_htaccess → app/assets/root_htaccess} +0 -0
- /package/{src → dev-tools/starter-kit/source_files/app/css}/fonts.css +0 -0
- /package/dev-tools/starter-kit/source_files/{favicons-readme.md → app/favicons/favicons-readme.md} +0 -0
- /package/dev-tools/starter-kit/{webpack-template.ejs → source_files/app/tpl/index.ejs} +0 -0
- /package/dev-tools/starter-kit/source_files/{__project__.code-workspace → root/__project__.code-workspace} +0 -0
- /package/dev-tools/starter-kit/source_files/{_browserslistrc → root/_browserslistrc} +0 -0
- /package/dev-tools/starter-kit/source_files/{_editorconfig → root/_editorconfig} +0 -0
- /package/dev-tools/starter-kit/source_files/{_gitignore → root/_gitignore} +0 -0
- /package/dev-tools/starter-kit/source_files/{_prettierrc → root/_prettierrc} +0 -0
- /package/dev-tools/starter-kit/source_files/{eslint.config.mjs → root/eslint.config.mjs} +0 -0
- /package/dev-tools/starter-kit/source_files/{jsconfig.json → root/jsconfig.json} +0 -0
- /package/dev-tools/starter-kit/{package-tpl.json → source_files/root/package-tpl.json} +0 -0
- /package/dev-tools/starter-kit/source_files/{stylelint.config.mjs → root/stylelint.config.mjs} +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/css-rules.mjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/get-jsConfig-aliases.mjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/mini-svg-data-uri-loader.cjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/postcss.config.mjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/purgecss-variables-safelist.mjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/svg-rules.mjs +0 -0
- /package/dev-tools/starter-kit/{webpack-config-modules → source_files/root/webpack-config-modules}/svgo.config.mjs +0 -0
- /package/snippets/{config-templates/_github → _github}/dependabot.yml +0 -0
- /package/snippets/{error-pages/error-pages.js → error-pages.js} +0 -0
- /package/snippets/{config-templates/react → react}/offcanvas/offcanvas.jsx +0 -0
- /package/snippets/{config-templates/react → react}/offcanvas/offcanvas.module.scss +0 -0
- /package/snippets/{config-templates → root}/_hintrc.cjs +0 -0
- /package/snippets/{config-templates → root}/_npmignore +0 -0
- /package/snippets/{config-templates → root}/_npmrc +0 -0
- /package/snippets/{config-templates → root}/_stylelintignore +0 -0
- /package/snippets/{config-templates → root}/babel.config--react.json +0 -0
- /package/snippets/{config-templates → root}/babel.config--solidJS.json +0 -0
- /package/snippets/{ImgViewerController.php → twig-symfony/ImgViewerController.php} +0 -0
- /package/snippets/{UnsplashController.php → twig-symfony/UnsplashController.php} +0 -0
- /package/snippets/{config-templates → twig-symfony}/_env.local +0 -0
- /package/snippets/{error-pages → twig-symfony}/bundles/TwigBundle/Exception/error.html.twig +0 -0
- /package/snippets/{flash-messages.html.twig → twig-symfony/flash-messages.html.twig} +0 -0
- /package/snippets/{minimo-form-layout.html.twig → twig-symfony/minimo-form-layout.html.twig} +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*! minimo - json-table: icons */
|
|
2
|
+
|
|
3
|
+
import checkBold from '../../../icons/check-bold.svg?inline';
|
|
4
|
+
import xBold from '../../../icons/x-bold.svg?inline';
|
|
5
|
+
import arrowUp from '../../../icons/arrow-up.svg?inline';
|
|
6
|
+
import arrowDown from '../../../icons/arrow-down.svg?inline';
|
|
7
|
+
import arrowsDownUp from '../../../icons/arrows-down-up.svg?inline';
|
|
8
|
+
import caretLeft from '../../../icons/caret-left.svg?inline';
|
|
9
|
+
import caretRight from '../../../icons/caret-right.svg?inline';
|
|
10
|
+
|
|
11
|
+
/** @typedef {import('./defaults.js').IconDef} IconDef */
|
|
12
|
+
|
|
13
|
+
/** Default icon of `true` values (minimo `check-bold`, inline SVG markup) */
|
|
14
|
+
export const boolTrueIcon = checkBold;
|
|
15
|
+
/** Default icon of `false` values (minimo `x-bold`) */
|
|
16
|
+
export const boolFalseIcon = xBold;
|
|
17
|
+
/** Default sort button icon, ascending sort active (minimo `arrow-up`) */
|
|
18
|
+
export const sortAscArrowIcon = arrowUp;
|
|
19
|
+
/** Default sort button icon, descending sort active (minimo `arrow-down`) */
|
|
20
|
+
export const sortDescArrowIcon = arrowDown;
|
|
21
|
+
/** Default sort button icon, no sort active (minimo `arrows-down-up`) */
|
|
22
|
+
export const sortNoneArrowIcon = arrowsDownUp;
|
|
23
|
+
/** Default previous page button icon (minimo `caret-left`) */
|
|
24
|
+
export const paginationPrevIcon = caretLeft;
|
|
25
|
+
/** Default next page button icon (minimo `caret-right`) */
|
|
26
|
+
export const paginationNextIcon = caretRight;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Markup strings already parsed, keyed by the string itself.
|
|
30
|
+
* @type {Map<string, DocumentFragment>}
|
|
31
|
+
*/
|
|
32
|
+
const fragmentCache = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolves an icon definition to a content usable by domBuilder (`content`) or `setContent()`.
|
|
36
|
+
*
|
|
37
|
+
* Markup strings are parsed once through an inert `<template>` and cloned on every call, so the
|
|
38
|
+
* same icon can be inserted in many cells without re-parsing and without going through the HTML
|
|
39
|
+
* sanitizer used by domBuilder for markup strings (icons are developer-supplied configuration,
|
|
40
|
+
* not user data). DOM nodes are cloned too, since a node can live in one place only.
|
|
41
|
+
*
|
|
42
|
+
* @param {IconDef|null|undefined} icon - Icon definition (see `IconDef`)
|
|
43
|
+
* @returns {Node|DomBuilderItem[]|null} A fresh node (or domBuilder array), `null` when `icon` is nullish
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* iconContent('<svg …></svg>'); // → DocumentFragment (clone of the parsed markup)
|
|
47
|
+
* iconContent(document.querySelector('svg')); // → cloned SVGElement
|
|
48
|
+
* iconContent({ tag: 'span', content: '✓' }); // → [{ tag: 'span', content: '✓' }] (domBuilder array)
|
|
49
|
+
* iconContent(() => '<svg …></svg>'); // → DocumentFragment
|
|
50
|
+
* iconContent(null); // → null
|
|
51
|
+
*/
|
|
52
|
+
export function iconContent(icon) {
|
|
53
|
+
|
|
54
|
+
const resolved = typeof icon === 'function' ? icon() : icon;
|
|
55
|
+
|
|
56
|
+
if (resolved == null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (resolved instanceof Node) {
|
|
61
|
+
return resolved.cloneNode(true);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof resolved === 'string') {
|
|
65
|
+
let fragment = fragmentCache.get(resolved);
|
|
66
|
+
if (!fragment) {
|
|
67
|
+
const tpl = document.createElement('template');
|
|
68
|
+
tpl.innerHTML = resolved.trim();
|
|
69
|
+
fragment = tpl.content;
|
|
70
|
+
fragmentCache.set(resolved, fragment);
|
|
71
|
+
}
|
|
72
|
+
return fragment.cloneNode(true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof resolved === 'object') {
|
|
76
|
+
return [resolved];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! minimo - json-table: info section */
|
|
1
|
+
/*! minimo - json-table: info section parts */
|
|
2
2
|
|
|
3
3
|
import * as styles from '../json-table-component.module.css';
|
|
4
4
|
import { classnames } from '../../../utilities/classnames.js';
|
|
@@ -7,66 +7,101 @@ import { classnames } from '../../../utilities/classnames.js';
|
|
|
7
7
|
/** @typedef {import('./main-builder.js').JsonTableElements} JsonTableElements */
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* domBuilder item of the info text container (template slot `resultInfo`).
|
|
11
|
+
*
|
|
12
|
+
* Generated structure: `div.resultInfo[classes.resultInfo][aria-live=polite]`; the content is
|
|
13
|
+
* set by `update-info.js`. The element is stored in `elements.resultInfo`.
|
|
14
|
+
*
|
|
15
|
+
* @param {JsonTableParams} params - Resolved params
|
|
16
|
+
* @param {JsonTableElements} elements - Object collecting the generated elements (mutated)
|
|
17
|
+
* @returns {DomBuilderItem}
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* domBuilder([resultInfoPart(params, elements)], host);
|
|
21
|
+
* elements.resultInfo; // → HTMLElement
|
|
22
|
+
*/
|
|
23
|
+
export function resultInfoPart(params, elements) {
|
|
24
|
+
return {
|
|
25
|
+
className: classnames(styles.resultInfo, params.classes.resultInfo),
|
|
26
|
+
attrs: { 'aria-live': 'polite' },
|
|
27
|
+
callback: el => { elements.resultInfo = /** @type {HTMLElement} */ (el); }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* domBuilder item of the search input (template slot `search`), rendered only when
|
|
34
|
+
* `params.search` is true.
|
|
35
|
+
*
|
|
36
|
+
* Generated structure: `div.search[classes.search] > input[type=search][classes.searchInput]`.
|
|
37
|
+
* The input is stored in `elements.searchInput`.
|
|
38
|
+
*
|
|
39
|
+
* TODO listener sull'input di ricerca: step 3
|
|
40
|
+
*
|
|
41
|
+
* @param {JsonTableParams} params - Resolved params
|
|
42
|
+
* @param {JsonTableElements} elements - Object collecting the generated elements (mutated)
|
|
43
|
+
* @returns {DomBuilderItem}
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* domBuilder([searchPart(params, elements)], host);
|
|
47
|
+
* elements.searchInput; // → HTMLInputElement (when params.search is true)
|
|
48
|
+
*/
|
|
49
|
+
export function searchPart(params, elements) {
|
|
50
|
+
return {
|
|
51
|
+
condition: params.search,
|
|
52
|
+
className: classnames(styles.search, params.classes.search),
|
|
53
|
+
children: [
|
|
54
|
+
{
|
|
55
|
+
tag: 'input',
|
|
56
|
+
className: classnames(params.classes.searchInput),
|
|
57
|
+
attrs: {
|
|
58
|
+
type: 'search',
|
|
59
|
+
title: params.labels.searchTitle,
|
|
60
|
+
placeholder: params.labels.searchPlaceholder,
|
|
61
|
+
'aria-label': params.labels.searchAriaLabel
|
|
62
|
+
},
|
|
63
|
+
callback: el => { elements.searchInput = /** @type {HTMLInputElement} */ (el); }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* domBuilder item of the whole info section (template slot `infoSection`): info text + search input.
|
|
11
72
|
*
|
|
12
73
|
* Generated structure:
|
|
13
74
|
* ```
|
|
14
|
-
* div.infoOuter[
|
|
15
|
-
* div.info[
|
|
16
|
-
* div.resultInfo ←
|
|
17
|
-
* div.search > input[type=search] ← only if `params.search` is true
|
|
75
|
+
* div.infoOuter[classes.infoOuter]
|
|
76
|
+
* div.info[classes.info]
|
|
77
|
+
* div.resultInfo ← resultInfoPart()
|
|
78
|
+
* div.search > input[type=search] ← searchPart(), only if `params.search` is true
|
|
18
79
|
* ```
|
|
19
80
|
*
|
|
20
81
|
* References to the generated elements are stored in `elements`
|
|
21
82
|
* (`infoOuter`, `info`, `resultInfo`, `searchInput`).
|
|
22
83
|
*
|
|
23
|
-
* TODO listener sull'input di ricerca: da impostare quando verrà implementata la ricerca
|
|
24
|
-
*
|
|
25
84
|
* @param {JsonTableParams} params - Resolved params
|
|
26
85
|
* @param {JsonTableElements} elements - Object collecting the generated elements (mutated)
|
|
27
|
-
* @returns {DomBuilderItem
|
|
86
|
+
* @returns {DomBuilderItem}
|
|
28
87
|
*
|
|
29
88
|
* @example
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* elements.searchInput; // → HTMLInputElement (when params.search is true)
|
|
89
|
+
* domBuilder([{ tag: 'section', children: [infoSectionPart(params, elements)] }], host);
|
|
90
|
+
* elements.info; // → HTMLElement
|
|
33
91
|
*/
|
|
34
|
-
export function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
condition: params.search,
|
|
52
|
-
className: styles.search,
|
|
53
|
-
children: [
|
|
54
|
-
{
|
|
55
|
-
tag: 'input',
|
|
56
|
-
className: classnames(params.searchInputClass),
|
|
57
|
-
attrs: {
|
|
58
|
-
type: 'search',
|
|
59
|
-
title: params.searchInputTitle,
|
|
60
|
-
placeholder: params.searchInputPlaceholder,
|
|
61
|
-
'aria-label': params.searchInputAriaLabel
|
|
62
|
-
},
|
|
63
|
-
callback: el => { elements.searchInput = /** @type {HTMLInputElement} */ (el); }
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
];
|
|
92
|
+
export function infoSectionPart(params, elements) {
|
|
93
|
+
return {
|
|
94
|
+
className: classnames(styles.infoOuter, params.classes.infoOuter),
|
|
95
|
+
callback: el => { elements.infoOuter = /** @type {HTMLElement} */ (el); },
|
|
96
|
+
children: [
|
|
97
|
+
{
|
|
98
|
+
className: classnames(styles.info, params.classes.info),
|
|
99
|
+
callback: el => { elements.info = /** @type {HTMLElement} */ (el); },
|
|
100
|
+
children: [
|
|
101
|
+
resultInfoPart(params, elements),
|
|
102
|
+
searchPart(params, elements)
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
};
|
|
72
107
|
}
|
|
@@ -3,93 +3,216 @@
|
|
|
3
3
|
import * as styles from '../json-table-component.module.css';
|
|
4
4
|
import { domBuilder } from '../../../utilities/dom-builder/dom-builder.js';
|
|
5
5
|
import { classnames } from '../../../utilities/classnames.js';
|
|
6
|
-
import {
|
|
6
|
+
import { defaults } from './defaults.js';
|
|
7
|
+
import { infoSectionPart, resultInfoPart, searchPart } from './info-section.js';
|
|
8
|
+
import { tablePart, tableFooterPart, captionPart, captionId } from './table-builder.js';
|
|
9
|
+
import { paginationPart } from './pagination.js';
|
|
7
10
|
|
|
8
|
-
/** @typedef {import('
|
|
11
|
+
/** @typedef {import('../json-table-component.js').JsonTable} JsonTable */
|
|
12
|
+
/** @typedef {import('./defaults.js').TemplateItem} TemplateItem */
|
|
13
|
+
/** @typedef {import('./defaults.js').TemplateSlot} TemplateSlot */
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Elements generated by the component, collected during the build.
|
|
12
17
|
* @typedef {Object} JsonTableElements
|
|
13
|
-
* @property {HTMLElement} [wrapper] - Main wrapper (`section`), contains
|
|
18
|
+
* @property {HTMLElement} [wrapper] - Main wrapper (`section`), contains the template parts
|
|
14
19
|
* @property {HTMLElement} [infoOuter] - Outer info container
|
|
15
20
|
* @property {HTMLElement} [info] - Info container (flex row: info text + search)
|
|
16
21
|
* @property {HTMLElement} [resultInfo] - Info text container
|
|
17
22
|
* @property {HTMLInputElement} [searchInput] - Search input (only when `search` is true)
|
|
18
23
|
* @property {HTMLElement} [tableWrapper] - Div wrapping the table (`table-responsive` by default)
|
|
19
24
|
* @property {HTMLTableElement} [table] - The `<table>` element
|
|
25
|
+
* @property {HTMLTableSectionElement} [thead] - The `<thead>` element
|
|
26
|
+
* @property {HTMLTableSectionElement} [tbody] - The `<tbody>` element
|
|
27
|
+
* @property {HTMLTableSectionElement} [tfoot] - The `<tfoot>` element (only when `tfoot` is true)
|
|
28
|
+
* @property {HTMLElement} [tableFooter] - Footer bar below the table (caption + pagination), when rendered
|
|
29
|
+
* @property {HTMLElement} [caption] - Caption container (only when `caption` is set)
|
|
30
|
+
* @property {HTMLElement} [pagination] - Pagination `<nav>` (only when `perPage` > 0)
|
|
20
31
|
*/
|
|
21
32
|
|
|
22
33
|
/**
|
|
23
|
-
*
|
|
34
|
+
* Whether a template item is a slot placeholder.
|
|
35
|
+
* @param {*} item
|
|
36
|
+
* @returns {item is { slot: TemplateSlot }}
|
|
37
|
+
*/
|
|
38
|
+
function isSlot(item) {
|
|
39
|
+
return item != null && typeof item === 'object' && !(item instanceof Node) && typeof item.slot === 'string';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Collects, recursively (`children` and array `content`), the names of the slots used by a template.
|
|
45
|
+
*
|
|
46
|
+
* @param {Array<TemplateItem|string|Node>} items - Template items
|
|
47
|
+
* @param {Set<string>} [found=new Set()] - Accumulator (default: new Set())
|
|
48
|
+
* @returns {Set<string>}
|
|
24
49
|
*
|
|
25
|
-
*
|
|
50
|
+
* @example
|
|
51
|
+
* collectSlots([{ slot: 'table' }, { children: [{ slot: 'pagination' }] }]); // → Set { 'table', 'pagination' }
|
|
52
|
+
*/
|
|
53
|
+
export function collectSlots(items, found = new Set()) {
|
|
54
|
+
|
|
55
|
+
items.forEach(item => {
|
|
56
|
+
if (isSlot(item)) {
|
|
57
|
+
found.add(item.slot);
|
|
58
|
+
} else if (item != null && typeof item === 'object' && !(item instanceof Node)) {
|
|
59
|
+
if (Array.isArray(item.children)) {
|
|
60
|
+
collectSlots(item.children, found);
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(item.content)) {
|
|
63
|
+
collectSlots(/** @type {TemplateItem[]} */ (item.content), found);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Replaces, recursively (`children` and array `content`), every `{ slot: 'name' }` item of a
|
|
74
|
+
* template with the corresponding built-in part. Unknown slots are reported in the console
|
|
75
|
+
* and skipped. Non-slot items are shallow-copied, so the original template is never mutated.
|
|
76
|
+
*
|
|
77
|
+
* @param {Array<TemplateItem|string|Node>} items - Template items
|
|
78
|
+
* @param {Object<string, DomBuilderItem>} parts - Built-in parts, keyed by slot name
|
|
79
|
+
* @returns {Array<DomBuilderItem|string|Node>}
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* expandSlots([{ className: 'row', children: [{ slot: 'search' }, { slot: 'resultInfo' }] }, { slot: 'table' }], parts);
|
|
83
|
+
* // → [{ className: 'row', children: [<search part>, <resultInfo part>] }, <table part>]
|
|
84
|
+
*/
|
|
85
|
+
export function expandSlots(items, parts) {
|
|
86
|
+
|
|
87
|
+
/** @type {Array<DomBuilderItem|string|Node>} */
|
|
88
|
+
const result = [];
|
|
89
|
+
|
|
90
|
+
items.forEach(item => {
|
|
91
|
+
|
|
92
|
+
if (item == null || typeof item !== 'object' || item instanceof Node) {
|
|
93
|
+
result.push(/** @type {string|Node} */ (item));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isSlot(item)) {
|
|
98
|
+
const part = parts[item.slot];
|
|
99
|
+
if (!part) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error(`[json-table] template: slot \`${item.slot}\` non riconosciuto (disponibili: ${Object.keys(parts).join(', ')})`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
result.push(part);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @type {DomBuilderItem} */
|
|
109
|
+
const copy = /** @type {DomBuilderItem} */ ({ ...item });
|
|
110
|
+
|
|
111
|
+
if (Array.isArray(item.children)) {
|
|
112
|
+
copy.children = expandSlots(item.children, parts);
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(item.content)) {
|
|
115
|
+
copy.content = /** @type {DomBuilderItem[]} */ (expandSlots(/** @type {TemplateItem[]} */ (item.content), parts));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
result.push(copy);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Builds the whole component structure inside the host element (emptied first) via domBuilder:
|
|
127
|
+
* a fixed main wrapper whose content is defined by `params.template`.
|
|
128
|
+
*
|
|
129
|
+
* Available template slots (see `TemplateItem`):
|
|
130
|
+
* - `infoSection` → `infoSectionPart()` (info text + search input)
|
|
131
|
+
* - `resultInfo` → `resultInfoPart()` (info text only)
|
|
132
|
+
* - `search` → `searchPart()` (search input only)
|
|
133
|
+
* - `table` → `tablePart()` (table wrapper + table with thead, tbody, tfoot, plus the footer bar)
|
|
134
|
+
* - `caption` → `captionPart()` (caption container; part of the `table` footer bar unless placed explicitly)
|
|
135
|
+
* - `pagination` → `paginationPart()` (pagination nav; idem)
|
|
136
|
+
*
|
|
137
|
+
* Generated structure with the default template (`[{ slot: 'infoSection' }, { slot: 'table' }]`):
|
|
26
138
|
* ```
|
|
27
|
-
* section.wrapper[
|
|
28
|
-
* div.infoOuter[
|
|
29
|
-
* div.info[
|
|
139
|
+
* section.wrapper[classes.wrapper]
|
|
140
|
+
* div.infoOuter[classes.infoOuter]
|
|
141
|
+
* div.info[classes.info]
|
|
30
142
|
* div.resultInfo
|
|
31
143
|
* div.search > input[type=search] (if `search`)
|
|
32
|
-
* div
|
|
33
|
-
*
|
|
34
|
-
*
|
|
144
|
+
* div.tableOuter
|
|
145
|
+
* div.tableWrapper[classes.tableWrapper]
|
|
146
|
+
* table#tableId.table[classes.table]
|
|
147
|
+
* thead / tbody / tfoot (tfoot if `tfoot`)
|
|
148
|
+
* div.tableFooter[classes.tableFooter] (if `caption` or `perPage` > 0)
|
|
149
|
+
* div.caption (if `caption`)
|
|
150
|
+
* nav.pagination (if `perPage` > 0)
|
|
35
151
|
* ```
|
|
36
152
|
*
|
|
37
|
-
*
|
|
153
|
+
* The `<tbody>`, `<tfoot>` and pagination are created empty: see `renderTbody()`, `renderTfoot()`
|
|
154
|
+
* and `renderPagination()`.
|
|
38
155
|
*
|
|
39
|
-
* @param {
|
|
40
|
-
* @param {JsonTableParams} params - Resolved params
|
|
156
|
+
* @param {JsonTable} jt - The component instance (host element, `params`, `cols`)
|
|
41
157
|
* @returns {JsonTableElements} References to the generated elements
|
|
42
158
|
*
|
|
43
159
|
* @example
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* tableClass: 'table table-bordered', // default: 'table table-bordered'
|
|
51
|
-
* mainWrapperExtraClass: null, // default: null
|
|
52
|
-
* outerInfoExtraClass: null, // default: null
|
|
53
|
-
* infoExtraClass: null // default: null
|
|
54
|
-
* });
|
|
160
|
+
* jt.params.template = [
|
|
161
|
+
* { className: 'flex gap-2', children: [{ slot: 'search' }, { slot: 'pagination' }] },
|
|
162
|
+
* { slot: 'table' }, // footer bar with the caption only: the pagination is placed above
|
|
163
|
+
* { slot: 'resultInfo' }
|
|
164
|
+
* ];
|
|
165
|
+
* const elements = mainBuilder(jt);
|
|
55
166
|
* elements.table; // → HTMLTableElement
|
|
56
167
|
*/
|
|
57
|
-
export function mainBuilder(
|
|
168
|
+
export function mainBuilder(jt) {
|
|
169
|
+
|
|
170
|
+
const { params } = jt;
|
|
58
171
|
|
|
59
172
|
/** @type {JsonTableElements} */
|
|
60
173
|
const elements = {};
|
|
61
174
|
|
|
175
|
+
const capId = captionId(params);
|
|
176
|
+
const captionItem = captionPart(jt, elements, capId);
|
|
177
|
+
const paginationItem = paginationPart(jt, elements);
|
|
178
|
+
|
|
179
|
+
// footer bar parts: removed below when the template places them elsewhere
|
|
180
|
+
/** @type {DomBuilderItem[]} */
|
|
181
|
+
const footerChildren = [captionItem, paginationItem];
|
|
182
|
+
const footerBar = tableFooterPart(jt, elements, footerChildren);
|
|
183
|
+
|
|
184
|
+
/** @type {Object<string, DomBuilderItem>} */
|
|
185
|
+
const parts = {
|
|
186
|
+
infoSection: infoSectionPart(params, elements),
|
|
187
|
+
resultInfo: resultInfoPart(params, elements),
|
|
188
|
+
search: searchPart(params, elements),
|
|
189
|
+
caption: captionItem,
|
|
190
|
+
pagination: paginationItem,
|
|
191
|
+
table: tablePart(jt, elements, footerBar, capId)
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const template = typeof params.template === 'function'
|
|
195
|
+
? params.template(parts, params)
|
|
196
|
+
: params.template;
|
|
197
|
+
|
|
198
|
+
const items = Array.isArray(template) ? template : /** @type {TemplateItem[]} */ (defaults.template);
|
|
199
|
+
const usedSlots = collectSlots(items);
|
|
200
|
+
|
|
201
|
+
['caption', 'pagination'].forEach(slot => {
|
|
202
|
+
if (usedSlots.has(slot)) {
|
|
203
|
+
footerChildren.splice(footerChildren.indexOf(parts[slot]), 1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
footerBar.condition = footerChildren.some(item => item.condition !== false);
|
|
207
|
+
|
|
62
208
|
domBuilder([
|
|
63
209
|
{
|
|
64
210
|
tag: 'section',
|
|
65
|
-
className: classnames(styles.wrapper, params.
|
|
211
|
+
className: classnames(styles.wrapper, params.classes.wrapper),
|
|
66
212
|
callback: el => { elements.wrapper = /** @type {HTMLElement} */ (el); },
|
|
67
|
-
children:
|
|
68
|
-
|
|
69
|
-
...infoSection(params, elements),
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
className: classnames(params.tableWrapperClass),
|
|
73
|
-
callback: el => { elements.tableWrapper = /** @type {HTMLElement} */ (el); },
|
|
74
|
-
children: [
|
|
75
|
-
{
|
|
76
|
-
tag: 'table',
|
|
77
|
-
id: params.tableId,
|
|
78
|
-
className: classnames(styles.table, params.tableClass),
|
|
79
|
-
callback: el => { elements.table = /** @type {HTMLTableElement} */ (el); },
|
|
80
|
-
children: [
|
|
81
|
-
{
|
|
82
|
-
tag: 'caption',
|
|
83
|
-
condition: params.caption != null,
|
|
84
|
-
content: params.caption
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
90
|
-
]
|
|
213
|
+
children: expandSlots(items, parts)
|
|
91
214
|
}
|
|
92
|
-
],
|
|
215
|
+
], jt, { emptyParent: true });
|
|
93
216
|
|
|
94
217
|
return elements;
|
|
95
218
|
}
|