Skip to content

Commit 51cdf1a

Browse files
committed
chore(DataTable): update test case and row check
1 parent cc890ed commit 51cdf1a

8 files changed

Lines changed: 193 additions & 34 deletions

File tree

playground/DataTable.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,37 @@ <h3>Multi-select</h3>
214214
</sgds-data-table>
215215
</div>
216216

217+
<!-- Multi-select pre-checked on load -->
218+
<div class="container">
219+
<h3>Multi-select (Pre-checked on load)</h3>
220+
<sgds-data-table currentPage="1" dataLength="3" itemsPerPage="5" multiSelect>
221+
<sgds-data-table-row>
222+
<sgds-data-table-head>#</sgds-data-table-head>
223+
<sgds-data-table-head>First name</sgds-data-table-head>
224+
<sgds-data-table-head>Last name</sgds-data-table-head>
225+
<sgds-data-table-head>Username</sgds-data-table-head>
226+
</sgds-data-table-row>
227+
<sgds-data-table-row id="ms-load-row-1">
228+
<sgds-data-table-cell>1</sgds-data-table-cell>
229+
<sgds-data-table-cell>John</sgds-data-table-cell>
230+
<sgds-data-table-cell>Doe</sgds-data-table-cell>
231+
<sgds-data-table-cell>@johndoe</sgds-data-table-cell>
232+
</sgds-data-table-row>
233+
<sgds-data-table-row id="ms-load-row-2" checked>
234+
<sgds-data-table-cell>2</sgds-data-table-cell>
235+
<sgds-data-table-cell>Jane</sgds-data-table-cell>
236+
<sgds-data-table-cell>Doe</sgds-data-table-cell>
237+
<sgds-data-table-cell>@janedoe</sgds-data-table-cell>
238+
</sgds-data-table-row>
239+
<sgds-data-table-row id="ms-load-row-3">
240+
<sgds-data-table-cell>3</sgds-data-table-cell>
241+
<sgds-data-table-cell>Bob</sgds-data-table-cell>
242+
<sgds-data-table-cell>Smith</sgds-data-table-cell>
243+
<sgds-data-table-cell>@bobsmith</sgds-data-table-cell>
244+
</sgds-data-table-row>
245+
</sgds-data-table>
246+
</div>
247+
217248
<!-- Expandable rows -->
218249
<div class="container">
219250
<h3>Expandable rows</h3>

src/components/DataTable/data-table.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ th {
110110
}
111111

112112
.sort .sort-icon {
113-
color: var(--sgds-color-subtle);
113+
color: var(--sgds-color-muted);
114114
}
115115

