@hashicorp/design-system-components
Version:
Helios Design System Components
1,321 lines (1,315 loc) • 70.4 kB
JavaScript
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { assert } from '@ember/debug';
import { tracked, cached } from '@glimmer/tracking';
import { guidFor } from '@ember/object/internals';
import { service } from '@ember/service';
import { modifier } from 'ember-modifier';
import { TrackedSet } from 'tracked-built-ins';
import { concat, hash, fn } from '@ember/helper';
import { not, and, eq, notEq } from 'ember-truth-helpers';
import style from 'ember-style-modifier';
import { on } from '@ember/modifier';
import { sortBy } from '@nullvoxpopuli/ember-composable-helpers';
import { HdsAdvancedTableColumnReorderSideValues, HdsAdvancedTableThSortOrderLabelValues, HdsAdvancedTableThSortOrderValues, HdsAdvancedTableHorizontalAlignmentValues, HdsAdvancedTableDensityValues, HdsAdvancedTableVerticalAlignmentValues } from './components/hds/advanced-table/types.js';
import HdsFilterBar from './components/hds/filter-bar/index.js';
import HdsApplicationState from './components/hds/application-state/index.js';
import Composite from './components/hds/composite/index.js';
import HdsAdvancedTableColumnManager from './components/hds/advanced-table/column-manager/index.js';
import { scheduleOnce } from '@ember/runloop';
import { focusable } from 'tabbable';
import hdsScrollIntoViewOnFocus from './modifiers/hds-scroll-into-view-on-focus.js';
import HdsAdvancedTableCellModifier from './modifiers/hds-advanced-table-cell.js';
import focusTrap from 'ember-focus-trap/modifiers/focus-trap';
import HdsLayoutFlex from './components/hds/layout/flex/index.js';
import HdsAdvancedTableThButtonExpand from './components/hds/advanced-table/th-button-expand.js';
import HdsAdvancedTableThButtonSort from './components/hds/advanced-table/th-button-sort.js';
import HdsAdvancedTableThButtonTooltip from './components/hds/advanced-table/th-button-tooltip.js';
import HdsAdvancedTableThContextMenu from './components/hds/advanced-table/th-context-menu.js';
import HdsAdvancedTableThReorderHandle from './components/hds/advanced-table/th-reorder-handle.js';
import { parsePixel, requestAnimationFrameWaiter } from './components/hds/advanced-table/utils.js';
import HdsAdvancedTableBody from './components/hds/advanced-table/body.js';
import HdsAdvancedTableTd from './components/hds/advanced-table/td.js';
import HdsTHelper from './helpers/hds-t.js';
import { precompileTemplate } from '@ember/template-compilation';
import { setComponentTemplate } from '@ember/component';
import { g, i, n } from 'decorator-transforms/runtime';
import { DEFAULT_MIN_WIDTH, DEFAULT_MAX_WIDTH } from './components/hds/advanced-table/column-manager/width.js';
import { onFocusTrapDeactivate } from './modifiers/hds-advanced-table-cell/dom-management.js';
import HdsFormCheckboxBase from './components/hds/form/checkbox/base.js';
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
const KEYBOARD_RESIZE_STEP = 10;
function calculateEffectiveDelta(deltaX, col, startColW, nextCol, startNextColW) {
const colMin = parsePixel(col.minWidth ?? DEFAULT_MIN_WIDTH) ?? 0;
const colMax = parsePixel(col.maxWidth ?? DEFAULT_MAX_WIDTH) ?? Infinity;
const nextMin = parsePixel(nextCol.minWidth ?? DEFAULT_MIN_WIDTH) ?? 0;
const nextMax = parsePixel(nextCol.maxWidth ?? DEFAULT_MAX_WIDTH) ?? Infinity;
let effectiveDelta = 0;
// expanding col, shrinking nextCol
if (deltaX > 0) {
const maxCanExpandCol = colMax - startColW;
const maxCanShrinkNext = startNextColW - nextMin;
effectiveDelta = Math.min(deltaX, maxCanExpandCol, maxCanShrinkNext);
effectiveDelta = Math.max(0, effectiveDelta);
} else if (deltaX < 0) {
const absDeltaX = -deltaX;
const maxCanShrinkCol = startColW - colMin;
let maxCanExpandNext;
if (startNextColW > nextMax) {
maxCanExpandNext = Infinity;
} else {
maxCanExpandNext = nextMax - startNextColW;
}
effectiveDelta = -Math.min(absDeltaX, maxCanShrinkCol, maxCanExpandNext);
effectiveDelta = Math.min(0, effectiveDelta);
}
return effectiveDelta;
}
class HdsAdvancedTableThResizeHandle extends Component {
static {
g(this.prototype, "resizing", [tracked], function () {
return null;
});
}
#resizing = (i(this, "resizing"), void 0);
static {
g(this.prototype, "_transientDelta", [tracked], function () {
return 0;
});
}
#_transientDelta = (i(this, "_transientDelta"), void 0); // track the width change as it is changing, applied when resizing stops
static {
g(this.prototype, "_isUpdateQueued", [tracked], function () {
return false;
});
}
#_isUpdateQueued = (i(this, "_isUpdateQueued"), void 0);
static {
g(this.prototype, "_lastPointerEvent", [tracked], function () {
return null;
});
}
#_lastPointerEvent = (i(this, "_lastPointerEvent"), void 0);
_handleElement;
_boundResize;
_boundStopResize;
_registerHandleElement = modifier(element => {
this._handleElement = element;
});
constructor(owner, args) {
super(owner, args);
this._boundResize = this._resize.bind(this);
this._boundStopResize = this._stopResize.bind(this);
}
get currentWidthInPixels() {
const {
column,
onGetAppliedWidth
} = this.args;
if (column === undefined || onGetAppliedWidth === undefined) {
return 0;
}
const appliedWidth = onGetAppliedWidth(column.key);
return parsePixel(appliedWidth) ?? 0;
}
get minWidthInPixels() {
return parsePixel(this.args.column?.minWidth ?? DEFAULT_MIN_WIDTH) ?? 0;
}
get maxWidthInPixels() {
return parsePixel(this.args.column?.maxWidth ?? DEFAULT_MAX_WIDTH) ?? Infinity;
}
get height() {
const {
tableHeight
} = this.args;
if (tableHeight === undefined) {
return;
}
return `${tableHeight - BORDER_WIDTH * 2}px`;
}
get classNames() {
const classes = ['hds-advanced-table__th-resize-handle'];
if (this.resizing !== null) {
classes.push('hds-advanced-table__th-resize-handle--resizing');
}
return classes.join(' ');
}
_applyTransientWidths() {
const {
column,
siblingColumnKeys,
onApplyTransientWidth
} = this.args;
if (column === undefined || onApplyTransientWidth === undefined) {
return;
}
const {
next: nextColumnKey
} = siblingColumnKeys ?? {};
onApplyTransientWidth(column.key);
if (nextColumnKey !== undefined) {
onApplyTransientWidth(nextColumnKey);
}
}
onColumnResize(key, width) {
const {
onColumnResize
} = this.args;
if (typeof onColumnResize === 'function' && key !== undefined) {
onColumnResize(key, width);
}
}
static {
n(this.prototype, "onColumnResize", [action]);
}
handleKeydown(event) {
const validKeys = ['ArrowLeft', 'ArrowRight'];
if (!validKeys.includes(event.key)) {
return;
}
event.preventDefault();
event.stopPropagation();
const {
column,
siblingColumnKeys,
onApplyTransientWidth,
onGetAppliedWidth,
onSetTransientColumnWidths,
onResetTransientColumnWidths,
onUpdateResizeDebt
} = this.args;
const {
next: nextColumnKey
} = siblingColumnKeys ?? {};
if (column === undefined || nextColumnKey === undefined || onApplyTransientWidth === undefined || onGetAppliedWidth === undefined || onSetTransientColumnWidths === undefined || onUpdateResizeDebt === undefined || onResetTransientColumnWidths === undefined) {
return;
}
onSetTransientColumnWidths({
roundValues: true
});
const startColumnAppliedWidth = onGetAppliedWidth(column.key);
const startNextColumnAppliedWidth = onGetAppliedWidth(nextColumnKey);
const startColumnPxWidth = Math.round(parsePixel(startColumnAppliedWidth) ?? 0);
const startNextColumnPxWidth = Math.round(parsePixel(startNextColumnAppliedWidth) ?? 0);
const deltaX = event.key === 'ArrowRight' ? KEYBOARD_RESIZE_STEP : -KEYBOARD_RESIZE_STEP;
this._applyResizeDelta(deltaX, startColumnPxWidth, column, nextColumnKey, startNextColumnPxWidth);
// ensure the resize handle remains visible during keyboard navigation.
this._handleElement.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
// use a microtask to commit the final state after the render pass.
queueMicrotask(() => {
if (this._transientDelta !== 0) {
onUpdateResizeDebt(this._transientDelta);
}
// reset transient values
onApplyTransientWidth(column.key);
onApplyTransientWidth(nextColumnKey);
onResetTransientColumnWidths();
this._transientDelta = 0;
this.onColumnResize(column.key, column.width);
});
}
static {
n(this.prototype, "handleKeydown", [action]);
}
startResize(event) {
if (event.button !== 0) {
return;
}
event.preventDefault();
event.stopPropagation();
const {
column,
siblingColumnKeys,
onGetAppliedWidth,
onSetTransientColumnWidths
} = this.args;
if (column === undefined || onGetAppliedWidth === undefined || onSetTransientColumnWidths === undefined) {
return;
}
const {
next: nextColumnKey
} = siblingColumnKeys ?? {};
onSetTransientColumnWidths({});
const startColumnAppliedWidth = onGetAppliedWidth(column.key);
const startNextColumnAppliedWidth = nextColumnKey !== undefined ? onGetAppliedWidth(nextColumnKey) : undefined;
const startColumnPxWidth = Math.round(parsePixel(startColumnAppliedWidth) ?? 0);
const startNextColumnPxWidth = Math.round(parsePixel(startNextColumnAppliedWidth) ?? 0);
this.resizing = {
startX: event.clientX,
startColumnPxWidth,
startNextColumnPxWidth
};
window.addEventListener('pointermove', this._boundResize);
window.addEventListener('pointerup', this._boundStopResize);
}
static {
n(this.prototype, "startResize", [action]);
}
_setColumnWidth(column, width) {
const {
onSetTransientColumnWidth
} = this.args;
if (column === undefined || onSetTransientColumnWidth === undefined) {
return;
}
onSetTransientColumnWidth(column.key, `${width}px`);
}
_applyResizeDelta(deltaX, startColumnPxWidth, column, nextColumnKey, startNextColumnPxWidth) {
const {
onGetAppliedWidth,
onGetColumnByKey,
onSetTransientColumnWidth
} = this.args;
if (column === undefined || onGetAppliedWidth === undefined || onGetColumnByKey === undefined) {
return;
}
const canResizeNeighbor = nextColumnKey !== undefined && startNextColumnPxWidth !== undefined;
if (canResizeNeighbor) {
const nextColumn = onGetColumnByKey(nextColumnKey);
if (nextColumn === undefined) {
return;
}
const effectiveDelta = calculateEffectiveDelta(deltaX, column, startColumnPxWidth, nextColumn, startNextColumnPxWidth);
// set the width for the current column
this._setColumnWidth(column, Math.round(startColumnPxWidth + effectiveDelta));
// the actual new column width may differ from the intended width due to min/max constraints.
const columnAppliedWidth = onGetAppliedWidth(column.key);
const actualNewColumnWidth = parsePixel(columnAppliedWidth) ?? startColumnPxWidth;
const actualAppliedDelta = actualNewColumnWidth - startColumnPxWidth;
// set the width for the next sibling column
this._setColumnWidth(nextColumn, Math.round(startNextColumnPxWidth - actualAppliedDelta));
this._transientDelta = actualAppliedDelta;
} else if (onSetTransientColumnWidth !== undefined) {
onSetTransientColumnWidth(column.key, `${Math.round(startColumnPxWidth + deltaX)}px`);
}
}
_resize(event) {
this._lastPointerEvent = event;
if (this._isUpdateQueued) {
return;
}
this._isUpdateQueued = true;
requestAnimationFrameWaiter(() => {
if (this.resizing === null || this._lastPointerEvent === null) {
this._isUpdateQueued = false;
return;
}
const event = this._lastPointerEvent;
event.preventDefault();
const {
column,
siblingColumnKeys
} = this.args;
const {
next: nextColumnKey
} = siblingColumnKeys ?? {};
const {
startX,
startColumnPxWidth,
startNextColumnPxWidth
} = this.resizing;
const deltaX = event.clientX - startX;
this._applyResizeDelta(deltaX, startColumnPxWidth, column, nextColumnKey, startNextColumnPxWidth // Width of next col at the start of the drag
);
this._isUpdateQueued = false;
});
}
_stopResize() {
const {
column,
onGetAppliedWidth,
onResetTransientColumnWidths,
onUpdateResizeDebt
} = this.args;
if (column === undefined || onGetAppliedWidth === undefined || onResetTransientColumnWidths === undefined || onUpdateResizeDebt === undefined) {
return;
}
window.removeEventListener('pointermove', this._boundResize);
window.removeEventListener('pointerup', this._boundStopResize);
if (this._transientDelta !== 0) {
onUpdateResizeDebt(this._transientDelta);
}
this._applyTransientWidths();
// reset the transient width
onResetTransientColumnWidths();
// reset the resizing state
this.resizing = null;
this._transientDelta = 0;
const appliedWidth = onGetAppliedWidth(column.key);
this.onColumnResize(column.key, appliedWidth);
}
static {
setComponentTemplate(precompileTemplate("{{!-- template-lint-disable no-pointer-down-event-binding --}}\n<div class={{this.classNames}} draggable=\"false\" role=\"slider\" aria-orientation=\"horizontal\" aria-valuenow={{this.currentWidthInPixels}} aria-valuemin={{this.minWidthInPixels}} aria-valuemax={{this.maxWidthInPixels}} tabindex=\"0\" aria-label={{hdsT \"hds.components.advanced-table.th-resize-handle.aria-label\" columnLabel=@column.label default=(concat \"Resize \" @column.label \" column\")}} {{this._registerHandleElement}} {{on \"pointerdown\" this.startResize}} {{on \"keydown\" this.handleKeydown}} {{style height=this.height}} ...attributes />", {
strictMode: true,
scope: () => ({
hdsT: HdsTHelper,
concat,
on,
style
})
}), this);
}
}
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
class HdsAdvancedTableThReorderDropTarget extends Component {
static {
g(this.prototype, "_dragSide", [tracked], function () {
return null;
});
}
#_dragSide = (i(this, "_dragSide"), void 0);
static {
g(this.prototype, "_isUpdateQueued", [tracked], function () {
return false;
});
}
#_isUpdateQueued = (i(this, "_isUpdateQueued"), void 0);
_element;
_registerElement = modifier(element => {
this._element = element;
});
// determines whether the drag event is occurring on the left or right side of the element
_getDragSide(event) {
const rect = this._element.getBoundingClientRect();
const mouseX = event.clientX;
const elementMiddleX = rect.left + rect.width / 2;
return mouseX < elementMiddleX ? HdsAdvancedTableColumnReorderSideValues.Left : HdsAdvancedTableColumnReorderSideValues.Right;
}
get isBeingDragged() {
const {
column,
draggedColumnKey
} = this.args;
return column !== undefined && column.key === draggedColumnKey;
}
get isDraggingOver() {
const {
column,
reorderHoveredColumnKey
} = this.args;
return column !== undefined && column.key === reorderHoveredColumnKey;
}
get classNames() {
const {
isFirstColumn,
isLastColumn
} = this.args;
const classes = ['hds-advanced-table__th-reorder-drop-target'];
if (isFirstColumn && !this.args.hasSelectableRows) {
classes.push('hds-advanced-table__th-reorder-drop-target--is-first');
} else if (isLastColumn) {
classes.push('hds-advanced-table__th-reorder-drop-target--is-last');
}
if (this.isBeingDragged) {
classes.push('hds-advanced-table__th-reorder-drop-target--is-being-dragged');
} else if (this.isDraggingOver && this._dragSide !== null) {
classes.push(...['hds-advanced-table__th-reorder-drop-target--is-dragging-over', `hds-advanced-table__th-reorder-drop-target--is-dragging-over--${this._dragSide}`]);
}
return classes.join(' ');
}
get height() {
const {
tableHeight
} = this.args;
if (tableHeight === undefined) {
return;
}
return `${tableHeight - BORDER_WIDTH * 2}px`;
}
handleDragOver(event) {
event.preventDefault();
if (this._isUpdateQueued) {
return;
}
this._isUpdateQueued = true;
requestAnimationFrameWaiter(() => {
const {
column,
draggedColumnKey,
draggedColumnSiblingColumnKeys,
onSetReorderHoveredColumnKey
} = this.args;
if (column === undefined || onSetReorderHoveredColumnKey === undefined) {
return;
}
if (draggedColumnKey !== null) {
if (this.isBeingDragged) {
onSetReorderHoveredColumnKey(null);
} else {
onSetReorderHoveredColumnKey(column.key);
const {
next,
previous
} = draggedColumnSiblingColumnKeys ?? {};
const dragSide = this._getDragSide(event);
if (column.key === previous && dragSide === HdsAdvancedTableColumnReorderSideValues.Left || column.key === next && dragSide === HdsAdvancedTableColumnReorderSideValues.Right || column.key !== previous && column.key !== next) {
this._dragSide = dragSide;
}
}
}
this._isUpdateQueued = false;
});
}
static {
n(this.prototype, "handleDragOver", [action]);
}
handleDrop(event) {
event.preventDefault();
const {
column,
onReorderDrop,
onSetReorderHoveredColumnKey
} = this.args;
const {
_dragSide
} = this;
if (column === undefined || _dragSide === null || typeof onReorderDrop !== 'function' || typeof onSetReorderHoveredColumnKey !== 'function') {
return;
}
onReorderDrop(column.key, _dragSide);
this._dragSide = null;
onSetReorderHoveredColumnKey(null);
}
static {
n(this.prototype, "handleDrop", [action]);
}
static {
setComponentTemplate(precompileTemplate("<div class={{this.classNames}} aria-hidden=\"true\" {{style height=this.height}} {{this._registerElement}} {{on \"dragover\" this.handleDragOver}} {{on \"drop\" this.handleDrop}} ...attributes />", {
strictMode: true,
scope: () => ({
style,
on
})
}), this);
}
}
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
const ALIGNMENTS = Object.values(HdsAdvancedTableHorizontalAlignmentValues);
const DEFAULT_ALIGN = HdsAdvancedTableHorizontalAlignmentValues.Left;
class HdsAdvancedTableTh extends Component {
_labelId;
static {
g(this.prototype, "_element", [tracked]);
}
#_element = (i(this, "_element"), void 0);
static {
g(this.prototype, "_shouldTrapFocus", [tracked], function () {
return false;
});
}
#_shouldTrapFocus = (i(this, "_shouldTrapFocus"), void 0);
static {
g(this.prototype, "_reorderHandleElement", [tracked]);
}
#_reorderHandleElement = (i(this, "_reorderHandleElement"), void 0);
static {
g(this.prototype, "_resizeHandleElement", [tracked]);
}
#_resizeHandleElement = (i(this, "_resizeHandleElement"), void 0);
constructor(owner, args) {
super(owner, args);
const {
newLabel,
rowspan,
colspan,
isStickyColumn
} = args;
if (isStickyColumn) {
assert('Cannot have custom rowspan or colspan if there are nested rows.', rowspan === undefined || colspan === undefined);
}
this._labelId = newLabel ?? guidFor(this);
}
get isSortable() {
return this.args.column?.isSortable ?? false;
}
get isFirstColumn() {
const {
column,
firstColumnKey
} = this.args;
return firstColumnKey !== undefined && column !== undefined && firstColumnKey === column.key;
}
get isFirstNonStickyColumn() {
const {
column,
firstNonStickyColumnKey
} = this.args;
return firstNonStickyColumnKey !== undefined && column !== undefined && firstNonStickyColumnKey === column.key;
}
get isLastColumn() {
const {
column,
lastColumnKey
} = this.args;
return lastColumnKey !== undefined && column !== undefined && lastColumnKey === column.key;
}
get tableHasColumnBeingDragged() {
const {
draggedColumnKey
} = this.args;
return draggedColumnKey != null;
}
get isColumnBeingDragged() {
const {
column,
draggedColumnKey
} = this.args;
return draggedColumnKey != null && column !== undefined && draggedColumnKey === column.key;
}
get scope() {
return this.args.scope ?? 'col';
}
get role() {
return this.scope === 'col' ? 'columnheader' : 'rowheader';
}
get ariaSort() {
switch (this.args.sortOrder) {
case HdsAdvancedTableThSortOrderValues.Asc:
return HdsAdvancedTableThSortOrderLabelValues.Asc;
case HdsAdvancedTableThSortOrderValues.Desc:
return HdsAdvancedTableThSortOrderLabelValues.Desc;
default:
// none is the default per the spec.
return HdsAdvancedTableThSortOrderLabelValues.None;
}
}
get align() {
const {
align = DEFAULT_ALIGN
} = this.args;
assert(`@align for "Hds::Table::Th" must be one of the following: ${ALIGNMENTS.join(', ')}; received: ${align}`, ALIGNMENTS.includes(align));
return align;
}
// rowspan and colspan have to return 'auto' if not defined because otherwise the style modifier sets grid-area: undefined on the cell, which breaks the grid styles
get rowspan() {
return this.args.rowspan !== undefined ? `span ${this.args.rowspan}` : 'auto';
}
get colspan() {
return this.args.colspan !== undefined ? `span ${this.args.colspan}` : 'auto';
}
get paddingLeft() {
return this.args.depth !== undefined ? `calc(${this.args.depth} * 32px + 16px)` : undefined;
}
get classNames() {
const {
isStickyColumn,
isStickyColumnPinned
} = this.args;
const classes = ['hds-advanced-table__th'];
if (this.isSortable) {
classes.push('hds-advanced-table__th--sort');
}
if (this.align) {
classes.push(`hds-advanced-table__th--align-${this.align}`);
}
if (isStickyColumn) {
classes.push('hds-advanced-table__th--is-sticky-column');
}
if (isStickyColumn && isStickyColumnPinned) {
classes.push('hds-advanced-table__th--is-sticky-column-pinned');
}
if (this.isColumnBeingDragged) {
classes.push('hds-advanced-table__th--is-being-dragged');
}
return classes.join(' ');
}
get showDropTarget() {
const {
isStickyColumn
} = this.args;
return this.tableHasColumnBeingDragged === true && !isStickyColumn;
}
get showResizeHandle() {
const {
hasResizableColumns
} = this.args;
return hasResizableColumns === true && !this.isLastColumn && !this.tableHasColumnBeingDragged;
}
onFocusTrapDeactivate() {
this._shouldTrapFocus = false;
onFocusTrapDeactivate(this._element);
}
static {
n(this.prototype, "onFocusTrapDeactivate", [action]);
}
enableFocusTrap() {
this._shouldTrapFocus = true;
}
static {
n(this.prototype, "enableFocusTrap", [action]);
}
getInitialFocus() {
const cellFocusableElements = focusable(this._element);
return cellFocusableElements[0];
}
static {
n(this.prototype, "getInitialFocus", [action]);
}
focusReorderHandle() {
if (this._element === undefined) {
return;
}
// focus the th element first (parent) to ensure the handle is visible
this._element.focus({
preventScroll: true
});
if (this._reorderHandleElement === undefined) {
return;
}
// then focus the reorder handle element
this._reorderHandleElement.focus();
}
static {
n(this.prototype, "focusReorderHandle", [action]);
}
applyTransientWidth(columnKey) {
this.args.onApplyTransientWidth?.(columnKey);
}
static {
n(this.prototype, "applyTransientWidth", [action]);
}
resetTransientColumnWidths() {
this.args.onResetTransientColumnWidths?.();
}
static {
n(this.prototype, "resetTransientColumnWidths", [action]);
}
getAppliedWidth(columnKey) {
return this.args.onGetAppliedWidth?.(columnKey);
}
static {
n(this.prototype, "getAppliedWidth", [action]);
}
getColumnByKey(columnKey) {
return this.args.onGetColumnByKey?.(columnKey);
}
static {
n(this.prototype, "getColumnByKey", [action]);
}
setDraggedColumnKey(columnKey) {
this.args.onSetDraggedColumnKey?.(columnKey);
}
static {
n(this.prototype, "setDraggedColumnKey", [action]);
}
setTransientColumnWidth(columnKey, width) {
const {
column,
onSetTransientColumnWidth
} = this.args;
if (column !== undefined && onSetTransientColumnWidth !== undefined) {
onSetTransientColumnWidth(columnKey, width);
}
}
static {
n(this.prototype, "setTransientColumnWidth", [action]);
}
setTransientColumnWidths(options) {
this.args.onSetTransientColumnWidths?.(options);
}
static {
n(this.prototype, "setTransientColumnWidths", [action]);
}
stepColumn(step) {
const {
column,
onStepColumn
} = this.args;
if (column !== undefined && onStepColumn !== undefined) {
onStepColumn(column.key, step);
}
}
static {
n(this.prototype, "stepColumn", [action]);
}
updateResizeDebt(delta) {
const {
column,
onUpdateResizeDebt
} = this.args;
if (column !== undefined && onUpdateResizeDebt !== undefined) {
onUpdateResizeDebt(column.key, delta);
}
}
static {
n(this.prototype, "updateResizeDebt", [action]);
}
setElement(element) {
// eslint-disable-next-line ember/no-runloop, ember/no-incorrect-calls-with-inline-anonymous-functions
scheduleOnce('afterRender', () => {
this._element = element;
});
}
static {
n(this.prototype, "setElement", [action]);
}
_registerReorderHandleElement = modifier(element => {
this._reorderHandleElement = element;
});
_registerResizeHandleElement = modifier(element => {
this._resizeHandleElement = element;
});
_manageExpandButton = modifier(button => {
const {
didInsertExpandButton,
willDestroyExpandButton
} = this.args;
if (typeof didInsertExpandButton === 'function') {
didInsertExpandButton(button);
}
return () => {
if (typeof willDestroyExpandButton === 'function') {
willDestroyExpandButton(button);
}
};
});
static {
setComponentTemplate(precompileTemplate("<div class={{this.classNames}} role={{this.role}} aria-sort={{this.ariaSort}} aria-rowspan={{@rowspan}} aria-colspan={{@colspan}} aria-describedby={{@parentId}} {{style grid-row=this.rowspan grid-column=this.colspan padding-left=this.paddingLeft}} {{hdsAdvancedTableCell handleEnableFocusTrap=this.enableFocusTrap shouldTrapFocus=this._shouldTrapFocus setCellElement=this.setElement}} {{focusTrap isActive=this._shouldTrapFocus focusTrapOptions=(hash onDeactivate=this.onFocusTrapDeactivate initialFocus=this.getInitialFocus clickOutsideDeactivates=true)}} {{@compositeItem disabled=@isCompositeItemDisabled}} {{hdsScrollIntoViewOnFocus options=(hash block=\"center\" inline=\"center\")}} ...attributes>\n <HdsLayoutFlex @justify=\"space-between\" @align=\"center\" @gap=\"8\">\n {{#if @column.isVisuallyHidden}}\n <span class=\"sr-only\">{{yield}}</span>\n {{else}}\n {{#if (and @isExpandable (not this.isSortable))}}\n <HdsAdvancedTableThButtonExpand @labelId={{this._labelId}} @onToggle={{@onClickToggle}} @isExpanded={{@isExpanded}} @isExpandAll={{@hasExpandAllButton}} {{this._manageExpandButton}} />\n {{/if}}\n <div class=\"hds-advanced-table__th-content\">\n <span id={{this._labelId}} class=\"hds-advanced-table__th-content-text hds-typography-body-200 hds-font-weight-semibold\">\n {{yield}}\n </span>\n {{#if @tooltip}}\n <HdsAdvancedTableThButtonTooltip @tooltip={{@tooltip}} @labelId={{this._labelId}} />\n {{/if}}\n </div>\n {{#if this.isSortable}}\n <HdsAdvancedTableThButtonSort @sortOrder={{@sortOrder}} @onClick={{@onClickSort}} @labelId={{this._labelId}} />\n {{/if}}\n {{#if @column}}\n <HdsAdvancedTableThContextMenu @column={{@column}} @hasReorderableColumns={{@hasReorderableColumns}} @hasResizableColumns={{@hasResizableColumns}} @isFirstColumn={{this.isFirstColumn}} @isFirstNonStickyColumn={{this.isFirstNonStickyColumn}} @isLastColumn={{this.isLastColumn}} @isStickyColumn={{@isStickyColumn}} @reorderHandleElement={{this._reorderHandleElement}} @resizeHandleElement={{this._resizeHandleElement}} @onColumnResize={{@onColumnResize}} @onFocusReorderHandle={{this.focusReorderHandle}} @onMoveColumnToTerminalPosition={{@onMoveColumnToTerminalPosition}} @onPinFirstColumn={{@onPinFirstColumn}} @onRestoreColumnWidth={{@onRestoreColumnWidth}} />\n {{#if (and @hasReorderableColumns (not @isStickyColumn))}}\n <HdsAdvancedTableThReorderHandle @column={{@column}} @tableHeight={{@tableHeight}} @thElement={{this._element}} @onFocusReorderHandle={{this.focusReorderHandle}} @onSetDraggedColumnKey={{this.setDraggedColumnKey}} @onStepColumn={{this.stepColumn}} {{this._registerReorderHandleElement}} />\n {{/if}}\n {{#if this.showResizeHandle}}\n <HdsAdvancedTableThResizeHandle @column={{@column}} @siblingColumnKeys={{@siblingColumnKeys}} @tableHeight={{@tableHeight}} @onApplyTransientWidth={{this.applyTransientWidth}} @onColumnResize={{@onColumnResize}} @onGetAppliedWidth={{this.getAppliedWidth}} @onGetColumnByKey={{this.getColumnByKey}} @onSetTransientColumnWidth={{this.setTransientColumnWidth}} @onSetTransientColumnWidths={{this.setTransientColumnWidths}} @onResetTransientColumnWidths={{this.resetTransientColumnWidths}} @onUpdateResizeDebt={{this.updateResizeDebt}} {{this._registerResizeHandleElement}} />\n {{/if}}\n {{/if}}\n {{/if}}\n </HdsLayoutFlex>\n {{#if this.showDropTarget}}\n <HdsAdvancedTableThReorderDropTarget @column={{@column}} @draggedColumnKey={{@draggedColumnKey}} @hasSelectableRows={{@hasSelectableRows}} @isFirstColumn={{this.isFirstColumn}} @isLastColumn={{this.isLastColumn}} @reorderHoveredColumnKey={{@reorderHoveredColumnKey}} @draggedColumnSiblingColumnKeys={{@draggedColumnSiblingColumnKeys}} @tableHeight={{@tableHeight}} @onReorderDrop={{@onReorderDrop}} @onSetReorderHoveredColumnKey={{@onSetReorderHoveredColumnKey}} />\n {{/if}}\n</div>", {
strictMode: true,
scope: () => ({
style,
hdsAdvancedTableCell: HdsAdvancedTableCellModifier,
focusTrap,
hash,
hdsScrollIntoViewOnFocus,
HdsLayoutFlex,
and,
not,
HdsAdvancedTableThButtonExpand,
HdsAdvancedTableThButtonTooltip,
HdsAdvancedTableThButtonSort,
HdsAdvancedTableThContextMenu,
HdsAdvancedTableThReorderHandle,
HdsAdvancedTableThResizeHandle,
HdsAdvancedTableThReorderDropTarget
})
}), this);
}
}
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
class HdsAdvancedTableThSelectable extends Component {
static {
g(this.prototype, "hdsIntl", [service]);
}
#hdsIntl = (i(this, "hdsIntl"), void 0);
static {
g(this.prototype, "_isSelected", [tracked], function () {
return this.args.isSelected ?? false;
});
}
#_isSelected = (i(this, "_isSelected"), void 0);
_guid = guidFor(this);
_checkboxId = `checkbox-${this._guid}`;
_labelId = `label-${this._guid}`;
get isSortable() {
return this.args.onClickSortBySelected !== undefined;
}
get ariaLabel() {
const {
selectionAriaLabelSuffix = 'row'
} = this.args;
const defaultString = `Select ${selectionAriaLabelSuffix}`;
return this.hdsIntl.t('hds.components.advanced-table.th-selectable.aria-label', {
default: defaultString,
suffix: selectionAriaLabelSuffix
});
}
get ariaSort() {
switch (this.args.sortBySelectedOrder) {
case HdsAdvancedTableThSortOrderValues.Asc:
return HdsAdvancedTableThSortOrderLabelValues.Asc;
case HdsAdvancedTableThSortOrderValues.Desc:
return HdsAdvancedTableThSortOrderLabelValues.Desc;
default:
// none is the default per the spec.
return HdsAdvancedTableThSortOrderLabelValues.None;
}
}
_manageCheckbox = modifier(checkbox => {
const {
didInsert,
willDestroy
} = this.args;
if (typeof didInsert === 'function') {
didInsert(checkbox, this.args.selectionKey);
}
return () => {
if (typeof willDestroy === 'function') {
willDestroy(this.args.selectionKey);
}
};
});
onSelectionChange(event) {
// Assert event.target as HdsFormCheckboxBaseSignature['Element'] to access the 'checked' property
const target = event.target;
this._isSelected = target.checked;
const {
onSelectionChange
} = this.args;
if (typeof onSelectionChange === 'function') {
onSelectionChange(target, this.args.selectionKey);
}
}
static {
n(this.prototype, "onSelectionChange", [action]);
}
static {
setComponentTemplate(precompileTemplate("<HdsAdvancedTableTh class=\"hds-advanced-table__th--is-selectable\" aria-sort={{if this.isSortable this.ariaSort}} @scope={{@selectionScope}} @isStickyColumn={{@isStickyColumn}} @isStickyColumnPinned={{@isStickyColumnPinned}} @compositeItem={{@compositeItem}} @isCompositeItemDisabled={{@isCompositeItemDisabled}} ...attributes>\n <div class=\"hds-advanced-table__th-content\">\n <HdsFormCheckboxBase id={{this._checkboxId}} class=\"hds-advanced-table__checkbox\" checked={{@isSelected}} aria-label={{this.ariaLabel}} {{this._manageCheckbox}} {{on \"change\" this.onSelectionChange}} />\n {{#if this.isSortable}}\n <HdsAdvancedTableThButtonSort @sortOrder={{@sortBySelectedOrder}} @onClick={{@onClickSortBySelected}} @labelId={{this._labelId}} />\n {{/if}}\n </div>\n</HdsAdvancedTableTh>", {
strictMode: true,
scope: () => ({
HdsAdvancedTableTh,
HdsFormCheckboxBase,
on,
HdsAdvancedTableThButtonSort
})
}), this);
}
}
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
/*
* NOTE: There is currently an issue with `WithBoundArgs` or Glint that causes a typing error where @selectionKey is set as always required.
*
* Until this is fixed, we are holding off on doing a union with the SelectableHdsAdvancedTableTrArgs
*/
// Extended interface for selectable rows
// export interface SelectableHdsAdvancedTableTrArgs
// extends BaseHdsAdvancedTableTrSignature {
// Args: BaseHdsAdvancedTableTrSignature['Args'] & {
// isSelectable: true;
// selectionScope?: HdsAdvancedTableScopeValues.Row;
// selectionKey: string; // Now required for selectable rows
// };
// }
// Union type to combine both possible states
// | SelectableHdsAdvancedTableTrArgs;
class HdsAdvancedTableTr extends Component {
get selectionKey() {
if (this.args.isSelectable && this.args.selectionScope === 'row') {
assert(`@selectionKey must be defined on Table::Tr or B.Tr when @isSelectable is true`, this.args.selectionKey);
return this.args.selectionKey;
}
return undefined;
}
get classNames() {
const {
depth,
isLastRow,
isParentRow,
displayRow
} = this.args;
const classes = ['hds-advanced-table__tr'];
if (depth && depth > 0) {
classes.push('hds-advanced-table__tr--nested');
}
if (isParentRow) {
classes.push('hds-advanced-table__tr--parent-row');
}
if (displayRow === false) {
classes.push('hds-advanced-table__tr--hidden');
}
if (isLastRow) {
classes.push('hds-advanced-table__tr--last-row');
}
return classes.join(' ');
}
get cells() {
const {
columnOrder,
data
} = this.args;
if (columnOrder === undefined || data == null) {
return [];
}
return columnOrder.map(columnKey => ({
columnKey,
content: data[columnKey]
}));
}
get orderedCells() {
const {
columnOrder,
data,
hasReorderableColumns
} = this.args;
if (columnOrder === undefined || data === undefined) {
return this.cells;
}
if (hasReorderableColumns) {
return columnOrder.reduce((acc, key) => {
const cell = this.cells.find(cell => cell.columnKey === key);
if (cell !== undefined) {
acc.push(cell);
}
return acc;
}, []);
} else {
return this.cells;
}
}
static {
setComponentTemplate(precompileTemplate("<div class={{this.classNames}} role=\"row\" {{@compositeGroup}} ...attributes>\n {{#if @isSelectable}}\n <HdsAdvancedTableThSelectable role={{if (eq @selectionScope \"row\") \"gridcell\" \"columnheader\"}} @compositeItem={{@compositeItem}} @isCompositeItemDisabled={{@isCompositeItemDisabled}} @isSelected={{@isSelected}} @selectionScope={{@selectionScope}} @selectionKey={{this.selectionKey}} @selectionAriaLabelSuffix={{@selectionAriaLabelSuffix}} @sortBySelectedOrder={{@sortBySelectedOrder}} @didInsert={{@didInsert}} @willDestroy={{@willDestroy}} @onClickSortBySelected={{@onClickSortBySelected}} @onSelectionChange={{@onSelectionChange}} @isStickyColumn={{@hasStickyColumn}} @isStickyColumnPinned={{@isStickyColumnPinned}} />\n {{/if}}\n\n {{yield (hash orderedCells=this.orderedCells)}}\n</div>", {
strictMode: true,
scope: () => ({
HdsAdvancedTableThSelectable,
eq,
hash
})
}), this);
}
}
/**
* Copyright IBM Corp. 2021, 2025
* SPDX-License-Identifier: MPL-2.0
*/
const DENSITIES = Object.values(HdsAdvancedTableDensityValues);
const DEFAULT_DENSITY = HdsAdvancedTableDensityValues.Medium;
const VALIGNMENTS = Object.values(HdsAdvancedTableVerticalAlignmentValues);
const DEFAULT_VALIGN = HdsAdvancedTableVerticalAlignmentValues.Top;
const BORDER_WIDTH = 1;
const DEFAULT_SCROLL_DIMENSIONS = {
bottom: '0px',
height: '0px',
left: '0px',
right: '0px',
width: '0px'
};
const REORDER_EDGE_SCROLL_TRIGGER_PX = 48;
const REORDER_EDGE_SCROLL_STEP_PX = 16;
const getScrollIndicatorDimensions = (scrollWrapper, theadElement, hasStickyFirstColumn, isStickyColumnPinned) => {
const horizontalScrollBarHeight = scrollWrapper.offsetHeight - scrollWrapper.clientHeight;
const verticalScrollBarWidth = scrollWrapper.offsetWidth - scrollWrapper.clientWidth;
let leftOffset = 0;
if (hasStickyFirstColumn) {
const stickyColumnHeaders = theadElement.querySelectorAll('.hds-advanced-table__th--is-sticky-column');
stickyColumnHeaders?.forEach(el => {
// querySelectorAll returns Elements, which don't have offsetWidth
// need to use offsetWidth to account for the cell borders
const elAsHTMLElement = el;
leftOffset += elAsHTMLElement.offsetWidth;
});
// offsets the left: -1px position if there are multiple sticky columns or the first column has a fixed pixel width
if (stickyColumnHeaders.length > 1) {
leftOffset -= 1;
}
// offsets the left: -1px position if the sticky column is already pinned when the scroll indicator is calculated
if (isStickyColumnPinned) {
leftOffset -= 1;
}
}
return {
bottom: `${horizontalScrollBarHeight}px`,
height: `${scrollWrapper.offsetHeight - horizontalScrollBarHeight}px`,
left: `${leftOffset}px`,
right: `${verticalScrollBarWidth}px`,
width: `${scrollWrapper.offsetWidth - verticalScrollBarWidth}px`
};
};
const getStickyColumnLeftOffset = (theadElement, hasRowSelection, isStickyColumnPinned) => {
// if there is no select checkbox column, the sticky column is all the way to the left
if (!hasRowSelection) return '0px';
const selectableCell = theadElement.querySelector('.hds-advanced-table__th--is-selectable');
let leftOffset = selectableCell?.offsetWidth ?? 0;
// if the sticky column is pinned when the offset is calculated, we need to account for the increased width of the border
if (isStickyColumnPinned && leftOffset > 0) {
leftOffset -= 2;
}
return `${leftOffset}px`;
};
class HdsAdvancedTable extends Component {
static {
g(this.prototype, "hdsIntl", [service]);
}
#hdsIntl = (i(this, "hdsIntl"), void 0);
static {
g(this.prototype, "_selectAllCheckbox", [tracked], function () {
return undefined;
});
}
#_selectAllCheckbox = (i(this, "_selectAllCheckbox"), void 0);
static {
g(this.prototype, "_isSelectAllCheckboxSelected", [tracked], function () {
return undefined;
});
}
#_isSelectAllCheckboxSelected = (i(this, "_isSelectAllCheckboxSelected"), void 0);
static {
g(this.prototype, "_tableHeight", [tracked], function () {
return 0;
});
}
#_tableHeight = (i(this, "_tableHeight"), void 0);
_selectableRows = [];
_captionId = 'caption-' + guidFor(this);
_scrollHandler;
_dragOverHandler;
_resizeObserver;
_theadElement;
_scrollWrapperElement;
static {
g(this.prototype, "scrollIndicatorDimensions", [tracked], function () {
return DEFAULT_SCROLL_DIMENSIONS;
});
}
#scrollIndicatorDimensions = (i(this, "scrollIndicatorDimensions"), void 0);
static {
g(this.prototype, "isStickyColumnPinned", [tracked], function () {
return false;
});
}
#isStickyColumnPinned = (i(this, "isStickyColumnPinned"), void 0);
static {
g(this.prototype, "isStickyHeaderPinned", [tracked], function () {
return false;
});
}
#isStickyHeaderPinned = (i(this, "isStickyHeaderPinned"), void 0);
static {
g(this.prototype, "hasPinnedFirstColumn", [tracked], function () {
return undefined;
});
}
#hasPinnedFirstColumn = (i(this, "hasPinnedFirstColumn"), void 0);
static {
g(this.prototype, "reorderedMessageText", [tracked], function () {
return '';
});
}
#reorderedMessageText = (i(this, "reorderedMessageText"), void 0);
static {
g(this.prototype, "showScrollIndicatorLeft", [tracked], function () {
return false;
});
}
#showScrollIndicatorLeft = (i(this, "showScrollIndicatorLeft"), void 0);
static {
g(this.prototype, "showScrollIndicatorRight", [tracked], function () {
return false;
});
}
#showScrollIndicatorRight = (i(this, "showScrollIndicatorRight"), void 0);
static {
g(this.prototype, "showScrollIndicatorTop", [tracked], function () {
return false;
});
}
#showScrollIndicatorTop = (i(this, "showScrollIndicatorTop"), void 0);
static {
g(this.prototype, "showScrollIndicatorBottom", [tracked], function () {
return false;
});
}
#showScrollIndicatorBottom = (i(this, "showScrollIndicatorBottom"), void 0);
static {
g(this.prototype, "stickyColumnOffset", [tracked], function () {
return '0px';
});
}
#stickyColumnOffset = (i(this, "stickyColumnOffset"), void 0);
static {
g(this.prototype, "currentSortBy", [tracked]);
}
#currentSortBy = (i(this, "currentSortBy"), void 0); // sorting properties
static {
g(this.prototype, "currentSortOrder", [tracked], function () {
return HdsAdvancedTableThSortOrderValues.Asc;
});
}
#currentSortOrder = (i(this, "currentSortOrder"), void 0);
// row expansion properties
expandedRowIds = new TrackedSet();
_setUpScrollWrapper = modifier(element => {
this._scrollWrapperElement = element;
const updateHorizontalScrollIndicators = () => {
this.showScrollIndicatorRight = element.clientWidth < element.scrollWidth;
};
this._scrollHandler = () => {
this._updateScrollIndicators(element);
};
this._dragOverHandler = event => {
if (this.args.hasReorderableColumns !== true) {
return;
}
const canScrollHorizontally = element.scrollWidth > element.clientWidth;
if (!canScrollHorizontally) {
return;
}
const firstReorderDropTarget = element.querySelector('.hds-advanced-table__th-reorder-drop-target');
if (firstReorderDropTarget === null) {
return;
}
const stickyColumnHeaders = element.querySelectorAll('.hds-advanced-table__th--is-sticky-column');
const lastStickyColumnHeader = stickyColumnHeaders[stickyColumnHeaders.length - 1];
const leftVisibleEdge = lastStickyColumnHeader?.getBoundingClientRect().right ?? firstReorderDropTarget.getBoundingClientRect().left;
const leftScrollTrigger = lastStickyColumnHeader !== undefined ? leftVisibleEdge : leftVisibleEdge + REORDER_EDGE_SCROLL_TRIGGER_PX;
const {
right
} = element.getBoundingClientRect();
const rightScrollTrigger = right - REORDER_EDGE_SCROLL_TRIGGER_PX;
if (event.clientX <= leftScrollTrigger) {
element.scrollBy({
left: -REORDER_EDGE_SCROLL_STEP_PX
});
} else if (event.clientX >= rightScrollTrigger) {
element.scrollBy({
left: REORDER_EDGE_SCROLL_STEP_PX
});
}
};
element.addEventListener('scroll', this._scrollHandler);
element.addEventListener('dragover', this._dragOverHandler);
const updateMeasurements = () => {
const {
isSelectable = false
} = this.args;
const newTableHeight = element.offsetHeight;
const newDimensions = getScrollIndicatorDimensions(element, this._theadElement, this.hasStickyFirstColumn ? true : false, this.isStickyColumnPinned);
const setUpdatedMeasurements = () => {
if (this.isDestroying || this.isDestroyed) {
return;
}
const isSameBottom = this.scrollIndicatorDimensions.bottom === newDimensions.bottom;
const isSameHeight = this.scrollIndicatorDimensions.height === newDimensions.height;
const isSameLeft = this.scrollIndicatorDimensions.left === newDimensions.left;
const isSameRight = this.scrollIndicatorDimensions.right === newDimensions.right;
if (isSameBottom && isSameHeight && isSameLeft && isSameRight && this._tableHeight === newTableHeight) {
return;
}
this._tableHeight = newTableHeight;
this.scrollIndicatorDimensions = newDimensions;
if (this.hasStickyFirstColumn) {
this.stickyColumnOffset = getStickyColumnLeftOffset(this._theadElement, isSelectable, this.isStickyColumnPinned);
}
};
window.requestAnimationFrame(setUpdatedMeasurements);
};
this._resizeObserver = new ResizeObserver(entries => {
entries.forEach(() => {
updateMeasurements();
updateHorizontalScrollIndicators();
});
});
this._resizeObserver.observe(element);
updateMeasurements();
// on render check if should show right scroll indicator
updateHorizontalScrollIndicators();
// on render check if should show bottom scroll indicator
if (element.clientHeight < element.scrollHeight) {
this.showScrollIndicatorBottom = true;
}
return () => {
element.removeEventListener('scroll', this._scrollHandler);
element.removeEventListener('dragover', this._dragOverHandler);
this._resizeObserver.disconnect();
};
});
_setUpThead = modifier(element => {
this._theadElement = element;
});
_syncSortArgs = modifier(() => {
const {
sortBy,
sortOrder
} = this.args;
if (sortBy !== undefined) {
this.currentSortBy = sortBy;
}
if (sortOrder !== undefined) {
this.currentSortOrder = sortOrder;
}
});
constructor(owner, args) {
super(owner, args);
const {
hasStickyFirstColumn,
model
} = args;
this._runAssertions();
this._initializeExpandedRows(model);
if (hasStickyFirstColumn) {
this.hasPinnedFirstColumn = true;
}
}
get childrenKey() {
return this.args.childrenKey ?? 'children';
}
get hasRowsWithChildren() {
const {
model
} = this.args;
return model.some(record => {
const children = record[this.childrenKey];
return Array.isArray(children) && children.length > 0;
});
}
get expandableRowIds() {
const {
model
} = this.args;
const ids = [];
const collect = items => {
items.forEach(item => {
const children = item[this.childrenKey];
if (Array.isArray(children) && children.length > 0) {
ids.push(guidFor(item));
collect(children);
}
});
};
collect(model);
return ids;
}
static {
n(this.prototype, "expandableRowIds", [cached]);
}
get isAllExpanded() {
if (this.expandableRowIds.length === 0) {
return false;
}
return this.expandableRowIds.every(id => this.expandedRowIds.has(id));
}
get sortCriteria() {
const {
columns
} = this.args;
const currentColumn = columns.find(column => column.key === this.currentSortBy);
if (currentColumn?.sortingFunction && typeof currentColumn.sortingFunction === 'function') {
return currentColumn.sortingFunction;
} else {
return `${this.currentSortBy}:${this.currentSortOrder}`;
}
}
get isEmpty() {
const {
model
} = this.args;
return model.length === 0;
}
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 hasStickyFirstColumn() {
// The user-controll