UNPKG

@hashicorp/design-system-components

Version:
244 lines (241 loc) 12 kB
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(`@density 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(`@valign 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