116116
.sort .active.sort-icon {

src/components/DataTable/sgds-data-table-head.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ import { property } from "lit/decorators.js";
1010
*/
1111
export class SgdsDataTableHead extends SgdsElement {
1212
/** Sets the column width. */
13-
@property({ type: String }) width: string | undefined;
13+
@property({ type: String, reflect: true }) width: string | undefined;
1414

1515
/** Number of columns this cell spans. */
16-
@property({ type: Number }) colspan: number | undefined;
16+
@property({ type: Number, reflect: true }) colspan: number | undefined;
1717

1818
/** Number of rows this cell spans. */
19-
@property({ type: Number }) rowspan: number | undefined;
19+
@property({ type: Number, reflect: true }) rowspan: number | undefined;
2020

2121
/** Text alignment for the header content. */
22-
@property({ type: String }) textAlign: "left" | "right" = "left";
22+
@property({ type: String, reflect: true }) textAlign: "left" | "right" = "left";
2323

2424
/** Current sort direction for this column. */
25-
@property({ type: String }) ariasort: "ascending" | "descending" | "none" | "other" | undefined;
25+
@property({ type: String, reflect: true }) ariasort: "ascending" | "descending" | "none" | "other" | undefined;
2626

2727
/** When true, clicking this header cycles through ascending → descending → none sort. */
28-
@property({ type: Boolean }) sorting = true;
28+
@property({ type: Boolean, reflect: true }) sorting = true;
2929

30-
/** Column key passed in `i-sgds-sort` event detail, used to identify which column to sort. */
31-
@property({ type: String }) sortKey = "";
30+
/** Column key emitted in the row's internal sort payload, used to identify which column to sort. */
31+
@property({ type: String, reflect: true }) sortKey = "";
3232

3333
/** @internal — called by the row when the rendered `<th>` is clicked. */
3434
handleSortClick() {

src/components/DataTable/sgds-data-table-row.ts

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { html, nothing } from "lit";
2-
import { property, query, queryAssignedElements } from "lit/decorators.js";
2+
import { property, query, queryAssignedElements, state } from "lit/decorators.js";
33
import { ifDefined } from "lit/directives/if-defined.js";
44
import SgdsElement from "../../base/sgds-element";
55
import { SgdsCheckbox } from "../Checkbox/sgds-checkbox";
@@ -36,13 +36,16 @@ export class SgdsDataTableRow extends SgdsElement {
3636
};
3737

3838
/** Arbitrary data associated with this row. Returned in event detail on row selection. */
39-
@property({ type: Object }) rowData: Record<string, unknown> = {};
39+
@property({ type: Object, reflect: true }) rowData: Record<string, unknown> = {};
4040

4141
/** When true, the row has an expandable content area toggled by a chevron. */
42-
@property({ type: Boolean }) expand = false;
42+
@property({ type: Boolean, reflect: true }) expand = false;
4343

4444
/** When true, the expandable content area is open. */
45-
@property({ type: Boolean }) open = false;
45+
@property({ type: Boolean, reflect: true }) open = false;
46+
47+
/** When true, the row is checked. */
48+
@property({ type: Boolean, reflect: true }) checked = false;
4649

4750
/** @internal — set by `sgds-data-table` to show a checkbox cell on this row. */
4851
@property({ type: Boolean }) showCheckbox = false;
@@ -62,11 +65,17 @@ export class SgdsDataTableRow extends SgdsElement {
6265
@queryAssignedElements({ flatten: true })
6366
private _assignedCells!: Array<SgdsDataTableCell | SgdsDataTableHead>;
6467

68+
@state() private _isHeaderRow = false;
69+
6570
/** The checkbox rendered inside this row, if `showCheckbox` is true. */
6671
get checkbox(): SgdsCheckbox | null {
6772
return this._checkboxEl ?? null;
6873
}
6974

75+
connectedCallback() {
76+
super.connectedCallback();
77+
}
78+
7079
firstUpdated() {
7180
if (this._expandableBody) {
7281
this._expandableBody.hidden = !this.open;
@@ -108,6 +117,13 @@ export class SgdsDataTableRow extends SgdsElement {
108117
}
109118
}
110119

120+
@watch("checked")
121+
async handleCheckedChange() {
122+
if (this.checked) {
123+
this.emit("i-sgds-change", { detail: { checked: true } });
124+
}
125+
}
126+
111127
/** Opens the expandable content area. */
112128
public async show() {
113129
if (this.open) return;
@@ -123,6 +139,8 @@ export class SgdsDataTableRow extends SgdsElement {
123139
}
124140

125141
private _onSlotChange() {
142+
const cells = this._assignedCells ?? [];
143+
this._isHeaderRow = cells.some(cell => cell instanceof SgdsDataTableHead);
126144
this.requestUpdate();
127145
}
128146

@@ -187,8 +205,8 @@ export class SgdsDataTableRow extends SgdsElement {
187205
</td>`;
188206
}
189207

190-
private _renderExpandCell(isHeaderRow: boolean) {
191-
if (isHeaderRow) {
208+
private _renderExpandCell() {
209+
if (this._isHeaderRow) {
192210
return html`<th class="control-cell" scope="col"></th>`;
193211
}
194212

@@ -199,44 +217,43 @@ export class SgdsDataTableRow extends SgdsElement {
199217
</td>`;
200218
}
201219

202-
private _renderExpandPlaceholder(isHeaderRow: boolean) {
203-
return isHeaderRow ? html`<th class="control-cell" scope="col"></th>` : html`<td class="control-cell"></td>`;
220+
private _renderExpandPlaceholder() {
221+
return this._isHeaderRow ? html`<th class="control-cell" scope="col"></th>` : html`<td class="control-cell"></td>`;
204222
}
205223

206-
private _renderCheckboxCell(isHeaderRow: boolean) {
207-
return isHeaderRow
224+
private _renderCheckboxCell() {
225+
return this._isHeaderRow
208226
? html`<th class="control-cell" scope="col">
209227
<div class="data-table-cell checkbox-cell">
210-
<sgds-checkbox @sgds-change=${this._onCheckboxChange}></sgds-checkbox>
228+
<sgds-checkbox .checked=${this.checked} @sgds-change=${this._onCheckboxChange}></sgds-checkbox>
211229
</div>
212230
</th>`
213231
: html`<td class="control-cell">
214232
<div class="data-table-cell checkbox-cell">
215-
<sgds-checkbox @sgds-change=${this._onCheckboxChange}></sgds-checkbox>
233+
<sgds-checkbox .checked=${this.checked} @sgds-change=${this._onCheckboxChange}></sgds-checkbox>
216234
</div>
217235
</td>`;
218236
}
219237

220-
private _renderHiddenSlotCell(isHeaderRow: boolean) {
221-
return isHeaderRow
238+
private _renderHiddenSlotCell() {
239+
return this._isHeaderRow
222240
? html`<th hidden style="display:none"><slot @slotchange=${this._onSlotChange}></slot></th>`
223241
: html`<td hidden style="display:none"><slot @slotchange=${this._onSlotChange}></slot></td>`;
224242
}
225243

226244
render() {
227245
const cells = this._assignedCells ?? [];
228-
const isHeaderRow = cells.some(cell => cell instanceof SgdsDataTableHead);
229246
const totalCols = cells.length + (this.showCheckbox ? 1 : 0) + (this.expand || this.showExpandPlaceholder ? 1 : 0);
230247

231248
return html`
232-
<tr ?data-header-row=${isHeaderRow} class=${this.open ? "active" : ""}>
233-
${this._renderHiddenSlotCell(isHeaderRow)}
249+
<tr ?data-header-row=${this._isHeaderRow} class=${this.open ? "active" : ""}>
250+
${this._renderHiddenSlotCell()}
234251
${this.expand
235-
? this._renderExpandCell(isHeaderRow)
252+
? this._renderExpandCell()
236253
: this.showExpandPlaceholder
237-
? this._renderExpandPlaceholder(isHeaderRow)
254+
? this._renderExpandPlaceholder()
238255
: nothing}
239-
${this.showCheckbox ? this._renderCheckboxCell(isHeaderRow) : nothing}
256+
${this.showCheckbox ? this._renderCheckboxCell() : nothing}
240257
${(() => {
241258
let visualColumnIndex = 0;
242259
return cells.map(el => {

src/components/DataTable/sgds-data-table.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,34 @@ export class SgdsDataTable extends SgdsElement {
9191
this.setAttribute("role", "table");
9292
}
9393

94+
private _isRowChecked(row: SgdsDataTableRow) {
95+
return row.checked || row.checkbox?.checked === true;
96+
}
97+
9498
private _updateHeaderCheckbox() {
95-
const checkedCount = this.tableRows.filter(r => r.checkbox?.checked).length;
99+
const checkedCount = this.tableRows.filter(r => this._isRowChecked(r)).length;
100+
96101
const allChecked = checkedCount === this.tableRows.length && checkedCount > 0;
102+
97103
this.headerRows.forEach(r => {
104+
r.checked = allChecked;
98105
if (r.checkbox) {
99106
r.checkbox.checked = allChecked;
100107
r.checkbox.indeterminate = checkedCount > 0 && !allChecked;
101108
}
102109
});
103110
}
104111

112+
private async _syncHeaderCheckboxAfterRender() {
113+
if (!this.multiSelect || this.headerRows.length === 0) return;
114+
115+
const rows = [...this.headerRows, ...this.tableRows];
116+
await Promise.all(rows.map(row => row.updateComplete));
117+
this._updateHeaderCheckbox();
118+
}
119+
105120
private _emitRowSelect() {
106-
const selected = this.tableRows.filter(r => r.checkbox?.checked).map(r => r.rowData);
121+
const selected = this.tableRows.filter(r => this._isRowChecked(r)).map(r => r.rowData);
107122
this.emit("sgds-row-select", { detail: { selected } });
108123
}
109124

@@ -113,11 +128,14 @@ export class SgdsDataTable extends SgdsElement {
113128

114129
const handler: EventListener = (e: Event) => {
115130
const { checked } = (e as CustomEvent<{ checked: boolean }>).detail;
131+
116132
if (this.headerRows.includes(row)) {
117133
this.tableRows.forEach(r => {
134+
r.checked = checked;
118135
if (r.checkbox) r.checkbox.checked = checked;
119136
});
120137
} else {
138+
row.checked = checked;
121139
this._updateHeaderCheckbox();
122140
}
123141
this._emitRowSelect();
@@ -142,14 +160,22 @@ export class SgdsDataTable extends SgdsElement {
142160
if (this.multiSelect) this._attachRowListener(row);
143161
});
144162

163+
if (this.multiSelect) {
164+
this._syncHeaderCheckboxAfterRender();
165+
}
166+
145167
this._applyColumnAlignment();
146168
}
147169

148170
private _applyColumnAlignment() {
149-
const headerAlignments = this._headerCells.flatMap(header => {
171+
const headerAlignments = this._headerCells.reduce<Array<"left" | "right">>((acc, header) => {
150172
const span = Number(header.colspan) > 0 ? Number(header.colspan) : 1;
151-
return Array(span).fill(header.textAlign ?? "left");
152-
});
173+
const alignment = header.textAlign ?? "left";
174+
for (let i = 0; i < span; i++) {
175+
acc.push(alignment);
176+
}
177+
return acc;
178+
}, []);
153179

154180
this.headerRows.forEach(row => {
155181
row.columnAlignments = [...headerAlignments];
@@ -318,6 +344,10 @@ export class SgdsDataTable extends SgdsElement {
318344
if (changed.has("multiSelect")) {
319345
this._configureRows();
320346
}
347+
348+
if (this.multiSelect && (changed.has("tableRows") || changed.has("headerRows"))) {
349+
this._syncHeaderCheckboxAfterRender();
350+
}
321351
}
322352

323353
render() {

stories/component-templates/DataTable/additional.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ Set `multiSelect` to render a checkbox column for row selection.
2424
<Story of={DataTableStories.MultiSelect} />
2525
</Canvas>
2626

27+
## Multi-select pre-checked on load
28+
29+
Set `checked` on body rows to preselect them on initial render.
30+
The header checkbox state is computed on load and shows indeterminate or checked accordingly.
31+
32+
<Canvas of={DataTableStories.MultiSelectPrecheckedOnLoad}>
33+
<Story of={DataTableStories.MultiSelectPrecheckedOnLoad} />
34+
</Canvas>
35+
2736
## Expandable rows
2837

2938
Set `expand` on a row and provide content in the `content` slot.

stories/component-templates/DataTable/additional.stories.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,35 @@ const MultiSelectTemplate = () => html`
5555
</sgds-data-table>
5656
`;
5757

58+
const MultiSelectPrecheckedOnLoadTemplate = () => html`
59+
<sgds-data-table .currentPage=${1} .dataLength=${3} .itemsPerPage=${5} .multiSelect=${true}>
60+
<sgds-data-table-row>
61+
<sgds-data-table-head>#</sgds-data-table-head>
62+
<sgds-data-table-head>First name</sgds-data-table-head>
63+
<sgds-data-table-head>Last name</sgds-data-table-head>
64+
<sgds-data-table-head>Username</sgds-data-table-head>
65+
</sgds-data-table-row>
66+
<sgds-data-table-row>
67+
<sgds-data-table-cell>1</sgds-data-table-cell>
68+
<sgds-data-table-cell>John</sgds-data-table-cell>
69+
<sgds-data-table-cell>Doe</sgds-data-table-cell>
70+
<sgds-data-table-cell>@johndoe</sgds-data-table-cell>
71+
</sgds-data-table-row>
72+
<sgds-data-table-row checked>
73+
<sgds-data-table-cell>2</sgds-data-table-cell>
74+
<sgds-data-table-cell>Jane</sgds-data-table-cell>
75+
<sgds-data-table-cell>Doe</sgds-data-table-cell>
76+
<sgds-data-table-cell>@janedoe</sgds-data-table-cell>
77+
</sgds-data-table-row>
78+
<sgds-data-table-row>
79+
<sgds-data-table-cell>3</sgds-data-table-cell>
80+
<sgds-data-table-cell>Bob</sgds-data-table-cell>
81+
<sgds-data-table-cell>Smith</sgds-data-table-cell>
82+
<sgds-data-table-cell>@bobsmith</sgds-data-table-cell>
83+
</sgds-data-table-row>
84+
</sgds-data-table>
85+
`;
86+
5887
const ExpandableRowsTemplate = () => html`
5988
<sgds-data-table .currentPage=${1} .dataLength=${3} .itemsPerPage=${5}>
6089
<sgds-data-table-row>
@@ -283,6 +312,13 @@ export const MultiSelect = {
283312
parameters: {}
284313
};
285314

315+
export const MultiSelectPrecheckedOnLoad = {
316+
render: MultiSelectPrecheckedOnLoadTemplate.bind({}),
317+
name: "Multi-select pre-checked on load",
318+
args: {},
319+
parameters: {}
320+
};
321+
286322
export const ExpandableRows = {
287323
render: ExpandableRowsTemplate.bind({}),
288324
name: "Expandable rows",

0 commit comments

Comments
 (0)