@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.
Files changed (55) hide show
  1. package/dist/css/animation.css +121 -0
  2. package/dist/css/base.css +621 -0
  3. package/dist/css/docs.css +216 -0
  4. package/dist/css/min-ui.css +1 -0
  5. package/dist/css/responsive.css +3259 -0
  6. package/dist/css/ui-components.css +8757 -0
  7. package/dist/css/ui.css +12765 -0
  8. package/dist/css/ui.min.css +1 -0
  9. package/dist/fonts/Nunito-Black.ttf +0 -0
  10. package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
  11. package/dist/fonts/Nunito-Bold.ttf +0 -0
  12. package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
  13. package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
  14. package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
  15. package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
  16. package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
  17. package/dist/fonts/Nunito-Italic.ttf +0 -0
  18. package/dist/fonts/Nunito-Light.ttf +0 -0
  19. package/dist/fonts/Nunito-LightItalic.ttf +0 -0
  20. package/dist/fonts/Nunito-Medium.ttf +0 -0
  21. package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
  22. package/dist/fonts/Nunito-Regular.ttf +0 -0
  23. package/dist/fonts/Nunito-SemiBold.ttf +0 -0
  24. package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
  25. package/dist/img/logo.svg +131 -0
  26. package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
  27. package/dist/min-ui.js +34 -0
  28. package/dist/ui.js +21027 -0
  29. package/dist/ui.min.js +34 -0
  30. package/package.json +40 -0
  31. package/src/00-bootstrap.js +330 -0
  32. package/src/01-foundation-helpers.js +1208 -0
  33. package/src/10-primitives-layout.js +5972 -0
  34. package/src/20-display-content.js +1014 -0
  35. package/src/21-list-content.js +341 -0
  36. package/src/22-banner-content.js +489 -0
  37. package/src/23-tooltip.js +326 -0
  38. package/src/31-form-advanced.js +3149 -0
  39. package/src/40-navigation.js +1454 -0
  40. package/src/41-tab-panel.js +1090 -0
  41. package/src/50-feedback.js +1209 -0
  42. package/src/51-feedback-service.js +166 -0
  43. package/src/60-shell-app.js +1483 -0
  44. package/src/61-form-service.js +450 -0
  45. package/src/70-dialog.js +401 -0
  46. package/src/71-menu-overlays.js +1442 -0
  47. package/src/80-data.js +503 -0
  48. package/src/README.md +159 -0
  49. package/src/css/README.md +17 -0
  50. package/src/css/animation.css +121 -0
  51. package/src/css/base.css +621 -0
  52. package/src/css/docs.css +216 -0
  53. package/src/css/responsive.css +3259 -0
  54. package/src/css/ui-components.css +8757 -0
  55. 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
+