@hashicorp/design-system-components
Version:
Helios Design System Components
244 lines (241 loc) • 12 kB
JavaScript
import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { hash, fn } from '@ember/helper';
import { eq } from 'ember-truth-helpers';
import { sortBy } from '@nullvoxpopuli/ember-composable-helpers';
import { HdsTableThSortOrderValues, HdsTableDensityValues, HdsTableVerticalAlignmentValues } from './types.js';
import HdsTableTr from './tr.js';
import HdsTableTh from './th.js';
import HdsTableThSort from './th-sort.js';
import HdsTableTd from './td.js';
import { precompileTemplate } from '@ember/template-compilation';
import { setComponentTemplate } from '@ember/component';
import { g, i } from 'decorator-transforms/runtime';
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
const DENSITIES = Object.values(HdsTableDensityValues);
const DEFAULT_DENSITY = HdsTableDensityValues.Medium;
const VALIGNMENTS = Object.values(HdsTableVerticalAlignmentValues);
const DEFAULT_VALIGN = HdsTableVerticalAlignmentValues.Top;
class HdsTable extends Component {
static {
g(this.prototype, "hdsIntl", [service]);
}
#hdsIntl = (i(this, "hdsIntl"), void 0);
static {
g(this.prototype, "sortBy", [tracked]);
}
#sortBy = (i(this, "sortBy"), void 0);
static {
g(this.prototype, "sortOrder", [tracked]);
}
#sortOrder = (i(this, "sortOrder"), void 0);
static {
g(this.prototype, "_selectAllCheckbox", [tracked], function () {
return undefined;
});
}
#_selectAllCheckbox = (i(this, "_selectAllCheckbox"), void 0);
_selectableRows = [];
static {
g(this.prototype, "_isSelectAllCheckboxSelected", [tracked], function () {
return undefined;
});
}
#_isSelectAllCheckboxSelected = (i(this, "_isSelectAllCheckboxSelected"), void 0);
constructor(owner, args) {
super(owner, args);
this.sortBy = this.args.sortBy ?? undefined;
this.sortOrder = this.args.sortOrder ?? HdsTableThSortOrderValues.Asc;
}
get getSortCriteria() {
// get the current column
const currentColumn = this.args?.columns?.find(column => column.key === this.sortBy);
if (
// check if there is a custom sorting function associated with the current `sortBy` column (we assume the column has `isSortable`)
currentColumn?.sortingFunction && typeof currentColumn.sortingFunction === 'function') {
return currentColumn.sortingFunction;
} else {
// otherwise fallback to the default format "sortBy:sortOrder"
return `${this.sortBy}:${this.sortOrder}`;
}
}
get identityKey() {
// we have to provide a way for the consumer to pass undefined because Ember tries to interpret undefined as missing an arg and therefore falls back to the default
if (this.args.identityKey === 'none') {
return undefined;
} else {
return this.args.identityKey ?? '@identity';
}
}
get sortedMessageText() {
if (this.args.sortedMessageText) {
return this.args.sortedMessageText;
} else if (this.sortBy && this.sortOrder) {
// we should allow the user to define a custom value here (e.g., for i18n) - tracked with HDS-965
const translatedSortOrder = {
[HdsTableThSortOrderValues.Asc]: this.hdsIntl.t('hds.components.common.ascending', {
default: 'ascending'
}),
[HdsTableThSortOrderValues.Desc]: this.hdsIntl.t('hds.components.common.descending', {
default: 'descending'
})
}[this.sortOrder];
const lowerCaseTranslatedSortOrder = translatedSortOrder.toLowerCase();
return this.hdsIntl.t('hds.components.table.sorted-message-text', {
sortBy: this.sortBy,
sortOrder: lowerCaseTranslatedSortOrder,
default: `Sorted by ${this.sortBy} ${lowerCaseTranslatedSortOrder}`
});
} else {
return '';
}
}
get isStriped() {
return this.args.isStriped ?? false;
}
get isFixedLayout() {
return this.args.isFixedLayout ?? false;
}
get density() {
const {
density = DEFAULT_DENSITY
} = this.args;
assert(` for "Hds::Table" must be one of the following: ${DENSITIES.join(', ')}; received: ${density}`, DENSITIES.includes(density));
return density;
}
get valign() {
const {
valign = DEFAULT_VALIGN
} = this.args;
assert(` for "Hds::Table" must be one of the following: ${VALIGNMENTS.join(', ')}; received: ${valign}`, VALIGNMENTS.includes(valign));
return valign;
}
get classNames() {
const classes = ['hds-table'];
// add a class based on the @isStriped argument
if (this.isStriped) {
classes.push('hds-table--striped');
}
// add a class based on the @isFixedLayout argument
if (this.isFixedLayout) {
classes.push('hds-table--layout-fixed');
}
// add a class based on the @density argument
if (this.density) {
classes.push(`hds-table--density-${this.density}`);
}
// add a class based on the @valign argument
if (this.valign) {
classes.push(`hds-table--valign-${this.valign}`);
}
return classes.join(' ');
}
setSortBy = column => {
if (this.sortBy === column) {
// check to see if the column is already sorted and invert the sort order if so
this.sortOrder = this.sortOrder === HdsTableThSortOrderValues.Asc ? HdsTableThSortOrderValues.Desc : HdsTableThSortOrderValues.Asc;
} else {
// otherwise, set the sort order to ascending
this.sortBy = column;
this.sortOrder = HdsTableThSortOrderValues.Asc;
}
const {
onSort
} = this.args;
if (typeof onSort === 'function') {
onSort(this.sortBy, this.sortOrder);
}
};
onSelectionChangeCallback = (checkbox, selectionKey) => {
const {
onSelectionChange
} = this.args;
if (typeof onSelectionChange === 'function') {
onSelectionChange({
selectionKey: selectionKey,
selectionCheckboxElement: checkbox,
selectedRowsKeys: this._selectableRows.reduce((acc, row) => {
if (row.checkbox.checked) {
acc.push(row.selectionKey);
}
return acc;
}, []),
selectableRowsStates: this._selectableRows.reduce((acc, row) => {
acc.push({
selectionKey: row.selectionKey,
isSelected: row.checkbox.checked
});
return acc;
}, [])
});
}
};
onSelectionAllChange = () => {
this._selectableRows.forEach(row => {
row.checkbox.checked = this._selectAllCheckbox?.checked ?? false;
});
this._isSelectAllCheckboxSelected = this._selectAllCheckbox?.checked ?? false;
this.onSelectionChangeCallback(this._selectAllCheckbox, 'all');
};
onSelectionRowChange = (checkbox, selectionKey) => {
this.setSelectAllState();
this.onSelectionChangeCallback(checkbox, selectionKey);
};
didInsertSelectAllCheckbox = checkbox => {
this._selectAllCheckbox = checkbox;
};
willDestroySelectAllCheckbox = () => {
this._selectAllCheckbox = undefined;
};
didInsertRowCheckbox = (checkbox, selectionKey) => {
if (selectionKey) {
this._selectableRows.push({
selectionKey,
checkbox
});
}
this.setSelectAllState();
};
willDestroyRowCheckbox = selectionKey => {
if (selectionKey === undefined) {
return;
}
const index = this._selectableRows.findIndex(row => row.selectionKey === selectionKey);
if (index === -1) {
return;
}
this._selectableRows.splice(index, 1);
this.setSelectAllState();
};
setSelectAllState = () => {
if (this._selectAllCheckbox) {
const selectableRowsCount = this._selectableRows.length;
const selectedRowsCount = this._selectableRows.filter(row => row.checkbox.checked).length;
this._selectAllCheckbox.checked = selectedRowsCount === selectableRowsCount;
this._selectAllCheckbox.indeterminate = selectedRowsCount > 0 && selectedRowsCount < selectableRowsCount;
this._isSelectAllCheckboxSelected = this._selectAllCheckbox.checked;
}
};
static {
setComponentTemplate(precompileTemplate("<table class={{this.classNames}} ...attributes>\n {{#if @columns}}\n <caption class=\"sr-only\" aria-live=\"polite\">{{@caption}}\n {{this.sortedMessageText}}</caption>\n {{else if @caption}}\n <caption class=\"sr-only\">{{@caption}}</caption>\n {{/if}}\n\n <thead class=\"hds-table__thead\">\n {{#if @columns}}\n <HdsTableTr @selectionScope=\"col\" @onClickSortBySelected={{if @selectableColumnKey (fn this.setSortBy @selectableColumnKey)}} @sortBySelectedOrder={{if (eq this.sortBy @selectableColumnKey) this.sortOrder}} @isSelectable={{@isSelectable}} @onSelectionChange={{this.onSelectionAllChange}} @didInsert={{this.didInsertSelectAllCheckbox}} @willDestroy={{this.willDestroySelectAllCheckbox}} @selectionAriaLabelSuffix=\"all rows\">\n {{#each @columns as |column|}}\n {{#if column.isSortable}}\n <HdsTableThSort @sortOrder={{if (eq column.key this.sortBy) this.sortOrder}} @onClickSort={{fn this.setSortBy column.key}} @align={{column.align}} @width={{column.width}} @tooltip={{column.tooltip}}>\n {{column.label}}\n </HdsTableThSort>\n {{else}}\n <HdsTableTh @align={{column.align}} @width={{column.width}} @tooltip={{column.tooltip}} @isVisuallyHidden={{column.isVisuallyHidden}}>{{column.label}}</HdsTableTh>\n {{/if}}\n {{/each}}\n </HdsTableTr>\n {{else}}\n {{yield (hash Tr=(component HdsTableTr selectionScope=\"col\" isSelectable=@isSelectable onSelectionChange=this.onSelectionAllChange didInsert=this.didInsertSelectAllCheckbox willDestroy=this.willDestroySelectAllCheckbox selectionAriaLabelSuffix=\"all rows\" onClickSortBySelected=(if @selectableColumnKey (fn this.setSortBy @selectableColumnKey)) sortBySelectedOrder=(if (eq this.sortBy @selectableColumnKey) this.sortOrder)) Th=HdsTableTh ThSort=HdsTableThSort sortBy=this.sortBy sortOrder=this.sortOrder setSortBy=this.setSortBy) to=\"head\"}}\n {{/if}}\n </thead>\n\n <tbody class=\"hds-table__tbody\">\n {{#if @columns}}\n {{!-- -----------------------------------------------------------------\n IMPORTANT: we loop on the model array and for each record\n we yield the Tr/Td/Th elements _and_ the record itself as data\n this means the consumer will *have to* use the data key to access it in their template\n ----------------------------------------------------------------- --}}\n {{!-- @glint-expect-error: [HDS-4380](https://hashicorp.atlassian.net/browse/HDS-4380) --}}\n {{#let (sortBy this.getSortCriteria @model) as |sortedModel|}}\n {{#each sortedModel key=this.identityKey as |record index|}}\n {{yield (hash Tr=(component HdsTableTr selectionScope=\"row\" isSelectable=@isSelectable onSelectionChange=this.onSelectionRowChange didInsert=this.didInsertRowCheckbox willDestroy=this.willDestroyRowCheckbox selectionAriaLabelSuffix=@selectionAriaLabelSuffix) Th=(component HdsTableTh scope=\"row\") Td=(component HdsTableTd align=@align) data=record rowIndex=index) to=\"body\"}}\n {{/each}}\n {{/let}}\n {{else}}\n {{yield (hash Tr=(component HdsTableTr selectionScope=\"row\" isSelectable=@isSelectable onSelectionChange=this.onSelectionRowChange didInsert=this.didInsertRowCheckbox willDestroy=this.willDestroyRowCheckbox selectionAriaLabelSuffix=@selectionAriaLabelSuffix) Th=(component HdsTableTh scope=\"row\") Td=(component HdsTableTd align=@align) sortBy=this.sortBy sortOrder=this.sortOrder) to=\"body\"}}\n {{/if}}\n </tbody>\n</table>", {
strictMode: true,
scope: () => ({
HdsTableTr,
fn,
eq,
HdsTableThSort,
HdsTableTh,
hash,
sortBy,
HdsTableTd
})
}), this);
}
}
export { DEFAULT_DENSITY, DEFAULT_VALIGN, DENSITIES, VALIGNMENTS, HdsTable as default };
//# sourceMappingURL=index.js.map