Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { FlatTableProps } from "../flat-table.component";

export interface StrictFlatTableContextType
extends Pick<FlatTableProps, "colorTheme" | "size"> {
getTabStopElementId: () => string;
tabStopElementId: string;
notifyTabStopChange: () => void;
}

const [StrictFlatTableProvider, useStrictFlatTableContext] =
Expand All @@ -12,7 +13,8 @@ const [StrictFlatTableProvider, useStrictFlatTableContext] =
errorMessage:
"Carbon FlatTable: Context not found. Have you wrapped your Carbon subcomponents properly? See stack trace for more details.",
defaultValue: {
getTabStopElementId: () => "",
tabStopElementId: "",
notifyTabStopChange: () => {},
},
});

Expand Down
15 changes: 3 additions & 12 deletions src/components/flat-table/__internal__/use-table-cell.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useContext, useEffect, useState } from "react";
import { useContext } from "react";
import FlatTableRowContext from "../flat-table-row/__internal__/flat-table-row.context";
import { useStrictFlatTableContext } from "./strict-flat-table.context";

export default (id: string) => {
const { getTabStopElementId } = useStrictFlatTableContext();
const [tabIndex, setTabIndex] = useState(-1);
const { tabStopElementId } = useStrictFlatTableContext();
const {
expandable,
firstCellId,
Expand Down Expand Up @@ -85,15 +84,7 @@ export default (id: string) => {
}
};

useEffect(() => {
const tabstopTimer = setTimeout(() => {
setTabIndex(isExpandableCell && getTabStopElementId() === id ? 0 : -1);
}, 0);

return () => {
clearTimeout(tabstopTimer);
};
}, [getTabStopElementId, isExpandableCell, id]);
const tabIndex = isExpandableCell && tabStopElementId === id ? 0 : -1;

return {
expandable,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { buildPositionMap } from "../../__internal__";

interface RowLayoutState {
leftPositions: Record<string, number>;
rightPositions: Record<string, number>;
firstCellIndex: number;
lhsRowHeaderIndex: number;
rhsRowHeaderIndex: number;
firstCellId: string | null;
cellsArray: Element[];
}

export const initialRowLayoutState: RowLayoutState = {
leftPositions: {},
rightPositions: {},
firstCellIndex: 0,
lhsRowHeaderIndex: -1,
rhsRowHeaderIndex: -1,
firstCellId: null,
cellsArray: [],
};

export const rowLayoutReducer = (
state: RowLayoutState,
cells: HTMLTableCellElement[],
): RowLayoutState => {
const firstIndex = cells.findIndex(
(cell) => cell.getAttribute("data-component") !== "flat-table-checkbox",
);
const lhsIndex = cells.findIndex(
(cell) => cell.getAttribute("data-sticky-align") === "left",
);
const rhsIndex = cells.findIndex(
(cell) => cell.getAttribute("data-sticky-align") === "right",
);
const { leftPositions, rightPositions, firstCellId } = state;

return {
cellsArray: cells,
firstCellIndex: firstIndex !== -1 ? firstIndex : 0,
firstCellId:
firstIndex !== -1 ? cells[firstIndex].getAttribute("id") : firstCellId,
lhsRowHeaderIndex: lhsIndex,
rhsRowHeaderIndex: rhsIndex,
leftPositions:
lhsIndex !== -1
? buildPositionMap(cells.slice(0, lhsIndex + 1), "offsetWidth")
: leftPositions,
rightPositions:
rhsIndex !== -1
? buildPositionMap(
cells.slice(rhsIndex, cells.length).reverse(),
"offsetWidth",
)
: rightPositions,
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { createContext, useCallback, useState } from "react";
import React, { createContext, useCallback, useRef } from "react";

export interface SubRowContextProps {
isSubRow?: boolean;
Expand All @@ -15,19 +15,24 @@ export const SubRowContext = createContext<SubRowContextProps>({
});

const SubRowProvider = ({ children }: { children: React.ReactNode }) => {
const [rowIds, setRowIds] = useState<string[]>([]);
const rowIds = useRef<string[]>([]);

const addRow = useCallback((id: string) => {
setRowIds((p) => [...p, id]);
rowIds.current = [...rowIds.current, id];
}, []);

const removeRow = useCallback((id: string) => {
setRowIds((p) => p.filter((rowId) => rowId !== id));
rowIds.current = rowIds.current.filter((rowId) => rowId !== id);
}, []);

return (
<SubRowContext.Provider
value={{ isSubRow: true, firstRowId: rowIds[0], addRow, removeRow }}
value={{
isSubRow: true,
firstRowId: rowIds.current[0],
addRow,
removeRow,
}}
>
{children}
</SubRowContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useState,
useLayoutEffect,
useCallback,
useReducer,
} from "react";
import invariant from "invariant";

Expand All @@ -19,9 +20,12 @@ import { useStrictFlatTableContext } from "../__internal__/strict-flat-table.con
import guid from "../../../__internal__/utils/helpers/guid";
import FlatTableRowContext from "./__internal__/flat-table-row.context";
import SubRowProvider, { SubRowContext } from "./__internal__/sub-row-provider";
import { buildPositionMap } from "../__internal__";
import FlatTableHeadContext from "../flat-table-head/__internal__/flat-table-head.context";
import { useSortableRow } from "../__internal__/sortable";
import {
rowLayoutReducer,
initialRowLayoutState,
} from "./__internal__/row-layout-reducer";

export interface FlatTableRowProps extends TagProps {
/** Overrides default cell color, provide design token, any color from palette or any valid css color value. */
Expand Down Expand Up @@ -82,88 +86,28 @@ export const FlatTableRow = React.forwardRef<
const [isExpanded, setIsExpanded] = useState(expanded);
const rowRef = useRef<HTMLTableRowElement | null>(null);
const firstColumnExpandable = expandableArea === "firstColumn";
const [leftPositions, setLeftPositions] = useState<Record<string, number>>(
{},
);
const [rightPositions, setRightPositions] = useState<
Record<string, number>
>({});
const [firstCellIndex, setFirstCellIndex] = useState(0);
const [lhsRowHeaderIndex, setLhsRowHeaderIndex] = useState(-1);
const [rhsRowHeaderIndex, setRhsRowHeaderIndex] = useState(-1);
const [firstCellId, setFirstCellId] = useState<string | null>(null);
const [cellsArray, setCellsArray] = useState<Element[]>([]);
const [tabIndex, setTabIndex] = useState(-1);

let interactiveRowProps = {};

useLayoutEffect(() => {
const checkForPositionUpdates = (
updated: Record<string, number>,
current: Record<string, number>,
) => {
const updatedKeys = Object.keys(updated);
const currentKeys = Object.keys(current);
if (updatedKeys.length !== currentKeys.length) {
return true;
}

return updatedKeys.some((key) => updated[key] !== current[key]);
};
const [layoutState, dispatchLayout] = useReducer(
rowLayoutReducer,
initialRowLayoutState,
);
const {
leftPositions,
rightPositions,
firstCellIndex,
lhsRowHeaderIndex,
rhsRowHeaderIndex,
firstCellId,
cellsArray,
} = layoutState;

useLayoutEffect(() => {
const cells = rowRef.current?.querySelectorAll("th, td") as
| NodeListOf<HTMLTableCellElement>
| undefined;

const cellArray = Array.from(cells || /*istanbul ignore next */ []);
setCellsArray(cellArray);

const firstIndex = cellArray.findIndex(
(cell) => cell.getAttribute("data-component") !== "flat-table-checkbox",
);
const lhsIndex = cellArray.findIndex(
(cell) => cell.getAttribute("data-sticky-align") === "left",
);
const rhsIndex = cellArray.findIndex(
(cell) => cell.getAttribute("data-sticky-align") === "right",
);

setLhsRowHeaderIndex(lhsIndex);
setRhsRowHeaderIndex(rhsIndex);

if (firstIndex !== -1) {
setFirstCellIndex(firstIndex);
setFirstCellId(cellArray[firstIndex].getAttribute("id"));
} else {
setFirstCellIndex(0);
}
if (lhsIndex !== -1) {
const updatedLeftPositions = buildPositionMap(
cellArray.slice(0, lhsRowHeaderIndex + 1),
"offsetWidth",
);

if (checkForPositionUpdates(updatedLeftPositions, leftPositions)) {
setLeftPositions(updatedLeftPositions);
}
}
if (rhsIndex !== -1) {
const updatedRightPositions = buildPositionMap(
cellArray.slice(rhsRowHeaderIndex, cellArray.length).reverse(),
"offsetWidth",
);

if (checkForPositionUpdates(updatedRightPositions, rightPositions)) {
setRightPositions(updatedRightPositions);
}
}
}, [
children,
leftPositions,
lhsRowHeaderIndex,
rhsRowHeaderIndex,
rightPositions,
]);
dispatchLayout(Array.from(cells || /* istanbul ignore next */ []));
}, [children]);

const noStickyColumnsOverlap = useMemo(() => {
const hasLhsColumn = lhsRowHeaderIndex !== -1;
Expand All @@ -178,8 +122,27 @@ export const FlatTableRow = React.forwardRef<
`Do not render a right hand side \`${FlatTableRowHeader.displayName}\` before left hand side \`${FlatTableRowHeader.displayName}\``,
);

const { colorTheme, size, getTabStopElementId } =
const { colorTheme, size, tabStopElementId, notifyTabStopChange } =
useStrictFlatTableContext();
const tabIndex = tabStopElementId === internalId.current ? 0 : -1;

// Notify FlatTable when this row's focusability or selection state changes
useEffect(() => {
if (onClick || expandable) {
notifyTabStopChange();
}
return () => {
notifyTabStopChange();
};
}, [
onClick,
expandable,
selected,
highlighted,
firstCellId,
notifyTabStopChange,
]);

const { isInSidebar } = useContext(DrawerSidebarContext);
const { stickyOffsets } = useContext(FlatTableHeadContext);

Expand Down Expand Up @@ -244,10 +207,6 @@ export const FlatTableRow = React.forwardRef<
setIsExpanded(expanded);
}, [expanded]);

useEffect(() => {
setTabIndex(getTabStopElementId() === internalId.current ? 0 : -1);
}, [getTabStopElementId]);

const { isSubRow, firstRowId, addRow, removeRow } =
useContext(SubRowContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ interface StyledFlatTableRowProps
rowHeight?: number;
}

const nav = getNavigator();
const isSafariBrowser = nav ? isSafari(nav) : /* istanbul ignore next */ false;

const StyledFlatTableRow = styled.tr.attrs(
applyBaseTheme,
)<StyledFlatTableRowProps>`
Expand Down Expand Up @@ -156,7 +159,6 @@ const StyledFlatTableRow = styled.tr.attrs(
draggable,
rowHeight,
}) => {
const nav = getNavigator();
const backgroundColor = bgColor ? toColor(theme, bgColor) : undefined;
const customBorderColor = horizontalBorderColor
? toColor(theme, horizontalBorderColor)
Expand Down Expand Up @@ -283,8 +285,7 @@ const StyledFlatTableRow = styled.tr.attrs(

/* Styling for safari. Position relative does not work on tr elements on Safari */
// FIXME: this can cause hydration mismatches during SSR.
${nav &&
isSafari(nav) &&
${isSafariBrowser &&
css`
position: -webkit-sticky;
:after {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ describe("when the row is `expandable`", () => {
const cell = screen.getByRole("cell", { name: "cell1" });

expect(row).not.toHaveAttribute("tabindex");
expect(cell).toHaveAttribute("tabindex", "-1");
expect(cell).toHaveAttribute("tabindex", "0");
});

it("should add and apply the expected styling to the chevron icon", () => {
Expand Down Expand Up @@ -1570,7 +1570,11 @@ describe("when the row is `expandable`", () => {
render(
<table>
<StrictFlatTableProvider
value={{ size: "compact", getTabStopElementId: () => "" }}
value={{
size: "compact",
tabStopElementId: "",
notifyTabStopChange: () => {},
}}
>
<FlatTableBody>
<FlatTableRow
Expand Down
Loading
Loading