From 789ceeae5be848726255b2e13a2d7d6955ca8903 Mon Sep 17 00:00:00 2001 From: Nathnael Dereje Date: Wed, 7 Jan 2026 14:17:48 +0100 Subject: [PATCH] feat: Add label based page scannable metadata for selected items and column --- src/table/analytics-metadata/interfaces.ts | 3 ++ src/table/analytics-metadata/styles.scss | 4 ++ src/table/body-cell/td-element.tsx | 11 ++++- src/table/header-cell/th-element.tsx | 2 + src/table/index.tsx | 48 ++++++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/table/analytics-metadata/interfaces.ts b/src/table/analytics-metadata/interfaces.ts index 7fb89ba058..370ea66b7a 100644 --- a/src/table/analytics-metadata/interfaces.ts +++ b/src/table/analytics-metadata/interfaces.ts @@ -55,6 +55,9 @@ export interface GeneratedAnalyticsMetadataTableComponent { sortingColumnId?: string; sortingDescending?: string; variant: string; + columnsLabel?: string[] | LabelIdentifier[]; + selectedItemsLabel?: string | Array>; + selectedItems?: string[]; }; innerContext?: { position: string; diff --git a/src/table/analytics-metadata/styles.scss b/src/table/analytics-metadata/styles.scss index b84ae6909d..b6428877c3 100644 --- a/src/table/analytics-metadata/styles.scss +++ b/src/table/analytics-metadata/styles.scss @@ -6,3 +6,7 @@ .header-cell-text { /* used in analytics metadata */ } + +.body-cell-content { + /* used in analytics metadata */ +} diff --git a/src/table/body-cell/td-element.tsx b/src/table/body-cell/td-element.tsx index 331ff7759d..cb655b83fe 100644 --- a/src/table/body-cell/td-element.tsx +++ b/src/table/body-cell/td-element.tsx @@ -15,6 +15,7 @@ import { StickyColumnsModel, useStickyCellStyles } from '../sticky-columns'; import { getTableCellRoleProps, TableRole } from '../table-role'; import { getStickyClassNames } from '../utils'; +import analyticsSelectors from '../analytics-metadata/styles.css.js'; import tableStyles from '../styles.css.js'; import styles from './styles.css.js'; @@ -165,7 +166,15 @@ export const TableTdElement = React.forwardRef )} -
{children}
+
+ {children} +
); } diff --git a/src/table/header-cell/th-element.tsx b/src/table/header-cell/th-element.tsx index 55e5739e02..a9eb84adbd 100644 --- a/src/table/header-cell/th-element.tsx +++ b/src/table/header-cell/th-element.tsx @@ -15,6 +15,7 @@ import { getTableColHeaderRoleProps, TableRole } from '../table-role'; import { getStickyClassNames } from '../utils'; import { SortingStatus } from './utils'; +import analyticsSelectors from '../analytics-metadata/styles.css.js'; import tableStyles from '../styles.css.js'; import styles from './styles.css.js'; @@ -80,6 +81,7 @@ export function TableThElement({ className={clsx( styles['header-cell'], styles[`header-cell-variant-${variant}`], + analyticsSelectors['body-cell-content'], sticky && styles['header-cell-sticky'], resizable && styles['header-cell-resizable'], stuck && styles['header-cell-stuck'], diff --git a/src/table/index.tsx b/src/table/index.tsx index 394c6d5f3e..f21fa3a707 100644 --- a/src/table/index.tsx +++ b/src/table/index.tsx @@ -11,9 +11,13 @@ import { CollectionPreferencesMetadata } from '../internal/context/collection-pr import useBaseComponent from '../internal/hooks/use-base-component'; import { applyDisplayName } from '../internal/utils/apply-display-name'; import { GeneratedAnalyticsMetadataTableComponent } from './analytics-metadata/interfaces'; +import { useExpandableTableProps } from './expandable-rows/expandable-rows-utils'; import { getSortingColumnId } from './header-cell/utils'; import { TableForwardRefType, TableProps } from './interfaces'; import InternalTable, { InternalTableAsSubstep } from './internal'; +import { getItemKey } from './utils'; + +import analyticsSelectors from './analytics-metadata/styles.css.js'; export { TableProps }; const Table = React.forwardRef( @@ -34,6 +38,15 @@ const Table = React.forwardRef( (props.visibleColumns && props.visibleColumns.length < props.columnDefinitions.length) || props.columnDisplay?.some(col => !col.visible); const hasStickyColumns = !!props.stickyColumns?.first || !!props.stickyColumns?.last; + + // Process expandable rows to get allItems (including expanded children) + const { allItems } = useExpandableTableProps({ + items, + expandableRows: props.expandableRows, + trackBy: props.trackBy, + ariaLabels: props.ariaLabels, + }); + const baseComponentProps = useBaseComponent( 'Table', { @@ -84,6 +97,41 @@ const Table = React.forwardRef( }, }; + const columns = props.columnDefinitions.map((colDef, colIndex) => { + const headerPosition = colIndex + (props.selectionType ? 2 : 1); + + return { + selector: `thead th:nth-child(${headerPosition}) .${analyticsSelectors['header-cell-text']}`, + root: 'self' as const, + }; + }); + + analyticsComponentMetadata.properties.columnsLabel = columns; + + if (props.trackBy && selectedItems.length > 0) { + analyticsComponentMetadata.properties.selectedItems = selectedItems.map( + (item, index) => `${getItemKey(props.trackBy, item, index)}` + ); + + const selectedItemsLabel = selectedItems.map((item, itemIndex) => { + const rowIndexInItems = allItems.findIndex( + i => getItemKey(props.trackBy, i, allItems.indexOf(i)) === getItemKey(props.trackBy, item, itemIndex) + ); + const rowNumber = rowIndexInItems + 1; // CSS nth-child is 1-based + + return props.columnDefinitions.map((colDef, colIndex) => { + const cellPosition = colIndex + (props.selectionType ? 2 : 1); + + return { + selector: `tbody tr:nth-child(${rowNumber}) > :nth-child(${cellPosition}) .${analyticsSelectors['body-cell-content']}`, + root: 'self' as const, + }; + }); + }); + + analyticsComponentMetadata.properties.selectedItemsLabel = selectedItemsLabel; + } + const sortingColumnId = getSortingColumnId(props.columnDefinitions, props.sortingColumn); if (sortingColumnId) { analyticsComponentMetadata.properties.sortingColumnId = sortingColumnId;