@zeyos/zx 4.3.4 → 4.4.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/CHANGELOG.md +155 -0
- package/README.md +7 -0
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +195 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +28 -0
- package/dist/types/components/button/button.d.ts +8 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -0
- package/dist/types/components/card/card.d.ts +7 -0
- package/dist/types/components/card-view/card-view.d.ts +11 -3
- package/dist/types/components/card-view/record-card.d.ts +7 -1
- package/dist/types/components/code-editor/code-editor.d.ts +160 -0
- package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +18 -2
- package/dist/types/components/date-picker/date-picker.d.ts +8 -0
- package/dist/types/components/date-picker/time-picker.d.ts +7 -0
- package/dist/types/components/dialog/dialog.d.ts +29 -0
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/file-item/file-item.d.ts +234 -0
- package/dist/types/components/file-list/file-list.d.ts +171 -0
- package/dist/types/components/filter/filter.d.ts +91 -36
- package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
- package/dist/types/components/kanban-view/kanban-card.d.ts +138 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
- package/dist/types/components/launcher/launcher.d.ts +19 -12
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +4 -37
- package/dist/types/components/message/message.d.ts +7 -0
- package/dist/types/components/modal/modal.d.ts +48 -0
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
- package/dist/types/components/panel/panel.d.ts +7 -0
- package/dist/types/components/search/search.d.ts +7 -0
- package/dist/types/components/select/select.d.ts +4 -0
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
- package/dist/types/components/tabbox/tabbox.d.ts +7 -0
- package/dist/types/components/table/table.d.ts +144 -0
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/timebox/timebox.d.ts +7 -0
- package/dist/types/components/value-list/value-list.d.ts +8 -0
- package/dist/types/components/view/record-view.d.ts +96 -5
- package/dist/types/core/icons.d.ts +17 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- package/dist/zx-zeyos.esm.js +616 -77
- package/dist/zx-zeyos.esm.min.js +8 -1
- package/dist/zx.css +2085 -238
- package/dist/zx.esm.js +6704 -1542
- package/dist/zx.esm.min.js +10 -3
- package/dist/zx.global.js +6704 -1542
- package/dist/zx.global.min.js +10 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1479 -127
- package/docs/llms.md +315 -10
- package/docs/llms.txt +11 -5
- package/package.json +1 -1
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +599 -0
- package/src/components/activity-list/activity-list.css +64 -0
- package/src/components/activity-list/activity-list.js +375 -0
- package/src/components/app-rail/app-rail.css +227 -19
- package/src/components/app-sidebar/app-sidebar.css +414 -24
- package/src/components/app-sidebar/app-sidebar.js +65 -5
- package/src/components/button/button.css +8 -0
- package/src/components/button/button.js +6 -1
- package/src/components/calendar/calendar.js +33 -3
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +1 -0
- package/src/components/card-view/card-view.js +25 -9
- package/src/components/card-view/record-card.js +7 -3
- package/src/components/code-editor/code-editor.css +67 -0
- package/src/components/code-editor/code-editor.js +288 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +80 -3
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.js +20 -8
- package/src/components/dialog/dialog.js +88 -66
- package/src/components/dropdown/dropdown.js +2 -2
- package/src/components/entity-ref/entity-ref.css +182 -0
- package/src/components/entity-ref/entity-ref.js +496 -0
- package/src/components/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +371 -0
- package/src/components/file-list/file-list.css +51 -0
- package/src/components/file-list/file-list.js +320 -0
- package/src/components/filter/filter.css +27 -1
- package/src/components/filter/filter.js +199 -45
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/kanban-view/kanban-card.js +239 -0
- package/src/components/kanban-view/kanban-view.css +194 -0
- package/src/components/kanban-view/kanban-view.js +251 -8
- package/src/components/launcher/launcher.js +59 -27
- package/src/components/list-toolbar/list-toolbar.css +189 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/menu-button/menu-button.css +80 -3
- package/src/components/menu-button/menu-button.js +21 -37
- package/src/components/message/message.css +5 -2
- package/src/components/message/message.js +13 -2
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +141 -0
- package/src/components/navigation-bar/navigation-bar.css +77 -2
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/panel/panel.js +13 -2
- package/src/components/search/search.js +17 -5
- package/src/components/select/field-adapter.js +4 -1
- package/src/components/select/select.js +3 -0
- package/src/components/sort-control/sort-control.css +17 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +213 -0
- package/src/components/stat-tile/stat-tile.js +590 -0
- package/src/components/tabbox/tabbox.js +12 -1
- package/src/components/table/table.css +51 -2
- package/src/components/table/table.js +298 -19
- package/src/components/table-view/table-view.css +8 -2
- package/src/components/table-view/table-view.js +21 -6
- package/src/components/tag-picker/tag-picker.css +3 -1
- package/src/components/timebox/timebox.js +18 -5
- package/src/components/value-list/value-list.js +22 -6
- package/src/components/view/record-view.css +5 -0
- package/src/components/view/record-view.js +100 -7
- package/src/core/icons.js +74 -5
- package/src/index.js +25 -0
- package/src/internal/app-rail.js +19 -1
- package/src/internal/menu-items.js +142 -0
- package/styles/print.css +192 -0
- package/styles/zx.css +11 -0
|
@@ -28,6 +28,24 @@ import { sortRows } from './sort.js';
|
|
|
28
28
|
* @property {boolean|unknown[]} [expanded=false] Initially expanded branches: all, none, or ids.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* How one resolved row kind is presented. `rowKinds` is keyed by the kind coerced to a string, so
|
|
33
|
+
* `{ 1: … }` and `{ '1': … }` name the same entry.
|
|
34
|
+
* @typedef {Object} TableRowKind
|
|
35
|
+
* @property {boolean} [span=false] Draw the row as a single cell across every rendered column — a
|
|
36
|
+
* section heading rather than a record. The cell is a `<th scope="colgroup">`, not a `<td>`,
|
|
37
|
+
* because it labels the columns of the rows beneath it and a screen reader walking the table
|
|
38
|
+
* should meet it as a heading rather than as a value. A spanning row carries no selection
|
|
39
|
+
* checkbox, is never editable, and is skipped by `rowReorder`; it still emits `rowclick` and still
|
|
40
|
+
* participates in `getData()` and `updateRow()`.
|
|
41
|
+
* @property {(row: TableRow, index: number) => Node|string|number|null|undefined} [render] Content
|
|
42
|
+
* of the spanning cell. Omitted, the cell falls back to the row's raw value for the first visible
|
|
43
|
+
* column, which is what a host otherwise writes by hand.
|
|
44
|
+
* @property {string} [class] Space-separated classes added to the row, on top of `rowClass`. Zx
|
|
45
|
+
* ships no section look: a billing section is not a kanban swimlane, so the tint, the weight and
|
|
46
|
+
* the rule above it are the host's to decide.
|
|
47
|
+
*/
|
|
48
|
+
|
|
31
49
|
/**
|
|
32
50
|
* Editing handle handed to a custom `column.editor`.
|
|
33
51
|
* @typedef {Object} TableEditorApi
|
|
@@ -100,6 +118,15 @@ import { sortRows } from './sort.js';
|
|
|
100
118
|
* placed as given, and a function is called on each render, which is what to use when the
|
|
101
119
|
* placeholder holds live controls.
|
|
102
120
|
* @property {((row: TableRow) => string)|null} [rowClass=null] Additional row class callback.
|
|
121
|
+
* @property {string|((row: TableRow) => unknown)|null} [rowKind=null] Row property or callback
|
|
122
|
+
* naming each row's kind — the `type` on a ZeyOS transaction line, where one value is a position
|
|
123
|
+
* carrying quantities and amounts and another is a section heading that groups the positions under
|
|
124
|
+
* it. `null`, the default, renders every row exactly as before.
|
|
125
|
+
* @property {Record<string, TableRowKind>|null} [rowKinds=null] Presentation per kind, keyed by the
|
|
126
|
+
* resolved kind coerced to a string. A kind with no entry here renders as an ordinary row. While
|
|
127
|
+
* any row in the current data resolves to a `span` kind the header sort controls are withheld and
|
|
128
|
+
* `setSort()` is inert: sorting a grouped table scatters the positions out of their sections, and
|
|
129
|
+
* doing that silently is worse than not offering it. `sortMode: 'server'` still owns its own order.
|
|
103
130
|
* @property {boolean} [zebra=true] Whether alternate rows use the zebra background.
|
|
104
131
|
* @property {TableEditMode} [editMode=false] Inline editing mode. `'cell'` edits one cell, `'row'`
|
|
105
132
|
* opens every editable cell of the row together and commits them as a unit. Editing is completely
|
|
@@ -258,6 +285,8 @@ export class Table extends Component {
|
|
|
258
285
|
height: null,
|
|
259
286
|
emptyText: null,
|
|
260
287
|
rowClass: null,
|
|
288
|
+
rowKind: null,
|
|
289
|
+
rowKinds: null,
|
|
261
290
|
zebra: true,
|
|
262
291
|
editMode: false,
|
|
263
292
|
editTrigger: 'double',
|
|
@@ -288,6 +317,12 @@ export class Table extends Component {
|
|
|
288
317
|
this._original = created ? null : snapshotTarget(root);
|
|
289
318
|
this._columns = normalizeTableColumns(this.options.columns);
|
|
290
319
|
this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
|
|
320
|
+
// Row kinds resolve before the first sort: `_sortData()` refuses to run while the data holds a
|
|
321
|
+
// spanning row, and the initial `sort` option reaches it before anything is rendered.
|
|
322
|
+
this._rowKind = normalizeRowKind(this.options.rowKind);
|
|
323
|
+
this._rowKinds = normalizeRowKinds(this.options.rowKinds);
|
|
324
|
+
this._spanning = this._hasSpanningRow();
|
|
325
|
+
this._sortWarned = false;
|
|
291
326
|
this._selected = new Set();
|
|
292
327
|
this._selectionAnchorId = null;
|
|
293
328
|
this._sort = normalizeSort(this.options.sort, this._columns);
|
|
@@ -666,6 +701,8 @@ export class Table extends Component {
|
|
|
666
701
|
const row = this.getRow(id);
|
|
667
702
|
const target = this.getRow(targetId);
|
|
668
703
|
if (!row || !target || Object.is(id, targetId)) return this;
|
|
704
|
+
// A section heading has no handle to grab; refuse the programmatic route for the same reason.
|
|
705
|
+
if (this._isSpanningRow(row)) return this;
|
|
669
706
|
const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position, this._hierarchy?.parentId);
|
|
670
707
|
if (!next) return this;
|
|
671
708
|
const event = this.emit('rowmove', { row, id, target, targetId, position });
|
|
@@ -704,6 +741,10 @@ export class Table extends Component {
|
|
|
704
741
|
/**
|
|
705
742
|
* Activates a column sort. Local mode stably reorders data; server mode only updates the
|
|
706
743
|
* header state and emits the request event.
|
|
744
|
+
*
|
|
745
|
+
* Inert while the data holds a spanning row: a sort would scatter the positions out of the
|
|
746
|
+
* sections that group them, in either sort mode. The header withholds its controls for the same
|
|
747
|
+
* reason, so nothing on screen invites the call.
|
|
707
748
|
* @param {string} id Column id.
|
|
708
749
|
* @param {TableSortDirection} [dir] Direction; omitted toggles the current column.
|
|
709
750
|
* @param {{silent?: boolean}} [options={}] Event options.
|
|
@@ -712,6 +753,14 @@ export class Table extends Component {
|
|
|
712
753
|
* @fires Table#editcancel
|
|
713
754
|
*/
|
|
714
755
|
setSort(id, dir, options = {}) {
|
|
756
|
+
if (this._spanning) {
|
|
757
|
+
if (!this._sortWarned) {
|
|
758
|
+
this._sortWarned = true;
|
|
759
|
+
console.warn('[zx.Table] setSort() is inert while the data contains spanning rowKinds rows: '
|
|
760
|
+
+ 'sorting would scatter the rows out of the sections that group them.');
|
|
761
|
+
}
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
715
764
|
// Server mode never re-renders the body, so the open editor is dropped here rather than in
|
|
716
765
|
// `_renderBody()`.
|
|
717
766
|
this._abortEdit({ focus: false });
|
|
@@ -769,7 +818,7 @@ export class Table extends Component {
|
|
|
769
818
|
*/
|
|
770
819
|
setSelection(ids) {
|
|
771
820
|
if (!Array.isArray(ids)) throw new TypeError('Table selection must be an array of row ids');
|
|
772
|
-
const valid =
|
|
821
|
+
const valid = this._selectableIds();
|
|
773
822
|
const next = new Set();
|
|
774
823
|
if (this.options.selectable !== false) {
|
|
775
824
|
for (const id of ids) {
|
|
@@ -889,6 +938,8 @@ export class Table extends Component {
|
|
|
889
938
|
if (!requested) return this;
|
|
890
939
|
const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
|
|
891
940
|
if (current === undefined) return this;
|
|
941
|
+
// A spanning row renders no column cells at all, so there is nothing to open an editor over.
|
|
942
|
+
if (this._isSpanningRow(current)) return this;
|
|
892
943
|
const column = this._editMode === 'row' && this._hiddenColumns.has(requested.id)
|
|
893
944
|
? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null
|
|
894
945
|
: requested;
|
|
@@ -1205,6 +1256,45 @@ export class Table extends Component {
|
|
|
1205
1256
|
return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
|
|
1206
1257
|
}
|
|
1207
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* Whether a row draws as one cell across the table instead of as a record.
|
|
1261
|
+
* @param {TableRow} row Row to classify.
|
|
1262
|
+
* @returns {boolean}
|
|
1263
|
+
*/
|
|
1264
|
+
_isSpanningRow(row) {
|
|
1265
|
+
if (this._rowKind === null || this._rowKinds === null) return false;
|
|
1266
|
+
return isSpanningRow(row, this._rowKind, this._rowKinds);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Whether any row in the current data — not only the rendered prefix — spans. This is what
|
|
1271
|
+
* withholds the sort controls, and the reader must not be offered a control that the next
|
|
1272
|
+
* `growBy()` would have to take away again.
|
|
1273
|
+
* @returns {boolean}
|
|
1274
|
+
*/
|
|
1275
|
+
_hasSpanningRow() {
|
|
1276
|
+
return hasSpanningRow(this._data, this._rowKind, this._rowKinds);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* How many cells one row occupies: every visible column plus the structural columns the table
|
|
1281
|
+
* renders beside them. Shared by the spanning cell and the empty-state cell so the two can never
|
|
1282
|
+
* disagree about the table's width.
|
|
1283
|
+
* @returns {number}
|
|
1284
|
+
*/
|
|
1285
|
+
_spanColspan() {
|
|
1286
|
+
return tableSpanColspan(this._visibleColumns().length, {
|
|
1287
|
+
selectable: this.options.selectable,
|
|
1288
|
+
rowReorder: this.options.rowReorder
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** @returns {TableRow[]} Rendered rows that can carry a selection; spanning rows cannot. */
|
|
1293
|
+
_selectableData() {
|
|
1294
|
+
const visible = this._visibleData();
|
|
1295
|
+
return this._rowKind === null ? visible : visible.filter((row) => !this._isSpanningRow(row));
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1208
1298
|
/** @returns {HTMLElement} */
|
|
1209
1299
|
_createColumnControls() {
|
|
1210
1300
|
const list = h('div', { class: 'zx-table__column-list' });
|
|
@@ -1213,7 +1303,8 @@ export class Table extends Component {
|
|
|
1213
1303
|
class: 'zx-table__column-status', role: 'status', ariaLive: 'polite'
|
|
1214
1304
|
});
|
|
1215
1305
|
const details = h('details', { class: 'zx-table__column-controls' },
|
|
1216
|
-
h('summary', { class: 'zx-button zx-button--sm' }, icon('fields', { size: 13 }),
|
|
1306
|
+
h('summary', { class: 'zx-button zx-button--sm' }, icon('fields', { size: 13 }),
|
|
1307
|
+
this._message('table.columns', 'Columns', [])),
|
|
1217
1308
|
list);
|
|
1218
1309
|
this._renderColumnControls();
|
|
1219
1310
|
return details;
|
|
@@ -1243,7 +1334,8 @@ export class Table extends Component {
|
|
|
1243
1334
|
? h('label', { for: controlId }, toggle, h('span', {}, column.label))
|
|
1244
1335
|
: h('span', { class: 'zx-table__column-label' }, column.label);
|
|
1245
1336
|
const order = this.options.columnReorder ? h('span', {
|
|
1246
|
-
class: 'zx-table__column-order', role: 'group',
|
|
1337
|
+
class: 'zx-table__column-order', role: 'group',
|
|
1338
|
+
ariaLabel: this._message('table.moveColumn', 'Move %1', [column.label])
|
|
1247
1339
|
},
|
|
1248
1340
|
h('button', {
|
|
1249
1341
|
class: 'zx-table__column-move', type: 'button', disabled: index === 0,
|
|
@@ -1494,9 +1586,9 @@ export class Table extends Component {
|
|
|
1494
1586
|
* click is usually a bulk action. `showAll()` first if you mean all of them.
|
|
1495
1587
|
*/
|
|
1496
1588
|
if (this._selectAll.checked) {
|
|
1497
|
-
for (const dataRow of this.
|
|
1589
|
+
for (const dataRow of this._selectableData()) this._selected.add(this._idFor(dataRow));
|
|
1498
1590
|
} else {
|
|
1499
|
-
for (const dataRow of this.
|
|
1591
|
+
for (const dataRow of this._selectableData()) this._selected.delete(this._idFor(dataRow));
|
|
1500
1592
|
}
|
|
1501
1593
|
this._selectionAnchorId = null;
|
|
1502
1594
|
this._syncSelection();
|
|
@@ -1507,12 +1599,16 @@ export class Table extends Component {
|
|
|
1507
1599
|
}
|
|
1508
1600
|
|
|
1509
1601
|
this._headers = new Map();
|
|
1602
|
+
// What this header was built from, so `_renderBody()` can tell whether it is still the right one.
|
|
1603
|
+
this._headerSpanning = this._spanning;
|
|
1510
1604
|
for (const column of this._visibleColumns()) {
|
|
1511
1605
|
const th = h('th', { scope: 'col' });
|
|
1512
1606
|
const align = column.align ?? (isNumericColumn(column) ? 'end' : null);
|
|
1513
1607
|
if (align) th.dataset.align = align;
|
|
1514
1608
|
if (column.headerTitle) th.title = column.headerTitle;
|
|
1515
|
-
|
|
1609
|
+
// Withheld, not disabled: a header that invites a sort the component will refuse is worse
|
|
1610
|
+
// than a header that never offered one. The label still reads exactly the same.
|
|
1611
|
+
if (column.sortable && !this._spanning) {
|
|
1516
1612
|
const button = h('button', {
|
|
1517
1613
|
class: 'zx-table__sort-button',
|
|
1518
1614
|
type: 'button'
|
|
@@ -1537,6 +1633,18 @@ export class Table extends Component {
|
|
|
1537
1633
|
// Every data mutation funnels through here, so this is the single place that guarantees no
|
|
1538
1634
|
// editor component outlives the rows it was rendered into.
|
|
1539
1635
|
this._abortEdit({ focus: false });
|
|
1636
|
+
/*
|
|
1637
|
+
* And the single place that notices the data gaining or losing its sections. The header owns
|
|
1638
|
+
* the sort controls, so it has to be rebuilt when the answer flips — otherwise a table that was
|
|
1639
|
+
* given grouped data after construction keeps offering a sort it will now refuse, and one whose
|
|
1640
|
+
* sections were filtered away never gets its sort back.
|
|
1641
|
+
*
|
|
1642
|
+
* Compared against what the *header* was built from, not against `_spanning`: `_sortData()`
|
|
1643
|
+
* runs first on every data mutation and has already brought `_spanning` up to date, so
|
|
1644
|
+
* comparing against it would find no change and leave the stale header in place.
|
|
1645
|
+
*/
|
|
1646
|
+
this._spanning = this._hasSpanningRow();
|
|
1647
|
+
if (this._headers && this._spanning !== this._headerSpanning) this._renderHeader();
|
|
1540
1648
|
const fragment = document.createDocumentFragment();
|
|
1541
1649
|
this._rowMeta = new WeakMap();
|
|
1542
1650
|
this._rowElements = new Map();
|
|
@@ -1546,11 +1654,7 @@ export class Table extends Component {
|
|
|
1546
1654
|
this._hierarchyMeta = new Map(projected.map((entry) => [entry.id, entry]));
|
|
1547
1655
|
if (this._data.length === 0) {
|
|
1548
1656
|
fragment.append(h('tr', { class: 'zx-table__empty-row' },
|
|
1549
|
-
h('td', {
|
|
1550
|
-
class: 'zx-table__empty',
|
|
1551
|
-
colspan: this._visibleColumns().length + (this.options.selectable === 'multi' ? 1 : 0)
|
|
1552
|
-
+ (this.options.rowReorder ? 1 : 0)
|
|
1553
|
-
}, this._emptyContent())
|
|
1657
|
+
h('td', { class: 'zx-table__empty', colspan: this._spanColspan() }, this._emptyContent())
|
|
1554
1658
|
));
|
|
1555
1659
|
} else {
|
|
1556
1660
|
// Indices stay absolute: a row's index is its place in the data, not in what is on screen.
|
|
@@ -1599,7 +1703,16 @@ export class Table extends Component {
|
|
|
1599
1703
|
if (this._growStep) tr.setAttribute('aria-rowindex', String(tree.visibleIndex + 2));
|
|
1600
1704
|
const rowClass = typeof this.options.rowClass === 'function' ? this.options.rowClass(row) : '';
|
|
1601
1705
|
if (rowClass) tr.classList.add(...String(rowClass).split(/\s+/).filter(Boolean));
|
|
1602
|
-
|
|
1706
|
+
const kind = this._rowKind === null ? null : resolveRowKind(row, this._rowKind);
|
|
1707
|
+
const kindSpec = kind === null ? null : resolveRowKindSpec(this._rowKinds, kind);
|
|
1708
|
+
const spanning = Boolean(kindSpec?.span);
|
|
1709
|
+
if (kind !== null) tr.dataset.rowKind = kind;
|
|
1710
|
+
if (kindSpec?.class) tr.classList.add(...String(kindSpec.class).split(/\s+/).filter(Boolean));
|
|
1711
|
+
// A spanning row is a heading, not a record: `aria-selected="false"` would announce it as one
|
|
1712
|
+
// more selectable row that simply is not selected.
|
|
1713
|
+
if (this.options.selectable !== false && !spanning) {
|
|
1714
|
+
tr.setAttribute('aria-selected', String(this._selected.has(id)));
|
|
1715
|
+
}
|
|
1603
1716
|
if (this._hierarchy) {
|
|
1604
1717
|
tr.setAttribute('aria-level', String(tree.depth + 1));
|
|
1605
1718
|
if (tree.hasChildren) tr.setAttribute('aria-expanded', String(this._expanded.has(id)));
|
|
@@ -1609,6 +1722,25 @@ export class Table extends Component {
|
|
|
1609
1722
|
elements.push(tr);
|
|
1610
1723
|
this._rowElements.set(id, elements);
|
|
1611
1724
|
|
|
1725
|
+
if (spanning) {
|
|
1726
|
+
tr.dataset.span = 'true';
|
|
1727
|
+
/*
|
|
1728
|
+
* One cell, `<th scope="colgroup">`, covering the reorder and selection columns as well as
|
|
1729
|
+
* the data ones: the row labels the columns of the records beneath it, so it belongs in the
|
|
1730
|
+
* accessibility tree as a heading and not as a value — and a second, narrower cell beside it
|
|
1731
|
+
* would put a blank column back into exactly the layout this removes.
|
|
1732
|
+
*/
|
|
1733
|
+
const cell = /** @type {HTMLTableCellElement} */ (h('th', {
|
|
1734
|
+
class: 'zx-table__section-cell', scope: 'colgroup', colspan: this._spanColspan()
|
|
1735
|
+
}));
|
|
1736
|
+
const content = spanRowContent(row, kindSpec, this._visibleColumns(), index);
|
|
1737
|
+
if (content && typeof content === 'object' && typeof content.nodeType === 'number') {
|
|
1738
|
+
cell.append(content);
|
|
1739
|
+
} else if (content != null) cell.append(document.createTextNode(String(content)));
|
|
1740
|
+
tr.append(cell);
|
|
1741
|
+
return tr;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1612
1744
|
if (this.options.rowReorder) {
|
|
1613
1745
|
const handle = h('button', {
|
|
1614
1746
|
class: 'zx-table__row-handle', type: 'button', draggable: true,
|
|
@@ -1729,7 +1861,10 @@ export class Table extends Component {
|
|
|
1729
1861
|
}
|
|
1730
1862
|
|
|
1731
1863
|
this.emit('rowclick', { ...meta, event });
|
|
1732
|
-
|
|
1864
|
+
// The click still reaches the host — collapsing a section is a reasonable thing to hang off it —
|
|
1865
|
+
// but it does not select the heading, which carries no checkbox in `multi` either.
|
|
1866
|
+
if (this.options.selectable === 'single' && !this._selected.has(meta.id)
|
|
1867
|
+
&& !this._isSpanningRow(meta.row)) {
|
|
1733
1868
|
this._selected = new Set([meta.id]);
|
|
1734
1869
|
this._selectionAnchorId = meta.id;
|
|
1735
1870
|
this._syncSelection();
|
|
@@ -2093,6 +2228,8 @@ export class Table extends Component {
|
|
|
2093
2228
|
if (row < 0 || row >= visible.length) return null;
|
|
2094
2229
|
const candidateRow = visible[row];
|
|
2095
2230
|
const candidateColumn = columns[column];
|
|
2231
|
+
// Tab walks past a section heading rather than stopping on a row that has no cells to edit.
|
|
2232
|
+
if (this._isSpanningRow(candidateRow)) continue;
|
|
2096
2233
|
if (resolveEditable(candidateColumn, candidateRow) !== false) {
|
|
2097
2234
|
return {
|
|
2098
2235
|
id: this._idFor(candidateRow),
|
|
@@ -2172,6 +2309,9 @@ export class Table extends Component {
|
|
|
2172
2309
|
const start = Math.min(anchorIndex, currentIndex);
|
|
2173
2310
|
const end = Math.max(anchorIndex, currentIndex);
|
|
2174
2311
|
for (let index = start; index <= end; index += 1) {
|
|
2312
|
+
// Indices stay against the rendered rows so `visibleIndex` still lines up; a section caught
|
|
2313
|
+
// inside the range is stepped over rather than selected.
|
|
2314
|
+
if (this._isSpanningRow(visible[index])) continue;
|
|
2175
2315
|
const id = this._idFor(visible[index]);
|
|
2176
2316
|
if (checked) this._selected.add(id);
|
|
2177
2317
|
else this._selected.delete(id);
|
|
@@ -2191,6 +2331,9 @@ export class Table extends Component {
|
|
|
2191
2331
|
for (const [id, rows] of this._rowElements) {
|
|
2192
2332
|
const selected = this._selected.has(id);
|
|
2193
2333
|
for (const row of rows) {
|
|
2334
|
+
// A spanning row never claimed `aria-selected`; writing it here would tell a screen reader
|
|
2335
|
+
// the heading is a selectable record that happens to be unselected.
|
|
2336
|
+
if (row.hasAttribute('data-span')) continue;
|
|
2194
2337
|
row.setAttribute('aria-selected', String(selected));
|
|
2195
2338
|
const checkbox = row.querySelector('.zx-table__row-checkbox');
|
|
2196
2339
|
if (checkbox) checkbox.checked = selected;
|
|
@@ -2202,8 +2345,9 @@ export class Table extends Component {
|
|
|
2202
2345
|
/** @returns {void} */
|
|
2203
2346
|
_syncSelectAll() {
|
|
2204
2347
|
if (!this._selectAll) return;
|
|
2205
|
-
// Measured against what is rendered, so the box reflects the rows the reader can actually see
|
|
2206
|
-
|
|
2348
|
+
// Measured against what is rendered, so the box reflects the rows the reader can actually see —
|
|
2349
|
+
// and only the ones that can be ticked, so a section heading never holds the box indeterminate.
|
|
2350
|
+
const visible = this._selectableData();
|
|
2207
2351
|
const selectedRows = visible.reduce(
|
|
2208
2352
|
(count, row) => count + Number(this._selected.has(this._idFor(row))),
|
|
2209
2353
|
0
|
|
@@ -2217,14 +2361,20 @@ export class Table extends Component {
|
|
|
2217
2361
|
_syncSortHeader() {
|
|
2218
2362
|
if (!this._headers) return;
|
|
2219
2363
|
for (const [id, header] of this._headers) {
|
|
2220
|
-
|
|
2221
|
-
|
|
2364
|
+
// A withheld control must not leave `aria-sort` behind: with the sort inert the attribute
|
|
2365
|
+
// would announce an order the rows are not actually in.
|
|
2366
|
+
if (!this._spanning && this._sort?.id === id) {
|
|
2367
|
+
header.setAttribute('aria-sort', this._sort.dir === 'asc' ? 'ascending' : 'descending');
|
|
2368
|
+
} else header.removeAttribute('aria-sort');
|
|
2222
2369
|
}
|
|
2223
2370
|
}
|
|
2224
2371
|
|
|
2225
2372
|
/** @returns {void} */
|
|
2226
2373
|
_sortData() {
|
|
2227
|
-
|
|
2374
|
+
// Recomputed here and not only in `_renderBody()`: every data mutation sorts before it renders,
|
|
2375
|
+
// so data that has just gained a section must be seen as grouped before the sort would run.
|
|
2376
|
+
this._spanning = this._hasSpanningRow();
|
|
2377
|
+
if (!this._sort || this._spanning) return;
|
|
2228
2378
|
const column = this._columns.find((candidate) => candidate.id === this._sort.id);
|
|
2229
2379
|
if (!column) return;
|
|
2230
2380
|
const getValue = typeof column.sortValue === 'function' ? column.sortValue : (row) => row?.[column.id];
|
|
@@ -2263,9 +2413,18 @@ export class Table extends Component {
|
|
|
2263
2413
|
for (const id of this._expanded) if (!valid.has(id)) this._expanded.delete(id);
|
|
2264
2414
|
}
|
|
2265
2415
|
|
|
2416
|
+
/** @returns {Set<unknown>} Ids of every row in the data that may hold a selection. */
|
|
2417
|
+
_selectableIds() {
|
|
2418
|
+
const ids = new Set();
|
|
2419
|
+
for (const row of this._data) {
|
|
2420
|
+
if (!this._isSpanningRow(row)) ids.add(this._idFor(row));
|
|
2421
|
+
}
|
|
2422
|
+
return ids;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2266
2425
|
/** @returns {boolean} Whether selection changed. */
|
|
2267
2426
|
_pruneSelection() {
|
|
2268
|
-
const valid =
|
|
2427
|
+
const valid = this._selectableIds();
|
|
2269
2428
|
const previousSize = this._selected.size;
|
|
2270
2429
|
for (const id of this._selected) if (!valid.has(id)) this._selected.delete(id);
|
|
2271
2430
|
if (!valid.has(this._selectionAnchorId)) this._selectionAnchorId = null;
|
|
@@ -2337,6 +2496,104 @@ export class Table extends Component {
|
|
|
2337
2496
|
* @type {CustomEvent<TableEditInvalidDetail>}
|
|
2338
2497
|
*/
|
|
2339
2498
|
|
|
2499
|
+
/**
|
|
2500
|
+
* Resolves a row's kind through the `rowKind` accessor and coerces it to the string `rowKinds` is
|
|
2501
|
+
* keyed by.
|
|
2502
|
+
*
|
|
2503
|
+
* Only `null` and `undefined` mean "no kind". `0` and `''` are kinds like any other: a ZeyOS
|
|
2504
|
+
* transaction line uses `0` for a position and `1` for a section, so a falsy test here would render
|
|
2505
|
+
* every position as though the table had never been told about kinds at all.
|
|
2506
|
+
* @param {TableRow} row Row to classify.
|
|
2507
|
+
* @param {string|((row: TableRow) => unknown)|null|undefined} rowKind Field name or accessor.
|
|
2508
|
+
* @returns {string|null} The kind as a string, or null when there is none.
|
|
2509
|
+
*/
|
|
2510
|
+
export function resolveRowKind(row, rowKind) {
|
|
2511
|
+
if (rowKind == null || rowKind === '') return null;
|
|
2512
|
+
const raw = typeof rowKind === 'function' ? rowKind(row) : row?.[rowKind];
|
|
2513
|
+
return raw == null ? null : String(raw);
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
/**
|
|
2517
|
+
* Looks one resolved kind up in the `rowKinds` map.
|
|
2518
|
+
*
|
|
2519
|
+
* Own properties only: a kind read out of server data could just as easily be `toString` or
|
|
2520
|
+
* `constructor`, and inherited members of `Object.prototype` are not configuration.
|
|
2521
|
+
* @param {Record<string, TableRowKind>|null|undefined} rowKinds Kind map.
|
|
2522
|
+
* @param {string|null} kind Resolved kind.
|
|
2523
|
+
* @returns {TableRowKind|null} Matching entry, or null.
|
|
2524
|
+
*/
|
|
2525
|
+
export function resolveRowKindSpec(rowKinds, kind) {
|
|
2526
|
+
if (kind === null || rowKinds == null || typeof rowKinds !== 'object') return null;
|
|
2527
|
+
if (!Object.prototype.hasOwnProperty.call(rowKinds, kind)) return null;
|
|
2528
|
+
const spec = rowKinds[kind];
|
|
2529
|
+
return spec && typeof spec === 'object' ? spec : null;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/**
|
|
2533
|
+
* Whether a row draws as one spanning cell under the given configuration.
|
|
2534
|
+
* @param {TableRow} row Row to classify.
|
|
2535
|
+
* @param {string|((row: TableRow) => unknown)|null|undefined} rowKind Field name or accessor.
|
|
2536
|
+
* @param {Record<string, TableRowKind>|null|undefined} rowKinds Kind map.
|
|
2537
|
+
* @returns {boolean}
|
|
2538
|
+
*/
|
|
2539
|
+
export function isSpanningRow(row, rowKind, rowKinds) {
|
|
2540
|
+
return Boolean(resolveRowKindSpec(rowKinds, resolveRowKind(row, rowKind))?.span);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
/**
|
|
2544
|
+
* Whether a data set holds a row that spans — the question that decides whether `Table` offers its
|
|
2545
|
+
* sort controls at all.
|
|
2546
|
+
*
|
|
2547
|
+
* Asked of the whole data rather than of the rendered prefix: with `growing` on, a sort control
|
|
2548
|
+
* that disappeared the moment the reader pressed "show 20 more" would be worse than one that was
|
|
2549
|
+
* never there. Asked again on every data change, so a filter that removes the last section gives
|
|
2550
|
+
* the sort back.
|
|
2551
|
+
* @param {TableRow[]} rows Rows to scan.
|
|
2552
|
+
* @param {string|((row: TableRow) => unknown)|null|undefined} rowKind Field name or accessor.
|
|
2553
|
+
* @param {Record<string, TableRowKind>|null|undefined} rowKinds Kind map.
|
|
2554
|
+
* @returns {boolean}
|
|
2555
|
+
*/
|
|
2556
|
+
export function hasSpanningRow(rows, rowKind, rowKinds) {
|
|
2557
|
+
if (!Array.isArray(rows) || rowKind == null || rowKinds == null) return false;
|
|
2558
|
+
return rows.some((row) => isSpanningRow(row, rowKind, rowKinds));
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/**
|
|
2562
|
+
* How many cells one table row occupies.
|
|
2563
|
+
*
|
|
2564
|
+
* `selectable: 'multi'` and `rowReorder` each render a `<col>` and a leading cell of their own, and
|
|
2565
|
+
* a spanning cell that ignored them would leave the row one or two columns short — the table would
|
|
2566
|
+
* still lay out, silently and wrongly. `selectable: 'single'` adds no column: the whole row is the
|
|
2567
|
+
* control there.
|
|
2568
|
+
* @param {number} columnCount Number of visible data columns.
|
|
2569
|
+
* @param {{selectable?: TableSelectionMode, rowReorder?: boolean}} [structure={}] Structural columns.
|
|
2570
|
+
* @returns {number} Colspan covering every rendered cell, never below 1.
|
|
2571
|
+
*/
|
|
2572
|
+
export function tableSpanColspan(columnCount, structure = {}) {
|
|
2573
|
+
const columns = Number.isFinite(Number(columnCount)) ? Math.max(0, Math.trunc(Number(columnCount))) : 0;
|
|
2574
|
+
const extras = (structure.selectable === 'multi' ? 1 : 0) + (structure.rowReorder ? 1 : 0);
|
|
2575
|
+
return Math.max(1, columns + extras);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
/**
|
|
2579
|
+
* Content for a spanning row's single cell.
|
|
2580
|
+
*
|
|
2581
|
+
* Without a `render` the cell shows the row's raw value for the first visible column — the same
|
|
2582
|
+
* thing a host writes by hand today. Deliberately raw rather than routed through the column's own
|
|
2583
|
+
* renderer or display type: that renderer was written for a record, and running it against a
|
|
2584
|
+
* heading that carries no quantities or amounts is how a section row ends up showing `0,00 €`.
|
|
2585
|
+
* @param {TableRow} row Row being rendered.
|
|
2586
|
+
* @param {TableRowKind|null|undefined} spec Resolved kind presentation.
|
|
2587
|
+
* @param {TableColumn[]} visibleColumns Columns currently rendered, in display order.
|
|
2588
|
+
* @param {number} [index=0] Flat row index.
|
|
2589
|
+
* @returns {Node|string|number|null|undefined} Cell content.
|
|
2590
|
+
*/
|
|
2591
|
+
export function spanRowContent(row, spec, visibleColumns, index = 0) {
|
|
2592
|
+
if (typeof spec?.render === 'function') return spec.render(row, index);
|
|
2593
|
+
const first = visibleColumns?.[0];
|
|
2594
|
+
return first ? row?.[first.id] : null;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2340
2597
|
/**
|
|
2341
2598
|
* Applies a column's built-in display type. A custom renderer always wins and may still return a
|
|
2342
2599
|
* Node; all built-in results are strings so `_renderCellContent` can insert them as text.
|
|
@@ -2724,6 +2981,28 @@ function normalizeHierarchy(option, columns) {
|
|
|
2724
2981
|
return { ...option, parentId, column };
|
|
2725
2982
|
}
|
|
2726
2983
|
|
|
2984
|
+
/**
|
|
2985
|
+
* Normalizes the `rowKind` accessor. Anything that is not a non-empty field name or a callback is
|
|
2986
|
+
* `null`, which is the switch that keeps every existing table on exactly its old rendering path.
|
|
2987
|
+
* @param {unknown} option Configured accessor.
|
|
2988
|
+
* @returns {string|((row: TableRow) => unknown)|null}
|
|
2989
|
+
*/
|
|
2990
|
+
function normalizeRowKind(option) {
|
|
2991
|
+
if (typeof option === 'function') return /** @type {(row: TableRow) => unknown} */ (option);
|
|
2992
|
+
return typeof option === 'string' && option !== '' ? option : null;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
/**
|
|
2996
|
+
* Normalizes the `rowKinds` map. An array, a function or a primitive is not a keyed map and is
|
|
2997
|
+
* discarded rather than half-read.
|
|
2998
|
+
* @param {unknown} option Configured kind map.
|
|
2999
|
+
* @returns {Record<string, TableRowKind>|null}
|
|
3000
|
+
*/
|
|
3001
|
+
function normalizeRowKinds(option) {
|
|
3002
|
+
if (!option || typeof option !== 'object' || Array.isArray(option)) return null;
|
|
3003
|
+
return /** @type {Record<string, TableRowKind>} */ (option);
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2727
3006
|
/** @param {unknown} option @returns {Set<unknown>} */
|
|
2728
3007
|
function initialExpanded(option) {
|
|
2729
3008
|
return new Set(Array.isArray(option) ? option : []);
|
|
@@ -6,13 +6,19 @@
|
|
|
6
6
|
min-inline-size: 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.zx-table-
|
|
9
|
+
.zx-table-view__toolbar {
|
|
10
|
+
min-inline-size: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.zx-table-view[data-loading="true"] .zx-table__column-controls,
|
|
14
|
+
.zx-table-view[data-loading="true"] .zx-record-view__field-controls {
|
|
10
15
|
pointer-events: none;
|
|
11
16
|
opacity: .62;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
@media (prefers-reduced-motion: no-preference) {
|
|
15
|
-
.zx-table-view .zx-table__column-controls
|
|
20
|
+
.zx-table-view .zx-table__column-controls,
|
|
21
|
+
.zx-table-view .zx-record-view__field-controls {
|
|
16
22
|
transition: opacity var(--zx-dur-fast) var(--zx-ease);
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
3
|
import { formatTableCell, Table } from '../table/table.js';
|
|
4
|
-
import { RecordView, readViewField } from '../view/record-view.js';
|
|
4
|
+
import { normalizeViewFieldControls, RecordView, readViewField } from '../view/record-view.js';
|
|
5
5
|
|
|
6
6
|
/** @typedef {Record<string, any>} TableViewRecord */
|
|
7
7
|
/** @typedef {import('../view/record-view.js').ViewField} ViewField */
|
|
@@ -20,7 +20,10 @@ import { RecordView, readViewField } from '../view/record-view.js';
|
|
|
20
20
|
* @property {unknown[]} [selection=[]] Initially selected record ids.
|
|
21
21
|
* @property {string[]} [fieldOrder=[]] Preferred stable field order.
|
|
22
22
|
* @property {string[]} [hiddenFields=[]] Initially hidden field ids.
|
|
23
|
-
* @property {boolean} [fieldControls=true]
|
|
23
|
+
* @property {boolean|import('../view/record-view.js').ViewFieldControlsConfig} [fieldControls=true]
|
|
24
|
+
* Show the accessible visibility and reorder disclosure. `true` keeps the composed Table's own
|
|
25
|
+
* column chooser and `false` omits it; an object renders the shared record-view chooser instead,
|
|
26
|
+
* with a translated `label` and, with `target`, mounted into a host element.
|
|
24
27
|
* @property {string|Node|(()=>string|Node)|null} [emptyText=null] Empty-result content.
|
|
25
28
|
* @property {TableOptions} [table={}] Advanced low-level Table options. Shared options win where
|
|
26
29
|
* their contracts overlap; all other options and callbacks are forwarded unchanged.
|
|
@@ -74,9 +77,15 @@ export class TableView extends RecordView {
|
|
|
74
77
|
this._tableViewDestroyed = false;
|
|
75
78
|
this._tableHost = h('div', { class: 'zx-table-view__table' });
|
|
76
79
|
this._initRecordView(root);
|
|
80
|
+
// Only a `fieldControls` object replaces Table's own chooser, and only an unresolved target
|
|
81
|
+
// keeps that replacement inside the view — so `true` and `false` render exactly as before.
|
|
82
|
+
const fieldControls = normalizeViewFieldControls(this.options.fieldControls);
|
|
83
|
+
this._tableViewToolbar = fieldControls.custom && !this._viewFieldControlsTarget
|
|
84
|
+
? h('div', { class: 'zx-record-view__toolbar zx-table-view__toolbar' })
|
|
85
|
+
: null;
|
|
77
86
|
|
|
78
87
|
try {
|
|
79
|
-
root.replaceChildren(this._tableHost);
|
|
88
|
+
root.replaceChildren(...[this._tableViewToolbar, this._tableHost].filter(Boolean));
|
|
80
89
|
const viewOptions = /** @type {Readonly<TableViewOptions>} */ (this.options);
|
|
81
90
|
const options = tableOptionsForView(viewOptions.table, {
|
|
82
91
|
fields: this.getFields(),
|
|
@@ -93,9 +102,11 @@ export class TableView extends RecordView {
|
|
|
93
102
|
// Seed selection before bridging events so construction stays silent like RecordView itself.
|
|
94
103
|
this.table.setSelection(this.getSelectionIds());
|
|
95
104
|
this._bridgeTableEvents();
|
|
105
|
+
if (fieldControls.custom) this._mountViewFieldControls(this._tableViewToolbar ?? root);
|
|
96
106
|
return root;
|
|
97
107
|
} catch (error) {
|
|
98
108
|
this.table?.destroy();
|
|
109
|
+
this._removeViewFieldControls();
|
|
99
110
|
if (!created) restoreTarget(root, this._tableViewOriginal);
|
|
100
111
|
throw error;
|
|
101
112
|
}
|
|
@@ -302,10 +313,14 @@ export function fieldsToTableColumns(fields) {
|
|
|
302
313
|
* Builds the composed Table configuration. Shared options deliberately overwrite overlapping
|
|
303
314
|
* low-level options; every advanced option remains untouched.
|
|
304
315
|
* @param {TableOptions|undefined} table Advanced Table options.
|
|
305
|
-
* @param {{fields:ViewField[],data:TableViewRecord[],recordId:string|((record:TableViewRecord)=>unknown),sort:ViewSort|null,sortMode:'local'|'server',selectable:false|'single'|'multi',hiddenFields:string[],fieldControls:boolean,emptyText:string|Node|(()=>string|Node)|null}} shared Shared state.
|
|
316
|
+
* @param {{fields:ViewField[],data:TableViewRecord[],recordId:string|((record:TableViewRecord)=>unknown),sort:ViewSort|null,sortMode:'local'|'server',selectable:false|'single'|'multi',hiddenFields:string[],fieldControls:boolean|import('../view/record-view.js').ViewFieldControlsConfig,emptyText:string|Node|(()=>string|Node)|null}} shared Shared state.
|
|
306
317
|
* @returns {TableOptions} Table configuration.
|
|
307
318
|
*/
|
|
308
319
|
export function tableOptionsForView(table, shared) {
|
|
320
|
+
// A customised chooser is rendered by TableView from the shared field state, so Table's own one
|
|
321
|
+
// stands down; `true`/`false` keep Table's chooser exactly as it has always been.
|
|
322
|
+
const controls = normalizeViewFieldControls(shared.fieldControls);
|
|
323
|
+
const tableChooser = controls.enabled && !controls.custom;
|
|
309
324
|
return {
|
|
310
325
|
...(table && typeof table === 'object' ? table : {}),
|
|
311
326
|
columns: fieldsToTableColumns(shared.fields),
|
|
@@ -315,8 +330,8 @@ export function tableOptionsForView(table, shared) {
|
|
|
315
330
|
sortMode: shared.sortMode,
|
|
316
331
|
selectable: shared.selectable,
|
|
317
332
|
hiddenColumns: [...shared.hiddenFields],
|
|
318
|
-
columnVisibility:
|
|
319
|
-
columnReorder:
|
|
333
|
+
columnVisibility: tableChooser,
|
|
334
|
+
columnReorder: tableChooser,
|
|
320
335
|
emptyText: shared.emptyText
|
|
321
336
|
};
|
|
322
337
|
}
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
align-items: center;
|
|
11
11
|
gap: var(--zx-space-1);
|
|
12
12
|
min-block-size: var(--zx-control-height);
|
|
13
|
-
padding: 3px
|
|
13
|
+
padding: 3px;
|
|
14
|
+
/* The tags sit further from the field's leading edge than from its other three. */
|
|
15
|
+
padding-inline-start: var(--zx-space-2);
|
|
14
16
|
border: 1px solid var(--zx-color-border-control);
|
|
15
17
|
border-radius: var(--zx-control-radius);
|
|
16
18
|
background: var(--zx-color-bg-control);
|