Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Body/MeasureRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isVisible from '@rc-component/util/lib/Dom/isVisible';
import { useContext } from '@rc-component/context';
import TableContext from '../context/TableContext';
import type { ColumnType } from '../interface';
import { prepareMeasureTitle } from '../utils/measureUtil';

export interface MeasureRowProps {
prefixCls: string;
Expand Down Expand Up @@ -37,9 +38,8 @@ const MeasureRow: React.FC<MeasureRowProps> = ({
{columnsKey.map(columnKey => {
const column = columns.find(col => col.key === columnKey);
const rawTitle = column?.title;
const titleForMeasure = React.isValidElement<React.RefAttributes<any>>(rawTitle)
? React.cloneElement(rawTitle, { ref: null })
: rawTitle;
const titleForMeasure = prepareMeasureTitle(rawTitle);

return (
<MeasureCell
key={columnKey}
Expand Down
96 changes: 96 additions & 0 deletions src/utils/measureUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as React from 'react';

/**
* Props that should be filtered out from measure row elements to avoid DOM conflicts
*/
const FILTERED_PROPS = [
// Unique identifiers that shouldn't be duplicated in DOM
'id',
'data-testid',
'data-test-id',
'data-cy', // Cypress
'data-qa',
'data-automation-id',
'data-id',
'data-key',

// Event handlers that are unnecessary in hidden measure elements
'onClick',
'onMouseEnter',
'onMouseLeave',
'onFocus',
'onBlur',
'onKeyDown',
'onKeyPress',
'onKeyUp',
'onDoubleClick',
'onContextMenu',

// Accessibility props that might reference other elements and cause conflicts
'aria-describedby',
'aria-labelledby',
'aria-controls',
'aria-owns',

// Form-related props that might conflict
'name',
'htmlFor',
'for',
] as const;

/**
* Filter props from a React element that might cause DOM conflicts in measure row
* @param element - The React element to filter
* @param deep - Whether to recursively filter nested elements
* @returns A new React element with filtered props
*/
export function filterMeasureProps<T = any>(
element: React.ReactElement<T>,
deep: boolean = true,
): React.ReactElement<T> {
if (!React.isValidElement(element)) {
return element;
}

const filteredProps = { ...element.props } as any;

FILTERED_PROPS.forEach(prop => {
filteredProps[prop] = undefined;
});

// Nullify ref to avoid warnings and conflicts
filteredProps.ref = null;

// Recursively filter children if deep filtering is enabled
if (deep && filteredProps.children) {
filteredProps.children = filterMeasureChildren(filteredProps.children);
}

return React.cloneElement(element, filteredProps);
}

/**
* Recursively filter children elements
* @param children - React children to filter
* @returns Filtered children
*/
function filterMeasureChildren(children: React.ReactNode): React.ReactNode {
return React.Children.map(children, child => {
if (React.isValidElement(child)) {
return filterMeasureProps(child, true);
}
return child;
});
}

/**
* Prepare title content for use in measure row
* @param title - The original title content
* @returns Filtered title safe for measure row usage
*/
export function prepareMeasureTitle(title: React.ReactNode): React.ReactNode {
if (React.isValidElement(title)) {
return filterMeasureProps(title, true); // Enable deep filtering
}
return title;
}