@jsswift/ui 1.0.41
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/dist/css/animation.css +121 -0
- package/dist/css/base.css +621 -0
- package/dist/css/docs.css +216 -0
- package/dist/css/min-ui.css +1 -0
- package/dist/css/responsive.css +3259 -0
- package/dist/css/ui-components.css +8757 -0
- package/dist/css/ui.css +12765 -0
- package/dist/css/ui.min.css +1 -0
- package/dist/fonts/Nunito-Black.ttf +0 -0
- package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
- package/dist/fonts/Nunito-Bold.ttf +0 -0
- package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Italic.ttf +0 -0
- package/dist/fonts/Nunito-Light.ttf +0 -0
- package/dist/fonts/Nunito-LightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Medium.ttf +0 -0
- package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
- package/dist/fonts/Nunito-Regular.ttf +0 -0
- package/dist/fonts/Nunito-SemiBold.ttf +0 -0
- package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
- package/dist/img/logo.svg +131 -0
- package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
- package/dist/min-ui.js +34 -0
- package/dist/ui.js +21027 -0
- package/dist/ui.min.js +34 -0
- package/package.json +40 -0
- package/src/00-bootstrap.js +330 -0
- package/src/01-foundation-helpers.js +1208 -0
- package/src/10-primitives-layout.js +5972 -0
- package/src/20-display-content.js +1014 -0
- package/src/21-list-content.js +341 -0
- package/src/22-banner-content.js +489 -0
- package/src/23-tooltip.js +326 -0
- package/src/31-form-advanced.js +3149 -0
- package/src/40-navigation.js +1454 -0
- package/src/41-tab-panel.js +1090 -0
- package/src/50-feedback.js +1209 -0
- package/src/51-feedback-service.js +166 -0
- package/src/60-shell-app.js +1483 -0
- package/src/61-form-service.js +450 -0
- package/src/70-dialog.js +401 -0
- package/src/71-menu-overlays.js +1442 -0
- package/src/80-data.js +503 -0
- package/src/README.md +159 -0
- package/src/css/README.md +17 -0
- package/src/css/animation.css +121 -0
- package/src/css/base.css +621 -0
- package/src/css/docs.css +216 -0
- package/src/css/responsive.css +3259 -0
- package/src/css/ui-components.css +8757 -0
- package/src/modules.json +176 -0
package/src/80-data.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
UI.Table = (...args) => {
|
|
2
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = props.slots || {};
|
|
4
|
+
const columns = Array.isArray(props.columns) ? props.columns : [];
|
|
5
|
+
const basePageSizes = tableNormalizePageSizes(props.pageSizeOptions, [5, 10, 20, 50]);
|
|
6
|
+
const initialPageSize = Number(props.pageSize ?? basePageSizes[0] ?? 10) || 10;
|
|
7
|
+
const pageSizes = Array.from(new Set([...basePageSizes, initialPageSize])).sort((a, b) => a - b);
|
|
8
|
+
const initialSort = props.initialSort || (props.sortBy ? { key: props.sortBy, dir: props.sortDir === "desc" ? "desc" : "asc" } : null);
|
|
9
|
+
const searchable = props.searchable === true
|
|
10
|
+
|| typeof props.searchable === "string"
|
|
11
|
+
|| props.searchPlaceholder != null
|
|
12
|
+
|| props.search != null
|
|
13
|
+
|| props.query != null
|
|
14
|
+
|| !!props.searchModel
|
|
15
|
+
|| !!props.queryModel
|
|
16
|
+
|| Array.isArray(props.searchKeys)
|
|
17
|
+
|| typeof props.searchBy === "function"
|
|
18
|
+
|| typeof props.searchPredicate === "function";
|
|
19
|
+
const searchModel = resolveModel(props.searchModel || props.queryModel, "UI.Table:query");
|
|
20
|
+
|
|
21
|
+
const [getPage, setPage] = app.reactive.signal(Math.max(1, Number(props.page || 1) || 1));
|
|
22
|
+
const [getPageSize, setPageSizeState] = app.reactive.signal(initialPageSize);
|
|
23
|
+
const [getSort, setSort] = app.reactive.signal(initialSort);
|
|
24
|
+
const [getQuery, setQueryState] = app.reactive.signal(String((searchModel ? searchModel.get() : (props.search ?? props.query)) ?? ""));
|
|
25
|
+
|
|
26
|
+
const setQuery = (value) => {
|
|
27
|
+
const next = String(value ?? "");
|
|
28
|
+
setQueryState(next);
|
|
29
|
+
if (searchModel) searchModel.set(next);
|
|
30
|
+
setPage(1);
|
|
31
|
+
};
|
|
32
|
+
const setPageSize = (value) => {
|
|
33
|
+
const next = Number(value) || initialPageSize;
|
|
34
|
+
setPageSizeState(next);
|
|
35
|
+
setPage(1);
|
|
36
|
+
};
|
|
37
|
+
const toggleSort = (col, index) => {
|
|
38
|
+
const nextKey = tableColumnSortKey(col, index);
|
|
39
|
+
const current = getSort();
|
|
40
|
+
if (!current || (current.key !== nextKey && current.key !== col?.key)) setSort({ key: nextKey, dir: "asc" });
|
|
41
|
+
else if (current.dir === "asc") setSort({ key: nextKey, dir: "desc" });
|
|
42
|
+
else setSort(null);
|
|
43
|
+
setPage(1);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let searchInput = null;
|
|
47
|
+
if (searchModel) {
|
|
48
|
+
searchModel.watch((value) => {
|
|
49
|
+
const next = String(value ?? "");
|
|
50
|
+
setQueryState(next);
|
|
51
|
+
if (searchInput && searchInput.value !== next) searchInput.value = next;
|
|
52
|
+
setPage(1);
|
|
53
|
+
}, "UI.Table:queryWatch");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const wrapProps = JSswift.omit(props, [
|
|
57
|
+
"columns", "rows", "rowKey", "loading", "page", "pageSize", "pageSizeOptions", "pagination",
|
|
58
|
+
"initialSort", "sortBy", "sortDir", "search", "query", "searchable", "searchPlaceholder",
|
|
59
|
+
"searchKeys", "searchBy", "searchPredicate", "searchModel", "queryModel", "filter",
|
|
60
|
+
"actions", "actionsLabel", "emptyText", "loadingText", "onRowClick", "onRowDblClick",
|
|
61
|
+
"tableClass", "tableStyle", "cardClass", "dense", "striped", "hover", "stickyHeader",
|
|
62
|
+
"toolbar", "toolbarStart", "toolbarEnd", "caption", "footer", "status", "rowClass", "rowAttrs",
|
|
63
|
+
"minTableWidth", "hideHeader", "hideFooter", "slots", "body"
|
|
64
|
+
]);
|
|
65
|
+
wrapProps.class = uiClass(["cms-table-card", props.class, props.cardClass]);
|
|
66
|
+
if (props.dense != null) wrapProps.dense = props.dense;
|
|
67
|
+
|
|
68
|
+
const shell = _.div({ class: "cms-table-shell" });
|
|
69
|
+
const leadNodes = renderSlotToArray(slots, "default", {}, children);
|
|
70
|
+
if (leadNodes.length) shell.appendChild(_.div({ class: "cms-table-lead" }, ...leadNodes));
|
|
71
|
+
|
|
72
|
+
const toolbarStartNodes = renderSlotToArray(slots, "toolbarStart", {}, props.toolbarStart);
|
|
73
|
+
const toolbarNodes = renderSlotToArray(slots, "toolbar", {}, props.toolbar);
|
|
74
|
+
const toolbarEndNodes = renderSlotToArray(slots, "toolbarEnd", {}, props.toolbarEnd);
|
|
75
|
+
|
|
76
|
+
const toolbar = _.div({ class: "cms-table-toolbar" });
|
|
77
|
+
const toolbarMain = _.div({ class: "cms-table-toolbar-main" });
|
|
78
|
+
const toolbarSide = _.div({ class: "cms-table-toolbar-side" });
|
|
79
|
+
toolbarStartNodes.forEach((node) => toolbarMain.appendChild(node));
|
|
80
|
+
toolbarNodes.forEach((node) => toolbarMain.appendChild(node));
|
|
81
|
+
|
|
82
|
+
const searchSlotNodes = renderSlotToArray(slots, "search", { query: getQuery(), getQuery, setQuery }, null);
|
|
83
|
+
if (searchSlotNodes.length) {
|
|
84
|
+
searchSlotNodes.forEach((node) => toolbarSide.appendChild(node));
|
|
85
|
+
} else if (searchable) {
|
|
86
|
+
searchInput = _.input({
|
|
87
|
+
type: "search",
|
|
88
|
+
class: "cms-input",
|
|
89
|
+
placeholder: typeof props.searchable === "string" ? props.searchable : (props.searchPlaceholder || "Cerca nella tabella"),
|
|
90
|
+
value: String(getQuery() || "")
|
|
91
|
+
});
|
|
92
|
+
searchInput.addEventListener("input", () => setQuery(searchInput.value));
|
|
93
|
+
const clearSearch = UI.Btn({
|
|
94
|
+
class: "cms-table-search-clear",
|
|
95
|
+
outline: true,
|
|
96
|
+
onClick: () => {
|
|
97
|
+
if (searchInput) searchInput.value = "";
|
|
98
|
+
setQuery("");
|
|
99
|
+
}
|
|
100
|
+
}, "Reset");
|
|
101
|
+
toolbarSide.appendChild(
|
|
102
|
+
_.div({ class: "cms-singularity cms-table-search" },
|
|
103
|
+
_.span({ class: "cms-table-search-icon", "aria-hidden": "true" }, "⌕"),
|
|
104
|
+
searchInput,
|
|
105
|
+
clearSearch
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
toolbarEndNodes.forEach((node) => toolbarSide.appendChild(node));
|
|
110
|
+
if (toolbarMain.childNodes.length || toolbarSide.childNodes.length) {
|
|
111
|
+
toolbar.appendChild(toolbarMain);
|
|
112
|
+
toolbar.appendChild(toolbarSide);
|
|
113
|
+
shell.appendChild(toolbar);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const statusSummary = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
117
|
+
const statusFilter = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
118
|
+
const statusSort = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
119
|
+
const statusExtraNodes = renderSlotToArray(slots, "status", {}, props.status);
|
|
120
|
+
const status = _.div({ class: "cms-table-status" },
|
|
121
|
+
statusSummary,
|
|
122
|
+
statusFilter,
|
|
123
|
+
statusSort,
|
|
124
|
+
...statusExtraNodes
|
|
125
|
+
);
|
|
126
|
+
shell.appendChild(status);
|
|
127
|
+
|
|
128
|
+
const captionNodes = renderSlotToArray(slots, "caption", {}, props.caption);
|
|
129
|
+
if (captionNodes.length) shell.appendChild(_.div({ class: "cms-table-caption" }, ...captionNodes));
|
|
130
|
+
|
|
131
|
+
const thead = _.thead();
|
|
132
|
+
const tbody = _.tbody();
|
|
133
|
+
const hasActions = !!props.actions || !!slots.actions;
|
|
134
|
+
|
|
135
|
+
const tableClass = uiClass([
|
|
136
|
+
"cms-table",
|
|
137
|
+
uiWhen(props.dense, "dense"),
|
|
138
|
+
uiWhen(props.striped, "striped"),
|
|
139
|
+
uiWhen(props.hover !== false, "hover"),
|
|
140
|
+
uiWhen(props.stickyHeader !== false, "sticky-head"),
|
|
141
|
+
props.tableClass
|
|
142
|
+
]);
|
|
143
|
+
const tableStyle = {
|
|
144
|
+
...(props.tableStyle || {})
|
|
145
|
+
};
|
|
146
|
+
if (props.minTableWidth != null) tableStyle["--cms-table-min-width"] = toCssSize(props.minTableWidth);
|
|
147
|
+
const table = _.table({ class: tableClass, style: tableStyle }, thead, tbody);
|
|
148
|
+
const wrapTable = _.div({ class: "cms-singularity cms-table-wrap" }, table);
|
|
149
|
+
shell.appendChild(wrapTable);
|
|
150
|
+
|
|
151
|
+
const pagerInfo = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
152
|
+
const pagerMeta = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
153
|
+
const btnPrev = UI.Btn({ outline: true, onClick: () => setPage(Math.max(1, getPage() - 1)) }, "‹");
|
|
154
|
+
const btnNext = UI.Btn({ outline: true, onClick: () => setPage(getPage() + 1) }, "›");
|
|
155
|
+
const pageChip = _.div({ class: "cms-singularity cms-table-chip" }, "");
|
|
156
|
+
const sizeSelect = _.select({ class: "cms-input cms-table-size-select" },
|
|
157
|
+
...pageSizes.map((size) => uiOptionNode({ value: String(size) }, String(size)))
|
|
158
|
+
);
|
|
159
|
+
sizeSelect.value = String(getPageSize());
|
|
160
|
+
sizeSelect.addEventListener("change", () => setPageSize(sizeSelect.value));
|
|
161
|
+
const footerExtraNodes = renderSlotToArray(slots, "footer", {}, props.footer);
|
|
162
|
+
|
|
163
|
+
const footer = _.div({ class: "cms-table-foot" },
|
|
164
|
+
_.div({ class: "cms-table-foot-main" },
|
|
165
|
+
pagerInfo,
|
|
166
|
+
pagerMeta,
|
|
167
|
+
_.label({ class: "cms-table-size" },
|
|
168
|
+
_.span("Righe"),
|
|
169
|
+
sizeSelect
|
|
170
|
+
)
|
|
171
|
+
),
|
|
172
|
+
_.div({ class: "cms-table-foot-extra" },
|
|
173
|
+
...footerExtraNodes,
|
|
174
|
+
_.div({ class: "cms-table-pager" },
|
|
175
|
+
btnPrev,
|
|
176
|
+
pageChip,
|
|
177
|
+
btnNext
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
if (props.hideFooter !== true) shell.appendChild(footer);
|
|
182
|
+
|
|
183
|
+
const renderHeader = () => {
|
|
184
|
+
thead.innerHTML = "";
|
|
185
|
+
if (props.hideHeader) return;
|
|
186
|
+
|
|
187
|
+
const row = _.tr();
|
|
188
|
+
columns.forEach((col, colIndex) => {
|
|
189
|
+
const ctx = { col, colIndex, sort: getSort() };
|
|
190
|
+
const thStyle = {
|
|
191
|
+
...tableResolveStyle(col?.style, { ...ctx, header: true }),
|
|
192
|
+
...tableResolveStyle(col?.headerStyle ?? col?.thStyle, { ...ctx, header: true })
|
|
193
|
+
};
|
|
194
|
+
if (col?.width != null) thStyle.width = toCssSize(col.width);
|
|
195
|
+
if (col?.minWidth != null) thStyle.minWidth = toCssSize(col.minWidth);
|
|
196
|
+
if (col?.maxWidth != null) thStyle.maxWidth = toCssSize(col.maxWidth);
|
|
197
|
+
if (col?.align) thStyle.textAlign = col.align;
|
|
198
|
+
let headerNodes = renderSlotToArray(col?.slots, "header", ctx, null);
|
|
199
|
+
if (!headerNodes.length) {
|
|
200
|
+
const fallback = typeof col?.header === "function" ? col.header(ctx) : (col?.header ?? tableColumnLabel(col));
|
|
201
|
+
headerNodes = renderSlotToArray(slots, "header", ctx, fallback);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const currentSort = getSort();
|
|
205
|
+
const sortKey = tableColumnSortKey(col, colIndex);
|
|
206
|
+
const isSortable = col?.sortable !== false && (col?.key != null || typeof col?.get === "function" || typeof col?.compare === "function");
|
|
207
|
+
const isActive = !!currentSort && (currentSort.key === sortKey || currentSort.key === col?.key);
|
|
208
|
+
const th = _.th({
|
|
209
|
+
class: uiClass([
|
|
210
|
+
"cms-table-head-cell",
|
|
211
|
+
uiWhen(col?.nowrap, "is-nowrap"),
|
|
212
|
+
uiWhen(isSortable, "cms-table-sortable"),
|
|
213
|
+
uiWhen(isActive, "is-sorted"),
|
|
214
|
+
col?.headerClass
|
|
215
|
+
]),
|
|
216
|
+
style: thStyle
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (isSortable) {
|
|
220
|
+
th.appendChild(
|
|
221
|
+
_.button({
|
|
222
|
+
type: "button",
|
|
223
|
+
class: "cms-table-head-button",
|
|
224
|
+
onClick: () => toggleSort(col, colIndex)
|
|
225
|
+
},
|
|
226
|
+
_.span({ class: "cms-table-head-label" }, ...headerNodes),
|
|
227
|
+
_.span({ class: "cms-table-head-arrow", "aria-hidden": "true" },
|
|
228
|
+
isActive ? (currentSort.dir === "asc" ? "↑" : "↓") : "↕"
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
} else {
|
|
233
|
+
headerNodes.forEach((node) => th.appendChild(node));
|
|
234
|
+
}
|
|
235
|
+
row.appendChild(th);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (hasActions) {
|
|
239
|
+
const actionsHeaderNodes = renderSlotToArray(slots, "actionsHeader", {}, props.actionsLabel || "Azioni");
|
|
240
|
+
row.appendChild(
|
|
241
|
+
_.th({ class: "cms-table-head-cell cms-table-head-actions", style: { textAlign: "right" } }, ...actionsHeaderNodes)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
thead.appendChild(row);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const renderStateRow = (slotName, fallback) => {
|
|
248
|
+
const ctx = { columns, count: columns.length };
|
|
249
|
+
const nodes = renderSlotToArray(slots, slotName, ctx, fallback);
|
|
250
|
+
const colSpan = String(columns.length + (hasActions ? 1 : 0));
|
|
251
|
+
tbody.appendChild(
|
|
252
|
+
_.tr(
|
|
253
|
+
_.td({ colSpan, class: "cms-table-state" }, ...nodes)
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
app.reactive.effect(() => {
|
|
259
|
+
const rows = tableToArray(props.rows);
|
|
260
|
+
const query = String(getQuery() || "").trim();
|
|
261
|
+
const currentSort = getSort();
|
|
262
|
+
const loading = typeof props.loading === "function" ? !!props.loading() : !!props.loading;
|
|
263
|
+
const paginationEnabled = props.pagination !== false;
|
|
264
|
+
|
|
265
|
+
renderHeader();
|
|
266
|
+
tbody.innerHTML = "";
|
|
267
|
+
if (searchInput && searchInput.value !== getQuery()) searchInput.value = getQuery();
|
|
268
|
+
if (sizeSelect.value !== String(getPageSize())) sizeSelect.value = String(getPageSize());
|
|
269
|
+
|
|
270
|
+
if (loading) {
|
|
271
|
+
statusSummary.textContent = "Caricamento in corso";
|
|
272
|
+
statusFilter.style.display = "none";
|
|
273
|
+
statusSort.style.display = "none";
|
|
274
|
+
pagerInfo.textContent = "Loading…";
|
|
275
|
+
pagerMeta.textContent = "";
|
|
276
|
+
pageChip.textContent = "Pagina 1";
|
|
277
|
+
btnPrev.disabled = true;
|
|
278
|
+
btnNext.disabled = true;
|
|
279
|
+
renderStateRow("loading", props.loadingText || "Loading...");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let list = rows.slice();
|
|
284
|
+
const datasetTotal = list.length;
|
|
285
|
+
if (typeof props.filter === "function") {
|
|
286
|
+
list = list.filter((row, index) => props.filter(row, { row, index, query, rows, columns }));
|
|
287
|
+
}
|
|
288
|
+
if (query) {
|
|
289
|
+
list = list.filter((row) => tableMatchesQuery(row, columns, query, props));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (currentSort) {
|
|
293
|
+
const sortCol = tableFindColumn(columns, currentSort.key);
|
|
294
|
+
if (sortCol) {
|
|
295
|
+
list.sort((a, b) => {
|
|
296
|
+
const av = tableResolveValue(sortCol, a, -1);
|
|
297
|
+
const bv = tableResolveValue(sortCol, b, -1);
|
|
298
|
+
const out = typeof sortCol.compare === "function"
|
|
299
|
+
? sortCol.compare(av, bv, a, b)
|
|
300
|
+
: defaultCompare(av, bv);
|
|
301
|
+
return currentSort.dir === "asc" ? out : -out;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const filteredTotal = list.length;
|
|
307
|
+
const currentPageSize = Math.max(1, Number(getPageSize()) || initialPageSize);
|
|
308
|
+
const pages = paginationEnabled ? Math.max(1, Math.ceil(filteredTotal / currentPageSize)) : 1;
|
|
309
|
+
const page = paginationEnabled ? Math.min(getPage(), pages) : 1;
|
|
310
|
+
if (page !== getPage()) setPage(page);
|
|
311
|
+
|
|
312
|
+
const start = paginationEnabled ? (page - 1) * currentPageSize : 0;
|
|
313
|
+
const end = paginationEnabled ? start + currentPageSize : filteredTotal;
|
|
314
|
+
const pageRows = list.slice(start, end);
|
|
315
|
+
|
|
316
|
+
statusSummary.textContent = filteredTotal === datasetTotal
|
|
317
|
+
? `${filteredTotal} righe`
|
|
318
|
+
: `${filteredTotal} di ${datasetTotal} righe`;
|
|
319
|
+
statusFilter.textContent = query ? `Ricerca: ${query}` : "";
|
|
320
|
+
statusFilter.style.display = query ? "" : "none";
|
|
321
|
+
if (currentSort) {
|
|
322
|
+
const sortCol = tableFindColumn(columns, currentSort.key);
|
|
323
|
+
statusSort.textContent = sortCol
|
|
324
|
+
? `Ordine: ${tableColumnLabel(sortCol)} ${currentSort.dir === "asc" ? "↑" : "↓"}`
|
|
325
|
+
: "";
|
|
326
|
+
statusSort.style.display = sortCol ? "" : "none";
|
|
327
|
+
} else {
|
|
328
|
+
statusSort.style.display = "none";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
pagerInfo.textContent = filteredTotal
|
|
332
|
+
? `${start + 1}-${Math.min(end, filteredTotal)} di ${filteredTotal}`
|
|
333
|
+
: "0 risultati";
|
|
334
|
+
pagerMeta.textContent = filteredTotal !== datasetTotal ? `Dataset totale ${datasetTotal}` : `Page size ${currentPageSize}`;
|
|
335
|
+
pageChip.textContent = paginationEnabled ? `Pagina ${page} / ${pages}` : "Tutte le righe";
|
|
336
|
+
btnPrev.disabled = !paginationEnabled || page <= 1;
|
|
337
|
+
btnNext.disabled = !paginationEnabled || page >= pages;
|
|
338
|
+
sizeSelect.disabled = !paginationEnabled;
|
|
339
|
+
sizeSelect.parentNode.style.display = paginationEnabled ? "" : "none";
|
|
340
|
+
|
|
341
|
+
if (pageRows.length === 0) {
|
|
342
|
+
renderStateRow("empty", props.emptyText || "Nessun dato");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
for (let pageIndex = 0; pageIndex < pageRows.length; pageIndex++) {
|
|
347
|
+
const row = pageRows[pageIndex];
|
|
348
|
+
const rowIndex = start + pageIndex;
|
|
349
|
+
const rowCtx = { row, rowIndex, pageIndex };
|
|
350
|
+
const rowAttrs = typeof props.rowAttrs === "function" ? (props.rowAttrs(row, rowCtx) || {}) : (props.rowAttrs || {});
|
|
351
|
+
const rowClass = typeof props.rowClass === "function" ? props.rowClass(row, rowCtx) : props.rowClass;
|
|
352
|
+
const isInteractiveRow = !!(props.onRowClick || props.onRowDblClick);
|
|
353
|
+
const tr = _.tr({
|
|
354
|
+
...rowAttrs,
|
|
355
|
+
class: uiClass([
|
|
356
|
+
"cms-table-row",
|
|
357
|
+
rowClass,
|
|
358
|
+
rowAttrs.class,
|
|
359
|
+
uiWhen(isInteractiveRow, "is-clickable")
|
|
360
|
+
])
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
if (props.rowKey) {
|
|
364
|
+
const key = typeof props.rowKey === "function" ? props.rowKey(row, rowCtx) : tableGetByPath(row, props.rowKey);
|
|
365
|
+
if (key != null) tr.dataset.key = String(key);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const shouldIgnoreRowEvent = (event) => {
|
|
369
|
+
const target = event?.target;
|
|
370
|
+
return !!(target && target.closest && target.closest("button, a, input, select, textarea, label, [data-table-action]"));
|
|
371
|
+
};
|
|
372
|
+
if (props.onRowClick) {
|
|
373
|
+
tr.addEventListener("click", (event) => {
|
|
374
|
+
if (shouldIgnoreRowEvent(event)) return;
|
|
375
|
+
props.onRowClick(row, rowCtx, event);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (props.onRowDblClick) {
|
|
379
|
+
tr.addEventListener("dblclick", (event) => {
|
|
380
|
+
if (shouldIgnoreRowEvent(event)) return;
|
|
381
|
+
props.onRowDblClick(row, rowCtx, event);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
columns.forEach((col, colIndex) => {
|
|
386
|
+
const value = tableResolveValue(col, row, rowIndex);
|
|
387
|
+
const cellCtx = { ...rowCtx, col, colIndex, value };
|
|
388
|
+
const tdStyle = {
|
|
389
|
+
...tableResolveStyle(col?.style, cellCtx),
|
|
390
|
+
...tableResolveStyle(col?.cellStyle ?? col?.tdStyle, cellCtx)
|
|
391
|
+
};
|
|
392
|
+
if (col?.align) tdStyle.textAlign = col.align;
|
|
393
|
+
if (col?.width != null) tdStyle.width = toCssSize(col.width);
|
|
394
|
+
if (col?.minWidth != null) tdStyle.minWidth = toCssSize(col.minWidth);
|
|
395
|
+
if (col?.maxWidth != null) tdStyle.maxWidth = toCssSize(col.maxWidth);
|
|
396
|
+
const cellClass = typeof col?.cellClass === "function" ? col.cellClass(row, cellCtx) : (col?.cellClass || col?.class);
|
|
397
|
+
const td = _.td({
|
|
398
|
+
class: uiClass(["cms-table-cell", cellClass, uiWhen(col?.nowrap, "is-nowrap")]),
|
|
399
|
+
style: tdStyle
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
let cellNodes = renderSlotToArray(col?.slots, "cell", cellCtx, null);
|
|
403
|
+
if (!cellNodes.length) {
|
|
404
|
+
const raw = typeof col?.render === "function"
|
|
405
|
+
? col.render(row, cellCtx)
|
|
406
|
+
: (typeof col?.format === "function" ? col.format(value, row, cellCtx) : value);
|
|
407
|
+
const fallback = raw == null ? (col?.emptyText ?? "") : raw;
|
|
408
|
+
cellNodes = renderSlotToArray(slots, "cell", cellCtx, fallback);
|
|
409
|
+
}
|
|
410
|
+
if (!cellNodes.length) cellNodes = [""];
|
|
411
|
+
cellNodes.forEach((node) => td.appendChild(node));
|
|
412
|
+
tr.appendChild(td);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (hasActions) {
|
|
416
|
+
const actionCtx = { ...rowCtx };
|
|
417
|
+
const actionRaw = typeof props.actions === "function" ? props.actions(row, rowCtx) : props.actions;
|
|
418
|
+
let actionNodes = renderSlotToArray(slots, "actions", actionCtx, actionRaw);
|
|
419
|
+
if (!actionNodes.length) actionNodes = renderSlotToArray(null, "default", actionCtx, actionRaw);
|
|
420
|
+
tr.appendChild(
|
|
421
|
+
_.td({ class: "cms-table-cell cms-table-cell-actions", style: { textAlign: "right" } },
|
|
422
|
+
_.div({ class: "cms-table-actions" }, ...actionNodes)
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
tbody.appendChild(tr);
|
|
428
|
+
}
|
|
429
|
+
}, "UI.Table:render");
|
|
430
|
+
|
|
431
|
+
wrapProps.body = shell;
|
|
432
|
+
return UI.Card(wrapProps);
|
|
433
|
+
};
|
|
434
|
+
if (JSswift.isDev?.()) {
|
|
435
|
+
UI.meta = UI.meta || {};
|
|
436
|
+
UI.meta.Table = {
|
|
437
|
+
signature: "UI.Table(props)",
|
|
438
|
+
props: {
|
|
439
|
+
columns: "Array<{ key, label?, sortable?, get?, value?, render?, format?, width?, minWidth?, maxWidth?, align?, compare?, style?, headerStyle?, thStyle?, cellStyle?, tdStyle?, cellClass?, headerClass?, nowrap?, searchable? }>",
|
|
440
|
+
rows: "Array|() => Array",
|
|
441
|
+
rowKey: "string|((row)=>string)",
|
|
442
|
+
loading: "boolean|() => boolean",
|
|
443
|
+
page: "number",
|
|
444
|
+
pageSize: "number",
|
|
445
|
+
pageSizeOptions: "number[]",
|
|
446
|
+
pagination: "boolean",
|
|
447
|
+
initialSort: "{ key, dir: 'asc'|'desc' }",
|
|
448
|
+
search: "string",
|
|
449
|
+
query: "string",
|
|
450
|
+
searchable: "boolean|string",
|
|
451
|
+
searchPlaceholder: "string",
|
|
452
|
+
searchKeys: "Array<string|function>",
|
|
453
|
+
searchModel: "[get,set] signal",
|
|
454
|
+
filter: "(row, ctx)=>boolean",
|
|
455
|
+
actions: "(row, ctx)=>Node|Array",
|
|
456
|
+
actionsLabel: "string|Node|Function|Array",
|
|
457
|
+
emptyText: "string|Node|Function|Array",
|
|
458
|
+
loadingText: "string|Node|Function|Array",
|
|
459
|
+
toolbar: "Node|Function|Array",
|
|
460
|
+
toolbarStart: "Node|Function|Array",
|
|
461
|
+
toolbarEnd: "Node|Function|Array",
|
|
462
|
+
caption: "string|Node|Function|Array",
|
|
463
|
+
footer: "Node|Function|Array",
|
|
464
|
+
status: "string|Node|Function|Array",
|
|
465
|
+
rowClass: "string|((row,ctx)=>string)",
|
|
466
|
+
rowAttrs: "object|((row,ctx)=>object)",
|
|
467
|
+
minTableWidth: "string|number",
|
|
468
|
+
stickyHeader: "boolean",
|
|
469
|
+
hideHeader: "boolean",
|
|
470
|
+
hideFooter: "boolean",
|
|
471
|
+
dense: "boolean",
|
|
472
|
+
striped: "boolean",
|
|
473
|
+
hover: "boolean",
|
|
474
|
+
tableClass: "string",
|
|
475
|
+
cardClass: "string",
|
|
476
|
+
class: "string",
|
|
477
|
+
style: "object"
|
|
478
|
+
},
|
|
479
|
+
slots: {
|
|
480
|
+
default: "Introductory content above the table",
|
|
481
|
+
toolbarStart: "Toolbar left area",
|
|
482
|
+
toolbar: "Central/custom toolbar",
|
|
483
|
+
toolbarEnd: "Toolbar right area",
|
|
484
|
+
search: "Replaces the built-in search box",
|
|
485
|
+
header: "Custom column header",
|
|
486
|
+
cell: "Global cell renderer",
|
|
487
|
+
actions: "Global row actions renderer",
|
|
488
|
+
actionsHeader: "Actions column header",
|
|
489
|
+
caption: "Caption above the table",
|
|
490
|
+
status: "Extra content in the status row",
|
|
491
|
+
loading: "Loading state",
|
|
492
|
+
empty: "Empty state",
|
|
493
|
+
footer: "Extra content in the footer"
|
|
494
|
+
},
|
|
495
|
+
events: {
|
|
496
|
+
onRowClick: "(row, ctx, event) => void",
|
|
497
|
+
onRowDblClick: "(row, ctx, event) => void"
|
|
498
|
+
},
|
|
499
|
+
returns: "HTMLDivElement",
|
|
500
|
+
description: "Standardized table with toolbar, search, sorting, pagination, states, and flexible rendering."
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
package/src/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
## UI Source Modules
|
|
2
|
+
|
|
3
|
+
This folder contains the modular source structure of the JSswift UI layer.
|
|
4
|
+
|
|
5
|
+
Current status:
|
|
6
|
+
|
|
7
|
+
- a dedicated build exists: `npm run build:ui`
|
|
8
|
+
- the old `ui.js` monolith has been split into modules under `packages/ui/src/*`
|
|
9
|
+
- `99-legacy-ui.js` is no longer used in the build
|
|
10
|
+
- public runtime output is generated into `packages/ui/dist/`
|
|
11
|
+
- a compatibility mirror remains in `pages/_jsswift-fe/js/`
|
|
12
|
+
|
|
13
|
+
Manifest:
|
|
14
|
+
|
|
15
|
+
- `modules.json`
|
|
16
|
+
|
|
17
|
+
Build command:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm run build:ui
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Responsive CSS command:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node cms-dev/make-responsive-css.mjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Generated output:
|
|
30
|
+
|
|
31
|
+
- `packages/ui/dist/ui.js`
|
|
32
|
+
- `packages/ui/dist/min-ui.js`
|
|
33
|
+
- CSS, font, and image assets in `packages/ui/dist/`
|
|
34
|
+
|
|
35
|
+
Responsive source of truth:
|
|
36
|
+
|
|
37
|
+
- responsive CSS is generated, not edited by hand
|
|
38
|
+
- edit `cms-dev/make-responsive-css.mjs`
|
|
39
|
+
- run `node cms-dev/make-responsive-css.mjs`
|
|
40
|
+
- then run `npm run build:ui` and `npm run build:jsswift`
|
|
41
|
+
- generated package files:
|
|
42
|
+
- `packages/ui/dist/css/responsive.css`
|
|
43
|
+
- `packages/jsswift/dist/css/responsive.css`
|
|
44
|
+
|
|
45
|
+
Responsive component contract:
|
|
46
|
+
|
|
47
|
+
- mobile/default values live at the root of the props object
|
|
48
|
+
- `mobile`, `tablet`, and `pc` objects override only the props they contain
|
|
49
|
+
- `tablet` starts at `768px`
|
|
50
|
+
- `pc` starts at `1024px`
|
|
51
|
+
- responsive props are omitted from DOM attributes by the shared `JSswift.omit`
|
|
52
|
+
helper
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
_.Row({
|
|
58
|
+
direction: "column",
|
|
59
|
+
gap: "sm",
|
|
60
|
+
tablet: { direction: "row", gap: "md" },
|
|
61
|
+
pc: { justify: "space-between" }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
_.Col({
|
|
65
|
+
col: 24,
|
|
66
|
+
tablet: { col: 12 },
|
|
67
|
+
pc: { col: 6 }
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
_.Btn({
|
|
71
|
+
width: "100%",
|
|
72
|
+
tablet: { width: "220px" },
|
|
73
|
+
pc: { width: "320px" }
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Implementation notes:
|
|
78
|
+
|
|
79
|
+
- token values become generated classes such as `cms-tablet-row`,
|
|
80
|
+
`cms-pc-col-6`, or `cms-tablet-gap-md`
|
|
81
|
+
- arbitrary values become `.cms-rsp` custom properties such as
|
|
82
|
+
`--cms-rsp-tablet-width`
|
|
83
|
+
- `.cms-rsp-*` activation classes are emitted per property and breakpoint; this
|
|
84
|
+
keeps undefined custom properties out of the cascade and still lets responsive
|
|
85
|
+
values override defaults from `ui-components.css`
|
|
86
|
+
- grid child rules use `.cms-grid.cms-grid > .cms-grid-col` for the same reason
|
|
87
|
+
- layout primitives use `JSswift.uiResponsiveStyleRules`
|
|
88
|
+
- generic/shared props use `JSswift.uiResponsiveCommonStyleRules` through
|
|
89
|
+
`setPropertyProps`
|
|
90
|
+
|
|
91
|
+
Recommended module order:
|
|
92
|
+
|
|
93
|
+
- `00-bootstrap.js`
|
|
94
|
+
- `01-foundation-helpers.js`
|
|
95
|
+
- `10-primitives-layout.js`
|
|
96
|
+
- `20-display-content.js`
|
|
97
|
+
- `21-list-content.js`
|
|
98
|
+
- `22-banner-content.js`
|
|
99
|
+
- `23-tooltip.js`
|
|
100
|
+
- `31-form-advanced.js`
|
|
101
|
+
- `40-navigation.js`
|
|
102
|
+
- `41-tab-panel.js`
|
|
103
|
+
- `50-feedback.js`
|
|
104
|
+
- `51-feedback-service.js`
|
|
105
|
+
- `60-shell-app.js`
|
|
106
|
+
- `61-form-service.js`
|
|
107
|
+
- `70-dialog.js`
|
|
108
|
+
- `71-menu-overlays.js`
|
|
109
|
+
- `80-data.js`
|
|
110
|
+
|
|
111
|
+
Planned families:
|
|
112
|
+
|
|
113
|
+
- bootstrap/helpers:
|
|
114
|
+
rod path proxy, argument normalization, shared helpers, meta/dev wrapping
|
|
115
|
+
- primitives/layout:
|
|
116
|
+
`Row`, `Col`, `Spacer`, `Container`, `Card`, `Footer`, `Toolbar`, `Grid`, `GridCol`, `FormField`, `InputRaw`, `Input`, `Textarea`, `Select`
|
|
117
|
+
- display/content:
|
|
118
|
+
`Icon`, `Badge`, `Avatar`, `Chip`
|
|
119
|
+
- list/content:
|
|
120
|
+
`List`, `Item`, `Separator`
|
|
121
|
+
- banner/content:
|
|
122
|
+
`Banner`
|
|
123
|
+
- tooltip:
|
|
124
|
+
`Tooltip`
|
|
125
|
+
- advanced form:
|
|
126
|
+
`Checkbox`, `Radio`, `Toggle`, `Slider`, `Rating`, `Date`, `Time`
|
|
127
|
+
- navigation:
|
|
128
|
+
`Tabs`, `RouteTab`, `Breadcrumbs`, `Pagination`
|
|
129
|
+
- tab panel:
|
|
130
|
+
`TabPanel`
|
|
131
|
+
- docs:
|
|
132
|
+
`ComponentDocs`
|
|
133
|
+
- feedback:
|
|
134
|
+
`Spinner`, `Progress`, `LoadingBar`, `Notify`
|
|
135
|
+
- feedback service:
|
|
136
|
+
toast root, timers, update/remove/clear, notify shortcuts
|
|
137
|
+
- shell/app:
|
|
138
|
+
`Header`, `Drawer`, `Page`, `AppShell`, `Parallax`
|
|
139
|
+
- form/service:
|
|
140
|
+
`JSswift.form`, `JSswift.useForm`, `Form`, `cardHeader`, `cardBody`, `cardFooter`, dialog bootstrap
|
|
141
|
+
- dialog:
|
|
142
|
+
`Dialog`
|
|
143
|
+
- menu/overlays:
|
|
144
|
+
`Menu`, `Popover`, `ContextMenu`
|
|
145
|
+
- data:
|
|
146
|
+
`Table`
|
|
147
|
+
|
|
148
|
+
Rules:
|
|
149
|
+
|
|
150
|
+
- migrate one family at a time
|
|
151
|
+
- rebuild the UI package
|
|
152
|
+
- verify against the relevant demos
|
|
153
|
+
- update `docs/reference/ui.md`
|
|
154
|
+
|
|
155
|
+
Notes:
|
|
156
|
+
|
|
157
|
+
- `packages/ui/src/` is the source of truth
|
|
158
|
+
- `99-legacy-ui.js` has been removed; the bundle is built only from the manifest modules
|
|
159
|
+
- when a family was not contiguous in the legacy file, the migration followed the real source order
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
JSswift UI CSS source files.
|
|
2
|
+
|
|
3
|
+
Edit CSS here, not in `packages/ui/dist/css`.
|
|
4
|
+
|
|
5
|
+
Build outputs are generated with:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run build:ui:css
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Generated outputs:
|
|
12
|
+
|
|
13
|
+
- `packages/ui/dist/css/*` for npm packages
|
|
14
|
+
- `pages/_jsswift-fe/css/ui.css`
|
|
15
|
+
- `pages/_jsswift-fe/css/min-ui.css`
|
|
16
|
+
- `pages/_jsswift-fe/css/ui.min.css`
|
|
17
|
+
|