@zeyos/zx 4.1.0 → 4.2.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 +61 -0
- package/DESIGN-SYSTEM.md +33 -3
- package/README.md +17 -5
- package/dist/types/components/app-icon/app-icon.d.ts +8 -1
- package/dist/types/components/aurora/aurora.d.ts +72 -0
- package/dist/types/components/calendar/calendar-model.d.ts +202 -0
- package/dist/types/components/calendar/calendar.d.ts +500 -0
- package/dist/types/components/card-view/card-view.d.ts +303 -0
- package/dist/types/components/card-view/record-card.d.ts +242 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +572 -0
- package/dist/types/components/table/table.d.ts +120 -31
- package/dist/types/components/table-view/table-view.d.ts +192 -0
- package/dist/types/components/view/record-view.d.ts +464 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/zeyos/calendar.d.ts +91 -0
- package/dist/types/zeyos/icons.d.ts +19 -3
- package/dist/types/zeyos/index.d.ts +4 -0
- package/dist/types/zeyos/legacy-saved-views.d.ts +66 -0
- package/dist/types/zeyos/saved-views.d.ts +331 -0
- package/dist/types/zeyos/view.d.ts +305 -0
- package/dist/zx-zeyos.esm.js +4627 -1460
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +2492 -410
- package/dist/zx.esm.js +4891 -115
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +4891 -115
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1702 -191
- package/docs/llms.md +207 -20
- package/docs/llms.txt +16 -6
- package/package.json +1 -1
- package/src/components/app-icon/app-icon.css +140 -30
- package/src/components/app-icon/app-icon.js +32 -5
- package/src/components/app-rail/app-rail.css +11 -2
- package/src/components/app-sidebar/app-sidebar.css +11 -2
- package/src/components/aurora/aurora.css +143 -0
- package/src/components/aurora/aurora.js +146 -0
- package/src/components/avatar/avatar.css +1 -1
- package/src/components/button/button.css +34 -2
- package/src/components/calendar/calendar-model.js +402 -0
- package/src/components/calendar/calendar.css +567 -0
- package/src/components/calendar/calendar.js +1524 -0
- package/src/components/card/card.css +25 -3
- package/src/components/card-view/card-view.css +389 -0
- package/src/components/card-view/card-view.js +527 -0
- package/src/components/card-view/record-card.js +392 -0
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/date-picker/date-picker.css +15 -3
- package/src/components/datebox/date-range-box.css +15 -2
- package/src/components/datebox/datebox.css +16 -3
- package/src/components/dropdown/dropdown.css +15 -3
- package/src/components/kanban-view/kanban-view.css +216 -0
- package/src/components/kanban-view/kanban-view.js +1228 -0
- package/src/components/launcher/launcher.css +18 -5
- package/src/components/master-panel/master-panel.css +14 -3
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +15 -10
- package/src/components/modal/modal.css +15 -3
- package/src/components/navigation-bar/navigation-bar.css +17 -5
- package/src/components/panel/panel.css +10 -2
- package/src/components/select/select.css +20 -4
- package/src/components/sheet/sheet.css +4 -1
- package/src/components/table/table.css +80 -4
- package/src/components/table/table.js +322 -37
- package/src/components/table-view/table-view.css +18 -0
- package/src/components/table-view/table-view.js +347 -0
- package/src/components/tag-picker/tag-picker.css +15 -3
- package/src/components/tooltip/tooltip.css +15 -3
- package/src/components/view/record-view.css +114 -0
- package/src/components/view/record-view.js +770 -0
- package/src/index.js +15 -0
- package/src/zeyos/calendar.js +115 -0
- package/src/zeyos/icons.js +15 -8
- package/src/zeyos/index.js +13 -0
- package/src/zeyos/launcher.js +2 -2
- package/src/zeyos/legacy-saved-views.js +155 -0
- package/src/zeyos/saved-views.js +815 -0
- package/src/zeyos/table.js +3 -1
- package/src/zeyos/view.js +543 -0
- package/styles/icons.css +3 -10
- package/styles/tokens/semantic.css +74 -2
- package/styles/zx.css +6 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { formatTableCell, Table } from '../table/table.js';
|
|
4
|
+
import { RecordView, readViewField } from '../view/record-view.js';
|
|
5
|
+
|
|
6
|
+
/** @typedef {Record<string, any>} TableViewRecord */
|
|
7
|
+
/** @typedef {import('../view/record-view.js').ViewField} ViewField */
|
|
8
|
+
/** @typedef {import('../view/record-view.js').ViewSort} ViewSort */
|
|
9
|
+
/** @typedef {import('../table/table.js').TableColumn} TableColumn */
|
|
10
|
+
/** @typedef {import('../table/table.js').TableOptions} TableOptions */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} TableViewOptions
|
|
14
|
+
* @property {ViewField[]} [fields=[]] Shared ordered field descriptors. These win over `table.columns`.
|
|
15
|
+
* @property {TableViewRecord[]} [data=[]] Initial records. These win over `table.data`.
|
|
16
|
+
* @property {string|((record:TableViewRecord)=>unknown)} [recordId='ID'] Stable record id accessor.
|
|
17
|
+
* @property {ViewSort|null} [sort=null] Initial shared sort.
|
|
18
|
+
* @property {'local'|'server'} [sortMode='local'] Local or server-controlled sorting.
|
|
19
|
+
* @property {false|'single'|'multi'} [selectable=false] Shared selection behavior.
|
|
20
|
+
* @property {unknown[]} [selection=[]] Initially selected record ids.
|
|
21
|
+
* @property {string[]} [fieldOrder=[]] Preferred stable field order.
|
|
22
|
+
* @property {string[]} [hiddenFields=[]] Initially hidden field ids.
|
|
23
|
+
* @property {boolean} [fieldControls=true] Show the accessible visibility and reorder disclosure.
|
|
24
|
+
* @property {string|Node|(()=>string|Node)|null} [emptyText=null] Empty-result content.
|
|
25
|
+
* @property {TableOptions} [table={}] Advanced low-level Table options. Shared options win where
|
|
26
|
+
* their contracts overlap; all other options and callbacks are forwarded unchanged.
|
|
27
|
+
* @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onrecordclick]
|
|
28
|
+
* @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onrecorddblclick]
|
|
29
|
+
* @property {(event:CustomEvent<{records:TableViewRecord[]}> )=>void} [ondatachange]
|
|
30
|
+
* @property {(event:CustomEvent<{records:TableViewRecord[],ids:unknown[]}> )=>void} [onselectionchange]
|
|
31
|
+
* @property {(event:CustomEvent<{id:string|null,dir:'asc'|'desc'|null}>)=>void} [onsortchange]
|
|
32
|
+
* @property {(event:CustomEvent<{visible:string[],hidden:string[]}> )=>void} [onfieldvisibilitychange]
|
|
33
|
+
* @property {(event:CustomEvent<{order:string[]}> )=>void} [onfieldorderchange]
|
|
34
|
+
* @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onstatechange]
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Record-oriented table view. Shared record state stays in RecordView while the composed `table`
|
|
39
|
+
* instance retains Table's editing, hierarchy, growth, movement, responsive, and loading behavior.
|
|
40
|
+
* @fires TableView#recordclick
|
|
41
|
+
* @fires TableView#recorddblclick
|
|
42
|
+
* @fires TableView#datachange
|
|
43
|
+
* @fires TableView#selectionchange
|
|
44
|
+
* @fires TableView#sortchange
|
|
45
|
+
* @fires TableView#fieldvisibilitychange
|
|
46
|
+
* @fires TableView#fieldorderchange
|
|
47
|
+
* @fires TableView#statechange
|
|
48
|
+
* @extends {RecordView}
|
|
49
|
+
*/
|
|
50
|
+
export class TableView extends RecordView {
|
|
51
|
+
static cssName = 'table-view';
|
|
52
|
+
|
|
53
|
+
/** @type {Readonly<TableViewOptions>} */
|
|
54
|
+
static defaults = { table: {} };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates or enhances a record table view.
|
|
58
|
+
* @param {Element|string|null} [target=null] Existing root, selector, or null.
|
|
59
|
+
* @param {TableViewOptions} [options={}] Shared and table-specific options.
|
|
60
|
+
*/
|
|
61
|
+
constructor(target = null, options = {}) {
|
|
62
|
+
super(target, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @returns {HTMLElement} Table view root. */
|
|
66
|
+
render() {
|
|
67
|
+
const created = this.el === null;
|
|
68
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
69
|
+
this.el = root;
|
|
70
|
+
this._createdTableViewRoot = created;
|
|
71
|
+
this._tableViewOriginal = created ? null : snapshotTarget(root);
|
|
72
|
+
this._tableViewRestored = false;
|
|
73
|
+
this._tableSyncDepth = 0;
|
|
74
|
+
this._tableViewDestroyed = false;
|
|
75
|
+
this._tableHost = h('div', { class: 'zx-table-view__table' });
|
|
76
|
+
this._initRecordView(root);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
root.replaceChildren(this._tableHost);
|
|
80
|
+
const viewOptions = /** @type {Readonly<TableViewOptions>} */ (this.options);
|
|
81
|
+
const options = tableOptionsForView(viewOptions.table, {
|
|
82
|
+
fields: this.getFields(),
|
|
83
|
+
data: this.getData(),
|
|
84
|
+
recordId: this.options.recordId,
|
|
85
|
+
sort: this.getSort(),
|
|
86
|
+
sortMode: this.options.sortMode,
|
|
87
|
+
selectable: this.options.selectable,
|
|
88
|
+
hiddenFields: this.getHiddenFields(),
|
|
89
|
+
fieldControls: this.options.fieldControls,
|
|
90
|
+
emptyText: this.options.emptyText
|
|
91
|
+
});
|
|
92
|
+
this.table = new Table(this._tableHost, options);
|
|
93
|
+
// Seed selection before bridging events so construction stays silent like RecordView itself.
|
|
94
|
+
this.table.setSelection(this.getSelectionIds());
|
|
95
|
+
this._bridgeTableEvents();
|
|
96
|
+
return root;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.table?.destroy();
|
|
99
|
+
if (!created) restoreTarget(root, this._tableViewOriginal);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Returns the composed Table for advanced methods and Table-specific events. @returns {Table} */
|
|
105
|
+
getTable() {
|
|
106
|
+
return this.table;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @protected @param {string} reason Shared refresh reason. @returns {void} */
|
|
110
|
+
_refreshView(reason) {
|
|
111
|
+
if (!this.table || this._tableSyncDepth > 0) return;
|
|
112
|
+
this._withTableSync(() => {
|
|
113
|
+
if (reason === 'fields' || reason === 'state') {
|
|
114
|
+
this.table.setColumns(fieldsToTableColumns(this.getFields()), { silent: true });
|
|
115
|
+
this._syncTableFieldState();
|
|
116
|
+
}
|
|
117
|
+
if (reason === 'data' || reason === 'fields' || reason === 'sort' || reason === 'state') {
|
|
118
|
+
this.table.setData(this.getData());
|
|
119
|
+
}
|
|
120
|
+
if (reason === 'sort' || reason === 'fields' || reason === 'state') {
|
|
121
|
+
const sort = this.getSort();
|
|
122
|
+
if (sort) this.table.setSort(sort.id, sort.dir, { silent: true });
|
|
123
|
+
else this.table.clearSort({ silent: true });
|
|
124
|
+
}
|
|
125
|
+
if (reason === 'selection' || reason === 'data' || reason === 'state') {
|
|
126
|
+
this.table.setSelection(this.getSelectionIds());
|
|
127
|
+
}
|
|
128
|
+
if (reason === 'loading') {
|
|
129
|
+
this.table.setLoading(/** @type {HTMLElement} */ (this.el).dataset.loading === 'true');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Destroys the inner Table and restores an enhanced target exactly once. @returns {void} */
|
|
135
|
+
destroy() {
|
|
136
|
+
if (this._tableViewDestroyed) return;
|
|
137
|
+
this._tableViewDestroyed = true;
|
|
138
|
+
this.table?.destroy();
|
|
139
|
+
if (!this._tableViewRestored && !this._createdTableViewRoot) {
|
|
140
|
+
restoreTarget(this.el, this._tableViewOriginal);
|
|
141
|
+
this._tableViewRestored = true;
|
|
142
|
+
}
|
|
143
|
+
super.destroy();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @private @param {()=>void} callback Guarded synchronization. @returns {void} */
|
|
147
|
+
_withTableSync(callback) {
|
|
148
|
+
this._tableSyncDepth += 1;
|
|
149
|
+
try {
|
|
150
|
+
callback();
|
|
151
|
+
} finally {
|
|
152
|
+
this._tableSyncDepth -= 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @private @returns {void} */
|
|
157
|
+
_syncTableFieldState() {
|
|
158
|
+
const hidden = new Set(this.getHiddenFields());
|
|
159
|
+
// Show before hiding so Table's one-visible-column invariant never blocks a valid transition.
|
|
160
|
+
for (const id of this.getFieldOrder()) {
|
|
161
|
+
if (!hidden.has(id)) this.table.setColumnVisible(id, true, { silent: true });
|
|
162
|
+
}
|
|
163
|
+
for (const id of this.getFieldOrder()) {
|
|
164
|
+
if (hidden.has(id)) this.table.setColumnVisible(id, false, { silent: true });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** @private @returns {void} */
|
|
169
|
+
_bridgeTableEvents() {
|
|
170
|
+
// Table and RecordView intentionally share these event names but not their detail keys. Keep
|
|
171
|
+
// the inner DOM events available on the Table root without leaking a second, row-shaped event
|
|
172
|
+
// through the TableView root; the bridged record-shaped event is emitted there once below.
|
|
173
|
+
this.listen(this._tableHost, 'zx-datachange', (event) => event.stopPropagation());
|
|
174
|
+
this.listen(this._tableHost, 'zx-selectionchange', (event) => event.stopPropagation());
|
|
175
|
+
this.listen(this.table, 'rowclick', (event) => {
|
|
176
|
+
const detail = /** @type {CustomEvent<Record<string,unknown>>} */ (event).detail;
|
|
177
|
+
this.emit('recordclick', { ...detail, record: detail.row });
|
|
178
|
+
});
|
|
179
|
+
this.listen(this.table, 'rowdblclick', (event) => {
|
|
180
|
+
const detail = /** @type {CustomEvent<Record<string,unknown>>} */ (event).detail;
|
|
181
|
+
this.emit('recorddblclick', { ...detail, record: detail.row });
|
|
182
|
+
});
|
|
183
|
+
this.listen(this.table, 'sort', (event) => this._tableSortChanged(
|
|
184
|
+
/** @type {CustomEvent<{id:string|null,dir:'asc'|'desc'|null}>} */ (event).detail));
|
|
185
|
+
this.listen(this.table, 'selectionchange', (event) => this._tableSelectionChanged(
|
|
186
|
+
/** @type {CustomEvent<{rows:TableViewRecord[],ids:unknown[]}>} */ (event).detail));
|
|
187
|
+
this.listen(this.table, 'datachange', () => this._tableDataChanged());
|
|
188
|
+
this.listen(this.table, 'columnvisibilitychange', (event) => this._tableVisibilityChanged(
|
|
189
|
+
/** @type {CustomEvent<{visible:string[],hidden:string[]}>} */ (event).detail));
|
|
190
|
+
this.listen(this.table, 'columnorderchange', (event) => this._tableOrderChanged(
|
|
191
|
+
/** @type {CustomEvent<{order:string[]}>} */ (event).detail));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @private @param {{id:string|null,dir:'asc'|'desc'|null}} detail Sort detail. @returns {void} */
|
|
195
|
+
_tableSortChanged(detail) {
|
|
196
|
+
if (this._tableSyncDepth > 0) return;
|
|
197
|
+
const next = /** @type {ViewSort|null} */ (detail.id == null ? null
|
|
198
|
+
: { id: String(detail.id), dir: detail.dir === 'desc' ? 'desc' : 'asc' });
|
|
199
|
+
if (sameSort(next, this._viewSort)) return;
|
|
200
|
+
this._viewSort = next;
|
|
201
|
+
if (this.options.sortMode === 'local') this._viewData = this.table.getData();
|
|
202
|
+
this.emit('sortchange', next ? { ...next } : { id: null, dir: null });
|
|
203
|
+
this._emitTableViewState('sort');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @private @param {{rows:TableViewRecord[],ids:unknown[]}} detail Selection detail. @returns {void} */
|
|
207
|
+
_tableSelectionChanged(detail) {
|
|
208
|
+
if (this._tableSyncDepth > 0) return;
|
|
209
|
+
const next = new Set(detail.ids);
|
|
210
|
+
if (sameSet(next, this._viewSelected)) return;
|
|
211
|
+
this._viewSelected = next;
|
|
212
|
+
this._viewSelectionAnchor = detail.ids.at(-1) ?? null;
|
|
213
|
+
this._emitTableViewSelection();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** @private @returns {void} */
|
|
217
|
+
_tableDataChanged() {
|
|
218
|
+
if (this._tableSyncDepth > 0) return;
|
|
219
|
+
this._viewData = this.table.getData();
|
|
220
|
+
const valid = new Set(this._viewData.map((record) => this._viewRecordId(record)));
|
|
221
|
+
const nextSelection = new Set([...this._viewSelected].filter((id) => valid.has(id)));
|
|
222
|
+
const selectionChanged = !sameSet(nextSelection, this._viewSelected);
|
|
223
|
+
this._viewSelected = nextSelection;
|
|
224
|
+
if (this._viewSelectionAnchor != null && !valid.has(this._viewSelectionAnchor)) {
|
|
225
|
+
this._viewSelectionAnchor = null;
|
|
226
|
+
}
|
|
227
|
+
if (selectionChanged) this._emitTableViewSelection();
|
|
228
|
+
this.emit('datachange', { records: this.getData() });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** @private @param {{visible:string[],hidden:string[]}} detail Visibility detail. @returns {void} */
|
|
232
|
+
_tableVisibilityChanged(detail) {
|
|
233
|
+
if (this._tableSyncDepth > 0) return;
|
|
234
|
+
const allowed = new Set(this.getFieldOrder());
|
|
235
|
+
const next = new Set(detail.hidden.filter((id) => allowed.has(id)));
|
|
236
|
+
if (sameSet(next, this._viewHidden)) return;
|
|
237
|
+
this._viewHidden = next;
|
|
238
|
+
this.emit('fieldvisibilitychange', {
|
|
239
|
+
visible: this.getVisibleFields().map((field) => field.id), hidden: this.getHiddenFields()
|
|
240
|
+
});
|
|
241
|
+
this._emitTableViewState('fieldvisibility');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** @private @param {{order:string[]}} detail Order detail. @returns {void} */
|
|
245
|
+
_tableOrderChanged(detail) {
|
|
246
|
+
if (this._tableSyncDepth > 0) return;
|
|
247
|
+
const allowed = new Set(this.getFieldOrder());
|
|
248
|
+
const next = [...new Set(detail.order.filter((id) => allowed.has(id))),
|
|
249
|
+
...this.getFieldOrder().filter((id) => !detail.order.includes(id))];
|
|
250
|
+
if (sameArray(next, this._viewFieldOrder)) return;
|
|
251
|
+
this._viewFieldOrder = next;
|
|
252
|
+
this.emit('fieldorderchange', { order: this.getFieldOrder() });
|
|
253
|
+
this._emitTableViewState('fieldorder');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** @private @returns {void} */
|
|
257
|
+
_emitTableViewSelection() {
|
|
258
|
+
this.emit('selectionchange', { records: this.getSelection(), ids: this.getSelectionIds() });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** @private @param {string} reason State-change reason. @returns {void} */
|
|
262
|
+
_emitTableViewState(reason) {
|
|
263
|
+
this.emit('statechange', { state: this.getViewState(), reason });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Maps common fields to Table columns without mutating descriptors. Shared get/render/sortValue
|
|
269
|
+
* semantics win; `field.view.table` may provide the remaining advanced TableColumn properties.
|
|
270
|
+
* @param {ViewField[]} fields Shared fields in display order.
|
|
271
|
+
* @returns {TableColumn[]} Table columns.
|
|
272
|
+
*/
|
|
273
|
+
export function fieldsToTableColumns(fields) {
|
|
274
|
+
return fields.map((field) => {
|
|
275
|
+
const table = /** @type {Record<string,any>} */ (field.view && typeof field.view === 'object'
|
|
276
|
+
&& field.view.table && typeof field.view.table === 'object'
|
|
277
|
+
? field.view.table : {});
|
|
278
|
+
const column = /** @type {TableColumn & Record<string,unknown>} */ ({ ...field, ...table,
|
|
279
|
+
id: field.id, label: field.label, sortable: Boolean(field.sortable) });
|
|
280
|
+
delete column.get;
|
|
281
|
+
delete column.visible;
|
|
282
|
+
delete column.duplicate;
|
|
283
|
+
delete column.view;
|
|
284
|
+
if (typeof field.render === 'function') {
|
|
285
|
+
column.render = (record, index) => field.render(record, index, readViewField(field, record, index));
|
|
286
|
+
} else if (typeof table.render !== 'function' && typeof field.get === 'function') {
|
|
287
|
+
const formatter = { ...column };
|
|
288
|
+
delete formatter.render;
|
|
289
|
+
column.render = (record, index) => formatTableCell(formatter,
|
|
290
|
+
{ ...record, [field.id]: readViewField(field, record, index) }, index);
|
|
291
|
+
}
|
|
292
|
+
if (typeof field.sortValue === 'function') {
|
|
293
|
+
column.sortValue = (record) => field.sortValue(record, 0);
|
|
294
|
+
} else if (typeof table.sortValue !== 'function' && typeof field.get === 'function') {
|
|
295
|
+
column.sortValue = (record) => readViewField(field, record, 0);
|
|
296
|
+
}
|
|
297
|
+
return column;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Builds the composed Table configuration. Shared options deliberately overwrite overlapping
|
|
303
|
+
* low-level options; every advanced option remains untouched.
|
|
304
|
+
* @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.
|
|
306
|
+
* @returns {TableOptions} Table configuration.
|
|
307
|
+
*/
|
|
308
|
+
export function tableOptionsForView(table, shared) {
|
|
309
|
+
return {
|
|
310
|
+
...(table && typeof table === 'object' ? table : {}),
|
|
311
|
+
columns: fieldsToTableColumns(shared.fields),
|
|
312
|
+
data: [...shared.data],
|
|
313
|
+
rowId: shared.recordId,
|
|
314
|
+
sort: shared.sort ? { ...shared.sort } : null,
|
|
315
|
+
sortMode: shared.sortMode,
|
|
316
|
+
selectable: shared.selectable,
|
|
317
|
+
hiddenColumns: [...shared.hiddenFields],
|
|
318
|
+
columnVisibility: Boolean(shared.fieldControls),
|
|
319
|
+
columnReorder: Boolean(shared.fieldControls),
|
|
320
|
+
emptyText: shared.emptyText
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** @event TableView#recordclick @type {CustomEvent<Record<string,unknown>>} */
|
|
325
|
+
/** @event TableView#recorddblclick @type {CustomEvent<Record<string,unknown>>} */
|
|
326
|
+
/** @event TableView#datachange @type {CustomEvent<{records:TableViewRecord[]}>} */
|
|
327
|
+
/** @event TableView#selectionchange @type {CustomEvent<{records:TableViewRecord[],ids:unknown[]}>} */
|
|
328
|
+
/** @event TableView#sortchange @type {CustomEvent<{id:string|null,dir:'asc'|'desc'|null}>} */
|
|
329
|
+
/** @event TableView#fieldvisibilitychange @type {CustomEvent<{visible:string[],hidden:string[]}>} */
|
|
330
|
+
/** @event TableView#fieldorderchange @type {CustomEvent<{order:string[]}>} */
|
|
331
|
+
/** @event TableView#statechange @type {CustomEvent<Record<string,unknown>>} */
|
|
332
|
+
|
|
333
|
+
/** @param {ViewSort|null} left @param {ViewSort|null} right @returns {boolean} */
|
|
334
|
+
function sameSort(left, right) {
|
|
335
|
+
return left === null && right === null
|
|
336
|
+
|| Boolean(left && right && left.id === right.id && left.dir === right.dir);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** @param {Set<unknown>} left @param {Set<unknown>} right @returns {boolean} */
|
|
340
|
+
function sameSet(left, right) {
|
|
341
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** @param {unknown[]} left @param {unknown[]} right @returns {boolean} */
|
|
345
|
+
function sameArray(left, right) {
|
|
346
|
+
return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
347
|
+
}
|
|
@@ -122,14 +122,26 @@
|
|
|
122
122
|
inline-size: max-content;
|
|
123
123
|
max-inline-size: calc(100vw - var(--zx-space-4));
|
|
124
124
|
overflow: hidden;
|
|
125
|
-
border: 1px solid var(--zx-color-border
|
|
125
|
+
border: 1px solid var(--zx-color-overlay-border);
|
|
126
126
|
border-radius: var(--zx-radius-md);
|
|
127
|
-
background: var(--zx-color-
|
|
128
|
-
|
|
127
|
+
background: var(--zx-color-overlay-surface);
|
|
128
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
129
|
+
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
130
|
+
box-shadow: var(--zx-glass-raised-shadow);
|
|
129
131
|
color: var(--zx-color-text);
|
|
130
132
|
padding: var(--zx-space-1);
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
136
|
+
.zx-tag-picker__panel {
|
|
137
|
+
border-color: var(--zx-color-border-strong);
|
|
138
|
+
background: var(--zx-color-bg-raised);
|
|
139
|
+
-webkit-backdrop-filter: none;
|
|
140
|
+
backdrop-filter: none;
|
|
141
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
133
145
|
.zx-tag-picker__list {
|
|
134
146
|
min-inline-size: 0;
|
|
135
147
|
overflow-y: auto;
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
.zx-tooltip {
|
|
8
8
|
max-inline-size: var(--zx-tooltip-max-inline-size, 260px);
|
|
9
|
-
border: 1px solid var(--zx-color-border);
|
|
9
|
+
border: 1px solid var(--zx-color-overlay-border);
|
|
10
10
|
border-radius: var(--zx-radius-md);
|
|
11
|
-
background: var(--zx-color-
|
|
12
|
-
|
|
11
|
+
background: var(--zx-color-overlay-surface);
|
|
12
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
13
|
+
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
14
|
+
box-shadow: var(--zx-glass-raised-shadow);
|
|
13
15
|
padding: var(--zx-space-1) var(--zx-space-2);
|
|
14
16
|
color: var(--zx-color-text);
|
|
15
17
|
font-size: var(--zx-text-sm);
|
|
@@ -22,6 +24,16 @@
|
|
|
22
24
|
user-select: none;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
28
|
+
.zx-tooltip {
|
|
29
|
+
border-color: var(--zx-color-border);
|
|
30
|
+
background: var(--zx-color-bg-raised);
|
|
31
|
+
-webkit-backdrop-filter: none;
|
|
32
|
+
backdrop-filter: none;
|
|
33
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
@media (prefers-reduced-motion: no-preference) {
|
|
26
38
|
.zx-tooltip:popover-open {
|
|
27
39
|
animation: zx-tooltip-enter var(--zx-dur-fast) var(--zx-ease);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
.zx-record-view {
|
|
2
|
+
min-inline-size: 0;
|
|
3
|
+
color: var(--zx-color-text);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.zx-record-view__toolbar {
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: flex-end;
|
|
10
|
+
gap: var(--zx-space-2);
|
|
11
|
+
margin-block-end: var(--zx-space-2);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.zx-record-view__field-controls {
|
|
15
|
+
position: relative;
|
|
16
|
+
inline-size: max-content;
|
|
17
|
+
margin-inline-start: auto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.zx-record-view__field-controls > summary {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--zx-space-1);
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
list-style: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.zx-record-view__field-controls > summary::-webkit-details-marker {
|
|
29
|
+
display: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.zx-record-view__field-list {
|
|
33
|
+
position: absolute;
|
|
34
|
+
z-index: 4;
|
|
35
|
+
inset-block-start: calc(100% + var(--zx-space-1));
|
|
36
|
+
inset-inline-end: 0;
|
|
37
|
+
display: grid;
|
|
38
|
+
min-inline-size: 230px;
|
|
39
|
+
max-block-size: min(360px, 70vh);
|
|
40
|
+
overflow: auto;
|
|
41
|
+
border: 1px solid var(--zx-color-border-strong);
|
|
42
|
+
border-radius: var(--zx-radius-md);
|
|
43
|
+
background: var(--zx-color-bg-raised);
|
|
44
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
45
|
+
padding: var(--zx-space-2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.zx-record-view__field-option {
|
|
49
|
+
display: grid;
|
|
50
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--zx-space-2);
|
|
53
|
+
min-block-size: var(--zx-control-height);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.zx-record-view__field-option > label {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: var(--zx-space-2);
|
|
60
|
+
min-inline-size: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.zx-record-view__field-option > label > span {
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
text-overflow: ellipsis;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.zx-record-view__field-order {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.zx-record-view__field-move {
|
|
74
|
+
display: inline-grid;
|
|
75
|
+
place-items: center;
|
|
76
|
+
inline-size: 26px;
|
|
77
|
+
block-size: 26px;
|
|
78
|
+
border: 0;
|
|
79
|
+
border-radius: var(--zx-radius-sm);
|
|
80
|
+
background: transparent;
|
|
81
|
+
color: var(--zx-color-text-muted);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.zx-record-view__field-move:hover:not(:disabled) {
|
|
86
|
+
background: var(--zx-color-bg-hover);
|
|
87
|
+
color: var(--zx-color-text);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.zx-record-view__field-move:focus-visible {
|
|
91
|
+
outline: none;
|
|
92
|
+
box-shadow: var(--zx-focus-ring);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.zx-record-view__field-move:disabled {
|
|
96
|
+
opacity: .35;
|
|
97
|
+
cursor: not-allowed;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.zx-record-view[data-loading="true"] {
|
|
101
|
+
cursor: progress;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.zx-record-view[data-loading="true"] > :not(.zx-record-view__toolbar) {
|
|
105
|
+
opacity: .62;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
109
|
+
.zx-record-view__field-move,
|
|
110
|
+
.zx-record-view[data-loading="true"] > :not(.zx-record-view__toolbar) {
|
|
111
|
+
transition: color var(--zx-dur-fast) var(--zx-ease),
|
|
112
|
+
background var(--zx-dur-fast) var(--zx-ease), opacity var(--zx-dur-fast) var(--zx-ease);
|
|
113
|
+
}
|
|
114
|
+
}
|