@kubex/zinc 1.1.161 → 1.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +266 -118
- package/dist/vscode.html-custom-data.json +13 -14
- package/dist/web-types.json +31 -30
- package/dist/zn.d.ts +22 -8
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +335 -301
- package/docs/pages/components/data-table.md +30 -20
- package/package.json +1 -1
- package/scss/themes/_dark.scss +1 -0
- package/scss/themes/_light.scss +1 -0
- package/src/components/button/button.scss +5 -0
- package/src/components/checkbox/checkbox.component.ts +10 -6
- package/src/components/checkbox/checkbox.scss +4 -8
- package/src/components/data-table/data-table.component.ts +171 -76
- package/src/components/data-table/data-table.scss +16 -60
- package/src/components/dropdown/dropdown.component.ts +6 -3
- package/src/components/panel/panel.scss +23 -0
- package/src/components/popup/popup.component.ts +8 -1
- package/src/components/tooltip/tooltip.component.ts +10 -1
- package/src/components/tooltip/tooltip.scss +5 -0
|
@@ -156,7 +156,7 @@ Use `hide-pagination` to hide pagination controls even when multiple pages are a
|
|
|
156
156
|
|
|
157
157
|
### Row Selection
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
Rows get a checkbox column and can be selected by clicking once the table has an action to use them: something in the `delete-action`, `modify-action`, `create-action` or `actions` slot. Without one, the table has no checkboxes.
|
|
160
160
|
|
|
161
161
|
```html:preview
|
|
162
162
|
<zn-data-table
|
|
@@ -176,28 +176,11 @@ By default, rows can be selected by clicking. Selected rows are tracked and can
|
|
|
176
176
|
</zn-data-table>
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
### Hide Checkboxes
|
|
180
|
-
|
|
181
|
-
Use `hide-checkboxes` to disable row selection functionality.
|
|
182
|
-
|
|
183
|
-
```html:preview
|
|
184
|
-
<zn-data-table
|
|
185
|
-
data-uri="/data/data-table.json"
|
|
186
|
-
method="GET"
|
|
187
|
-
hide-checkboxes
|
|
188
|
-
headers='[
|
|
189
|
-
{"key":"id","label":"ID"},
|
|
190
|
-
{"key":"name","label":"Name"},
|
|
191
|
-
{"key":"email","label":"Email"}
|
|
192
|
-
]'>
|
|
193
|
-
</zn-data-table>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
179
|
### Bulk Actions
|
|
197
180
|
|
|
198
181
|
Add action buttons to the table header for performing operations on selected rows. Use the `delete-action`, `modify-action`, and `create-action` slots.
|
|
199
182
|
|
|
200
|
-
`delete-action` and `modify-action`
|
|
183
|
+
`delete-action` and `modify-action` appear at the start of the header's right-hand group once rows are selected. `create-action` is a primary action rather than a selection one, so it renders at the end of that group, after the search field.
|
|
201
184
|
|
|
202
185
|
```html:preview
|
|
203
186
|
<zn-data-table
|
|
@@ -225,6 +208,32 @@ Add action buttons to the table header for performing operations on selected row
|
|
|
225
208
|
</zn-data-table>
|
|
226
209
|
```
|
|
227
210
|
|
|
211
|
+
### Actions Menu
|
|
212
|
+
|
|
213
|
+
Elements in the `actions` slot are listed in an ellipsis menu at the start of the header's right-hand group. The menu is disabled until at least one row is checked. Each `zn-button` becomes a menu item using its label, `icon` and `color`, and choosing it clicks the button. A `zn-confirm` takes its label and icon from its `trigger` slot button, and choosing it opens the confirm dialog. An `input[name="keys"]` inside a slotted element is kept filled with the selected row keys, the same as for `delete-action`.
|
|
214
|
+
|
|
215
|
+
```html:preview
|
|
216
|
+
<zn-data-table
|
|
217
|
+
data-uri="/data/data-table.json"
|
|
218
|
+
method="GET"
|
|
219
|
+
caption="Customers"
|
|
220
|
+
headers='[
|
|
221
|
+
{"key":"id","label":"ID"},
|
|
222
|
+
{"key":"name","label":"Name"},
|
|
223
|
+
{"key":"email","label":"Email"},
|
|
224
|
+
{"key":"status","label":"Status"}
|
|
225
|
+
]'>
|
|
226
|
+
|
|
227
|
+
<zn-button slot="actions" icon="archive@lu">Archive</zn-button>
|
|
228
|
+
|
|
229
|
+
<zn-confirm slot="actions" type="error" caption="Delete customers" content="Delete the selected customers?">
|
|
230
|
+
<zn-button slot="trigger" icon="trash-2@lu" color="error">Delete</zn-button>
|
|
231
|
+
<form><input type="hidden" name="keys"></form>
|
|
232
|
+
</zn-confirm>
|
|
233
|
+
|
|
234
|
+
</zn-data-table>
|
|
235
|
+
```
|
|
236
|
+
|
|
228
237
|
### Secondary Columns
|
|
229
238
|
|
|
230
239
|
Mark columns as secondary to hide them by default. Users can expand rows to view secondary data.
|
|
@@ -862,6 +871,7 @@ Access table methods programmatically to control behavior.
|
|
|
862
871
|
{"key":"email","label":"Email"},
|
|
863
872
|
{"key":"status","label":"Status"}
|
|
864
873
|
]'>
|
|
874
|
+
<zn-button slot="delete-action" color="error" icon="delete">Delete Selected</zn-button>
|
|
865
875
|
</zn-data-table>
|
|
866
876
|
|
|
867
877
|
<br />
|
|
@@ -877,7 +887,7 @@ Access table methods programmatically to control behavior.
|
|
|
877
887
|
});
|
|
878
888
|
|
|
879
889
|
document.querySelector('#select-all-btn').addEventListener('click', () => {
|
|
880
|
-
table.selectAll(
|
|
890
|
+
table.selectAll();
|
|
881
891
|
});
|
|
882
892
|
</script>
|
|
883
893
|
```
|
package/package.json
CHANGED
package/scss/themes/_dark.scss
CHANGED
|
@@ -274,6 +274,7 @@
|
|
|
274
274
|
--zn-input-border-color-hover: rgb(var(--zn-color-border-active));
|
|
275
275
|
--zn-input-border-color-focus: var(--zn-color-primary-500);
|
|
276
276
|
--zn-input-border-color-disabled: hsl(280, 6%, 9%);
|
|
277
|
+
--zn-checkbox-border-color: rgb(var(--zn-color-border-active));
|
|
277
278
|
|
|
278
279
|
/* Focus rings */
|
|
279
280
|
--zn-focus-ring-color: var(--zn-color-primary-700);
|
package/scss/themes/_light.scss
CHANGED
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
--zn-input-border-color-hover: var(--zn-color-neutral-400);
|
|
218
218
|
--zn-input-border-color-focus: var(--zn-color-primary-500);
|
|
219
219
|
--zn-input-border-color-disabled: var(--zn-color-neutral-300);
|
|
220
|
+
--zn-checkbox-border-color: var(--zn-color-neutral-400);
|
|
220
221
|
--zn-input-border-width: 1px;
|
|
221
222
|
|
|
222
223
|
/* Input Required Content */
|
|
@@ -487,6 +487,11 @@
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
// Lift the focused button so later siblings don't paint over its focus ring
|
|
491
|
+
:host([data-zn-button-group__button]:focus-within) {
|
|
492
|
+
z-index: 1;
|
|
493
|
+
}
|
|
494
|
+
|
|
490
495
|
:host([data-zn-button-group__button--first]:not([data-zn-button-group__button--last])) .button {
|
|
491
496
|
border-radius: 5px 0 0 5px !important;
|
|
492
497
|
border-right: 0 !important;
|
|
@@ -43,7 +43,7 @@ type ColorOption = 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'suc
|
|
|
43
43
|
* @csspart control--indeterminate - Matches the control part when the checkbox is indeterminate.
|
|
44
44
|
* @csspart checked-icon - The checked icon, an `<zn-icon>` element.
|
|
45
45
|
* @csspart unchecked-icon - The unchecked icon, an `<zn-icon>` element.
|
|
46
|
-
* @csspart indeterminate-icon - The indeterminate icon, an `<
|
|
46
|
+
* @csspart indeterminate-icon - The indeterminate icon, an `<svg>` element.
|
|
47
47
|
* @csspart image-container - The wrapper around the built-in image or image slot.
|
|
48
48
|
* @csspart image - The built-in image.
|
|
49
49
|
* @csspart card-title - The title inside a selection card.
|
|
@@ -404,11 +404,15 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
|
|
|
404
404
|
|
|
405
405
|
${!this.checked && this.indeterminate
|
|
406
406
|
? html`
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
407
|
+
<svg viewBox="0 0 24 24"
|
|
408
|
+
fill="none"
|
|
409
|
+
stroke="currentColor"
|
|
410
|
+
stroke-width="4"
|
|
411
|
+
stroke-linecap="round"
|
|
412
|
+
part="indeterminate-icon"
|
|
413
|
+
class="checkbox__indeterminate-icon">
|
|
414
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
415
|
+
</svg>`
|
|
412
416
|
: ''}
|
|
413
417
|
</span>
|
|
414
418
|
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
--checkbox-color: var(--zn-color-primary-600);
|
|
29
29
|
--checkbox-color-hover: var(--zn-color-primary-500);
|
|
30
30
|
--checkbox-icon-color: var(--zn-color-neutral-0);
|
|
31
|
-
--checkbox-border-color: var(--zn-input-border-color);
|
|
31
|
+
--checkbox-border-color: var(--zn-checkbox-border-color, var(--zn-input-border-color));
|
|
32
32
|
--checkbox-bg-color: var(--zn-input-background-color);
|
|
33
33
|
|
|
34
34
|
&.checkbox--default {
|
|
35
35
|
--checkbox-color: var(--zn-color-primary-600);
|
|
36
36
|
--checkbox-color-hover: var(--zn-color-primary-500);
|
|
37
|
-
--checkbox-border-color: var(--zn-input-border-color);
|
|
37
|
+
--checkbox-border-color: var(--zn-checkbox-border-color, var(--zn-input-border-color));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
&.checkbox--primary {
|
|
@@ -131,12 +131,8 @@
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
.checkbox__indeterminate-icon {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
display: inline-flex;
|
|
138
|
-
width: var(--toggle-size);
|
|
139
|
-
height: var(--toggle-size);
|
|
134
|
+
height: max(16px, 70%);
|
|
135
|
+
color: var(--checkbox-icon-color);
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
.svg-icon {
|
|
@@ -2,7 +2,7 @@ import {classMap} from "lit/directives/class-map.js";
|
|
|
2
2
|
import {type CSSResultGroup, html, nothing, type PropertyValues, type TemplateResult, unsafeCSS} from 'lit';
|
|
3
3
|
import {HasSlotController} from "../../internal/slot";
|
|
4
4
|
import {ifDefined} from "lit/directives/if-defined.js";
|
|
5
|
-
import {property,
|
|
5
|
+
import {property, state} from 'lit/decorators.js';
|
|
6
6
|
import {ref} from "lit/directives/ref.js";
|
|
7
7
|
import {ResizeController} from '@lit-labs/observers/resize-controller.js';
|
|
8
8
|
import {Task} from "@lit/task";
|
|
@@ -91,6 +91,7 @@ interface Response {
|
|
|
91
91
|
export enum ActionSlots {
|
|
92
92
|
delete = 'delete-action',
|
|
93
93
|
modify = 'modify-action',
|
|
94
|
+
actions = 'actions',
|
|
94
95
|
create = 'create-action',
|
|
95
96
|
filter = 'filter',
|
|
96
97
|
filter_top = 'filter-top',
|
|
@@ -188,8 +189,9 @@ type AllowedInputElement =
|
|
|
188
189
|
* @slot sort - Slot for sort component, in the header's right-hand group.
|
|
189
190
|
* @slot filter - Slot for filter component, in the header's right-hand group.
|
|
190
191
|
* @slot filter-top - Slot for a top-level filter component, rendered above the table's panel.
|
|
191
|
-
* @slot delete-action - Slot for delete action button, shown
|
|
192
|
-
* @slot modify-action - Slot for modify action button, shown
|
|
192
|
+
* @slot delete-action - Slot for delete action button, shown in the header once rows are selected.
|
|
193
|
+
* @slot modify-action - Slot for modify action button, shown in the header once rows are selected.
|
|
194
|
+
* @slot actions - `zn-button` or `zn-confirm` elements listed in a header menu, enabled once rows are selected.
|
|
193
195
|
* @slot create-action - Slot for create action button, at the end of the header's right-hand group.
|
|
194
196
|
* @slot inputs - Slot for additional input controls.
|
|
195
197
|
* @slot empty-state - Slot for custom empty state.
|
|
@@ -261,9 +263,6 @@ export default class ZnDataTable extends ZincElement {
|
|
|
261
263
|
|
|
262
264
|
@property({attribute: "empty-state-icon"}) emptyStateIcon: string = "data_alert";
|
|
263
265
|
|
|
264
|
-
// Hide the checkbox column
|
|
265
|
-
@property({attribute: 'hide-checkboxes', type: Boolean}) hideCheckboxes: boolean;
|
|
266
|
-
|
|
267
266
|
@property() filters: [] = [];
|
|
268
267
|
|
|
269
268
|
@property() method: 'GET' | 'POST' = 'POST';
|
|
@@ -288,7 +287,6 @@ export default class ZnDataTable extends ZincElement {
|
|
|
288
287
|
@property() groups = '';
|
|
289
288
|
|
|
290
289
|
@property({attribute: 'per-page-size', type: Number}) itemsPerPage: number = DEFAULT_PER_PAGE;
|
|
291
|
-
@query('#select-all-rows') selectAllButton: ZnButton;
|
|
292
290
|
|
|
293
291
|
// Data Table Properties
|
|
294
292
|
private _initialLoad = true;
|
|
@@ -329,6 +327,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
329
327
|
ActionSlots.search.valueOf(),
|
|
330
328
|
ActionSlots.delete.valueOf(),
|
|
331
329
|
ActionSlots.modify.valueOf(),
|
|
330
|
+
ActionSlots.actions.valueOf(),
|
|
332
331
|
ActionSlots.create.valueOf(),
|
|
333
332
|
ActionSlots.filter.valueOf(),
|
|
334
333
|
ActionSlots.sort.valueOf(),
|
|
@@ -673,8 +672,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
673
672
|
return html`
|
|
674
673
|
<div>${this.loadingTable()}</div>`;
|
|
675
674
|
}
|
|
676
|
-
return
|
|
677
|
-
<div class="reduced-opacity">${this._lastTableContent}</div>`;
|
|
675
|
+
return this.tableContent(this._lastTableContent, true);
|
|
678
676
|
},
|
|
679
677
|
complete: (data) => {
|
|
680
678
|
this._initialLoad = false;
|
|
@@ -683,7 +681,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
683
681
|
emptyBody = !this._lastLoadHadRows;
|
|
684
682
|
this._lastTableContent = html`
|
|
685
683
|
<div>${this.renderTable(data as Response)}</div>`;
|
|
686
|
-
return this._lastTableContent;
|
|
684
|
+
return this.tableContent(this._lastTableContent, false);
|
|
687
685
|
},
|
|
688
686
|
error: (error) => {
|
|
689
687
|
if (error instanceof Error) {
|
|
@@ -719,6 +717,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
719
717
|
|
|
720
718
|
const hasActions = this.hasSlotController.test(ActionSlots.delete.valueOf())
|
|
721
719
|
|| this.hasSlotController.test(ActionSlots.modify.valueOf())
|
|
720
|
+
|| this.hasSlotController.test(ActionSlots.actions.valueOf())
|
|
722
721
|
|| this.hasSlotController.test(ActionSlots.create.valueOf())
|
|
723
722
|
|| this.hasSlotController.test(ActionSlots.sort.valueOf())
|
|
724
723
|
|| this.hasSlotController.test(ActionSlots.search.valueOf());
|
|
@@ -902,11 +901,14 @@ export default class ZnDataTable extends ZincElement {
|
|
|
902
901
|
const error = data?.error?.text ? data.error : undefined;
|
|
903
902
|
|
|
904
903
|
if (!data?.rows || data.rows.length === 0) {
|
|
904
|
+
this._rows = [];
|
|
905
|
+
this.pruneSelectedRows();
|
|
905
906
|
return html`${error ? html`
|
|
906
907
|
<div class="table__error">${this.renderErrorAlert(error)}</div>` : nothing}${this.emptyState()}`;
|
|
907
908
|
}
|
|
908
909
|
|
|
909
910
|
this._rows = this.getRows(data);
|
|
911
|
+
this.pruneSelectedRows();
|
|
910
912
|
|
|
911
913
|
if (this.groupBy) {
|
|
912
914
|
// we need to group the rows by the group column
|
|
@@ -1029,16 +1031,18 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1029
1031
|
});
|
|
1030
1032
|
|
|
1031
1033
|
const anyHidden = this.hasHiddenColumns();
|
|
1032
|
-
const
|
|
1034
|
+
const showCheckboxes = this.isSelectable();
|
|
1035
|
+
const colCount = filteredHeaders.length + (this.rowHasActions ? 1 : 0) + (anyHidden ? 1 : 0) + (showCheckboxes ? 1 : 0);
|
|
1033
1036
|
|
|
1034
1037
|
return html`
|
|
1035
1038
|
<div class="table__scroll">
|
|
1036
1039
|
<table class="${classMap({
|
|
1037
1040
|
'table': true,
|
|
1038
|
-
'with-hover': !this.unsortable &&
|
|
1041
|
+
'with-hover': !this.unsortable && this.isSelectable(),
|
|
1039
1042
|
})}">
|
|
1040
1043
|
<thead>
|
|
1041
1044
|
<tr>
|
|
1045
|
+
${showCheckboxes ? this.renderSelectAllCheckbox() : nothing}
|
|
1042
1046
|
${anyHidden ? html`
|
|
1043
1047
|
<th class="table__head table__head--expander"></th>` : nothing}
|
|
1044
1048
|
${filteredHeaders.map((header: HeaderConfig) => this.renderCellHeader(header))}
|
|
@@ -1057,10 +1061,8 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1057
1061
|
<th colspan="${colCount}" scope="colgroup">${group.label}</th>
|
|
1058
1062
|
</tr>` : nothing}
|
|
1059
1063
|
${group.rows.map((row: Row) => html`
|
|
1060
|
-
<tr class="${
|
|
1061
|
-
|
|
1062
|
-
'table__row--data': true,
|
|
1063
|
-
})}" data-row-id="${row.id}">
|
|
1064
|
+
<tr class="table__row--data" data-row-id="${row.id}">
|
|
1065
|
+
${showCheckboxes ? this.renderRowCheckbox(row) : nothing}
|
|
1064
1066
|
${anyHidden ? this.renderExpanderCell(row) : nothing}
|
|
1065
1067
|
${(visibleRowCells.get(row.id) || row.cells).map((value: Cell, index: number) => this.renderCellBody(index, value, row))}
|
|
1066
1068
|
${this.rowHasActions ? this.renderActions(row) : nothing}
|
|
@@ -1074,15 +1076,21 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1074
1076
|
`;
|
|
1075
1077
|
}
|
|
1076
1078
|
|
|
1079
|
+
// Pending and loaded states share this template so Lit keeps the table's DOM (and focus) across loads
|
|
1080
|
+
private tableContent(content: TemplateResult, pending: boolean) {
|
|
1081
|
+
return html`
|
|
1082
|
+
<div class="${classMap({'reduced-opacity': pending})}">${content}</div>`;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1077
1085
|
getTableHeader() {
|
|
1078
1086
|
return html`
|
|
1079
1087
|
<div class="table__header">
|
|
1080
1088
|
<div class="table__header__actions">
|
|
1081
1089
|
${this.caption ? html`
|
|
1082
1090
|
<h3 class="table__header__caption">${this.caption}</h3>` : nothing}
|
|
1083
|
-
${this.getActions()}
|
|
1084
1091
|
</div>
|
|
1085
1092
|
<div class="table__header__right">
|
|
1093
|
+
${this.getActions()}
|
|
1086
1094
|
${this.getHeaderControls()}
|
|
1087
1095
|
</div>
|
|
1088
1096
|
</div>
|
|
@@ -1302,37 +1310,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1302
1310
|
getActions() {
|
|
1303
1311
|
const actions = [];
|
|
1304
1312
|
|
|
1305
|
-
// Select-all only earns its place alongside the bulk actions it feeds
|
|
1306
|
-
const hasBulkActions = this.hasSlotController.test(ActionSlots.delete.valueOf())
|
|
1307
|
-
|| this.hasSlotController.test(ActionSlots.modify.valueOf());
|
|
1308
|
-
|
|
1309
|
-
if (!hasBulkActions) {
|
|
1310
|
-
return [];
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
if (!this.hideCheckboxes && this._rows.length > 0) {
|
|
1314
|
-
actions.push(html`
|
|
1315
|
-
<zn-button @click="${this.selectAll}"
|
|
1316
|
-
id="select-all-rows"
|
|
1317
|
-
color="transparent"
|
|
1318
|
-
icon="indeterminate_check_box"
|
|
1319
|
-
icon-size="22"
|
|
1320
|
-
icon-color="primary"
|
|
1321
|
-
tooltip="Select All"
|
|
1322
|
-
slot="trigger">
|
|
1323
|
-
</zn-button>`);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
1313
|
if (this.selectedRows.length > 0) {
|
|
1327
|
-
actions.push(html`
|
|
1328
|
-
<zn-button @click="${this.clearSelectedRows}"
|
|
1329
|
-
color="transparent"
|
|
1330
|
-
icon="disabled_by_default"
|
|
1331
|
-
icon-size="22"
|
|
1332
|
-
tooltip="Clear Selection"
|
|
1333
|
-
slot="trigger">
|
|
1334
|
-
</zn-button>`);
|
|
1335
|
-
|
|
1336
1314
|
if (this.hasSlotController.test(ActionSlots.delete.valueOf())) {
|
|
1337
1315
|
actions.push(html`
|
|
1338
1316
|
<slot name="${ActionSlots.delete.valueOf()}"></slot>`);
|
|
@@ -1344,9 +1322,72 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1344
1322
|
}
|
|
1345
1323
|
}
|
|
1346
1324
|
|
|
1325
|
+
if (this.hasSlotController.test(ActionSlots.actions.valueOf())) {
|
|
1326
|
+
actions.push(this.getActionsMenu());
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1347
1329
|
return actions;
|
|
1348
1330
|
}
|
|
1349
1331
|
|
|
1332
|
+
private getActionsMenu() {
|
|
1333
|
+
const items = [...this.hasSlotController.getSlots(ActionSlots.actions.valueOf())];
|
|
1334
|
+
const disabled = this.selectedRows.length === 0;
|
|
1335
|
+
|
|
1336
|
+
return html`
|
|
1337
|
+
<zn-dropdown placement="bottom-end" ?disabled="${disabled}">
|
|
1338
|
+
<zn-button slot="trigger"
|
|
1339
|
+
icon-button="small"
|
|
1340
|
+
icon="ellipsis@lu"
|
|
1341
|
+
icon-size="18"
|
|
1342
|
+
tooltip="Actions"
|
|
1343
|
+
aria-label="Selection actions"
|
|
1344
|
+
?disabled="${disabled}">
|
|
1345
|
+
</zn-button>
|
|
1346
|
+
<zn-menu variant="shell">
|
|
1347
|
+
${items.map((item: HTMLElement) => {
|
|
1348
|
+
const trigger = this.actionTrigger(item);
|
|
1349
|
+
const icon = trigger?.getAttribute('icon') || item.getAttribute('icon');
|
|
1350
|
+
return html`
|
|
1351
|
+
<zn-menu-item color="${ifDefined(trigger?.getAttribute('color') || undefined)}"
|
|
1352
|
+
?disabled="${trigger?.hasAttribute('disabled') ?? false}"
|
|
1353
|
+
@click="${() => this.runAction(item)}">
|
|
1354
|
+
${icon ? html`
|
|
1355
|
+
<zn-icon src="${icon}" size="20" slot="prefix"></zn-icon>` : nothing}
|
|
1356
|
+
${this.actionLabel(item, trigger)}
|
|
1357
|
+
</zn-menu-item>`;
|
|
1358
|
+
})}
|
|
1359
|
+
</zn-menu>
|
|
1360
|
+
</zn-dropdown>
|
|
1361
|
+
<div class="table__actions-source">
|
|
1362
|
+
<slot name="${ActionSlots.actions.valueOf()}"></slot>
|
|
1363
|
+
</div>`;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
private actionTrigger(item: HTMLElement): HTMLElement | null {
|
|
1367
|
+
if (item instanceof ZnConfirm) {
|
|
1368
|
+
return item.querySelector<HTMLElement>(':scope > [slot="trigger"]');
|
|
1369
|
+
}
|
|
1370
|
+
return item;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
private actionLabel(item: HTMLElement, trigger: HTMLElement | null): string {
|
|
1374
|
+
return trigger?.textContent?.trim()
|
|
1375
|
+
|| trigger?.getAttribute('content')
|
|
1376
|
+
|| trigger?.getAttribute('tooltip')
|
|
1377
|
+
|| item.getAttribute('caption')
|
|
1378
|
+
|| '';
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
private runAction(item: HTMLElement) {
|
|
1382
|
+
if (item instanceof ZnConfirm) {
|
|
1383
|
+
item.show();
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
// ZnButton.click() skips the native click, so listeners and href would never fire
|
|
1387
|
+
const target = item instanceof ZnButton ? item.button : item;
|
|
1388
|
+
target?.click();
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1350
1391
|
goToPage(page: number) {
|
|
1351
1392
|
this.page = page;
|
|
1352
1393
|
this.selectedRows = [];
|
|
@@ -1380,12 +1421,12 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1380
1421
|
this._dataTask.run().then(r => r);
|
|
1381
1422
|
}
|
|
1382
1423
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
if (
|
|
1424
|
+
/** Selects every row on the page, or clears the selection when every row is already selected. */
|
|
1425
|
+
selectAll() {
|
|
1426
|
+
if (!this.isSelectable()) return;
|
|
1386
1427
|
|
|
1387
1428
|
if (this.numberOfRowsSelected === this._rows.length) {
|
|
1388
|
-
this.clearSelectedRows(
|
|
1429
|
+
this.clearSelectedRows();
|
|
1389
1430
|
return;
|
|
1390
1431
|
}
|
|
1391
1432
|
|
|
@@ -1396,7 +1437,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1396
1437
|
}
|
|
1397
1438
|
|
|
1398
1439
|
selectRow(e: Event) {
|
|
1399
|
-
if (this.
|
|
1440
|
+
if (!this.isSelectable()) {
|
|
1400
1441
|
return;
|
|
1401
1442
|
}
|
|
1402
1443
|
|
|
@@ -1420,13 +1461,14 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1420
1461
|
return;
|
|
1421
1462
|
}
|
|
1422
1463
|
|
|
1423
|
-
const
|
|
1424
|
-
const
|
|
1425
|
-
if (index === -1) return;
|
|
1426
|
-
|
|
1427
|
-
const row = this._rows[index] as Row;
|
|
1464
|
+
const rowId = (parent as HTMLElement).dataset.rowId;
|
|
1465
|
+
const row = this._rows.find((r: Row) => r.id === rowId);
|
|
1428
1466
|
if (!row) return;
|
|
1429
1467
|
|
|
1468
|
+
this.toggleRowSelection(row);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
private toggleRowSelection(row: Row) {
|
|
1430
1472
|
const alreadySelected = this.selectedRows.some((r: Row) => r.id === row.id);
|
|
1431
1473
|
if (alreadySelected) {
|
|
1432
1474
|
this.selectedRows = this.selectedRows.filter((r: Row) => r.id !== row.id);
|
|
@@ -1439,10 +1481,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1439
1481
|
this.requestUpdate();
|
|
1440
1482
|
}
|
|
1441
1483
|
|
|
1442
|
-
clearSelectedRows(
|
|
1443
|
-
const button = event.target as ZnButton;
|
|
1444
|
-
if (button.disabled) return;
|
|
1445
|
-
|
|
1484
|
+
clearSelectedRows() {
|
|
1446
1485
|
this.selectedRows = [];
|
|
1447
1486
|
this.numberOfRowsSelected = 0;
|
|
1448
1487
|
this.updateKeys();
|
|
@@ -1642,7 +1681,10 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1642
1681
|
'table__head--last': header.key === lastVisibleHeaderKey,
|
|
1643
1682
|
'table__head--hidden': Object.values(this.hiddenHeaders).includes(header.key),
|
|
1644
1683
|
})}"
|
|
1645
|
-
|
|
1684
|
+
tabindex="${ifDefined(sortable ? '0' : undefined)}"
|
|
1685
|
+
aria-sort="${ifDefined(sortable ? this.sortState(header.key) : undefined)}"
|
|
1686
|
+
@click="${sortable ? this.updateSort(header.key) : undefined}"
|
|
1687
|
+
@keydown="${sortable ? (e: KeyboardEvent) => this.handleSortKeyDown(e, header.key) : undefined}">
|
|
1646
1688
|
<div>
|
|
1647
1689
|
${header.label}
|
|
1648
1690
|
${sortable ? this.getTableSortIcon(header.key) : nothing}
|
|
@@ -1651,6 +1693,17 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1651
1693
|
`;
|
|
1652
1694
|
}
|
|
1653
1695
|
|
|
1696
|
+
private sortState(key: string): 'ascending' | 'descending' | 'none' {
|
|
1697
|
+
if (this.sortColumn !== key) return 'none';
|
|
1698
|
+
return this.sortDirection === 'asc' ? 'ascending' : 'descending';
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
private handleSortKeyDown(e: KeyboardEvent, key: string) {
|
|
1702
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
1703
|
+
e.preventDefault();
|
|
1704
|
+
this.updateSort(key)();
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1654
1707
|
private renderCellBody(index: number, value: Cell, row: Row) {
|
|
1655
1708
|
const filteredHeaders = this.visibleHeaders();
|
|
1656
1709
|
const header: HeaderConfig | undefined = filteredHeaders[index];
|
|
@@ -1658,7 +1711,7 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1658
1711
|
|
|
1659
1712
|
return html`
|
|
1660
1713
|
<td
|
|
1661
|
-
@click="${this.
|
|
1714
|
+
@click="${this.isSelectable() ? this.selectRow : undefined}"
|
|
1662
1715
|
class="${classMap({
|
|
1663
1716
|
'table__cell': true,
|
|
1664
1717
|
'table__cell--wide': headerKey === this.wideColumn,
|
|
@@ -1731,6 +1784,60 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1731
1784
|
return this.selectedRows.some((r: Row) => r.id === row.id);
|
|
1732
1785
|
}
|
|
1733
1786
|
|
|
1787
|
+
// Drop selections for rows no longer in the result set (filtered out, archived, paged away)
|
|
1788
|
+
// and swap survivors for the fresh row objects so selected keys reflect current cell data.
|
|
1789
|
+
private pruneSelectedRows() {
|
|
1790
|
+
if (this.selectedRows.length === 0) return;
|
|
1791
|
+
|
|
1792
|
+
const current = new Map(this._rows.map((row: Row) => [row.id, row]));
|
|
1793
|
+
const kept = this.selectedRows
|
|
1794
|
+
.map((row: Row) => current.get(row.id))
|
|
1795
|
+
.filter((row): row is Row => row !== undefined);
|
|
1796
|
+
|
|
1797
|
+
const changed = kept.length !== this.selectedRows.length;
|
|
1798
|
+
this.selectedRows = kept;
|
|
1799
|
+
this.numberOfRowsSelected = kept.length;
|
|
1800
|
+
if (changed) this.updateKeys();
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
private isSelectable(): boolean {
|
|
1804
|
+
return [ActionSlots.delete, ActionSlots.modify, ActionSlots.actions, ActionSlots.create]
|
|
1805
|
+
.some(slot => this.hasSlotController.test(slot.valueOf()));
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
private renderSelectAllCheckbox() {
|
|
1809
|
+
const allSelected = this.numberOfRowsSelected === this._rows.length;
|
|
1810
|
+
const someSelected = this.numberOfRowsSelected > 0 && !allSelected;
|
|
1811
|
+
return html`
|
|
1812
|
+
<th class="table__head table__head--checkbox">
|
|
1813
|
+
<div>
|
|
1814
|
+
<zn-checkbox aria-label="Select all rows"
|
|
1815
|
+
.checked="${allSelected}"
|
|
1816
|
+
.indeterminate="${someSelected}"
|
|
1817
|
+
@zn-change="${() => this.selectAll()}">
|
|
1818
|
+
</zn-checkbox>
|
|
1819
|
+
</div>
|
|
1820
|
+
</th>`;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
private renderRowCheckbox(row: Row) {
|
|
1824
|
+
return html`
|
|
1825
|
+
<td class="table__cell table__cell--checkbox" @click="${(e: Event) => this.handleCheckboxCellClick(e, row)}">
|
|
1826
|
+
<div>
|
|
1827
|
+
<zn-checkbox aria-label="Select row"
|
|
1828
|
+
.checked="${this.isRowSelected(row)}"
|
|
1829
|
+
@zn-change="${() => this.toggleRowSelection(row)}">
|
|
1830
|
+
</zn-checkbox>
|
|
1831
|
+
</div>
|
|
1832
|
+
</td>`;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// A click on the checkbox itself arrives twice (label, then input), so leave it to zn-change
|
|
1836
|
+
private handleCheckboxCellClick(e: Event, row: Row) {
|
|
1837
|
+
if ((e.target as Element).closest('zn-checkbox')) return;
|
|
1838
|
+
this.toggleRowSelection(row);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1734
1841
|
private getRows(data: Response): Row[] {
|
|
1735
1842
|
// Copy rows to avoid mutating original data
|
|
1736
1843
|
const sourceRows = Array.isArray(data.rows) ? data.rows.slice() : [];
|
|
@@ -1761,21 +1868,9 @@ export default class ZnDataTable extends ZincElement {
|
|
|
1761
1868
|
}
|
|
1762
1869
|
|
|
1763
1870
|
private updateKeys() {
|
|
1764
|
-
this.updateSelectAll();
|
|
1765
1871
|
this.updateModifyKeys();
|
|
1766
1872
|
this.updateDeleteKeys();
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
private updateSelectAll() {
|
|
1770
|
-
if (!this.selectAllButton) {
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
if (this.numberOfRowsSelected === this._rows.length) {
|
|
1775
|
-
this.selectAllButton.icon = 'check_box';
|
|
1776
|
-
} else {
|
|
1777
|
-
this.selectAllButton.icon = 'indeterminate_check_box';
|
|
1778
|
-
}
|
|
1873
|
+
this.updateActionKeys(ActionSlots.actions.valueOf());
|
|
1779
1874
|
}
|
|
1780
1875
|
|
|
1781
1876
|
private updateModifyKeys() {
|