@multiplatform.one/table 6.4.3 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/ChildTable/index.native.js +1 -1
- package/dist/cjs/components/ChildTable/index.native.js.map +1 -1
- package/dist/cjs/components/ColumnHeaderFilter/index.cjs +1 -0
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js +2 -1
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js +1 -1
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableContext.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableMobileCards.cjs +9 -3
- package/dist/cjs/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/cjs/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableRoot.cjs +133 -13
- package/dist/cjs/components/DataTable/DataTableRoot.native.js +166 -20
- package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableStateContext.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.cjs +3 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/cjs/components/DataTable/index.native.js +49 -12
- package/dist/cjs/components/DataTable/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableBulkActions/index.cjs +16 -1
- package/dist/cjs/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/cjs/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableCell/CellModalEditor.cjs +4 -8
- package/dist/cjs/components/DataTableCell/CellOptionsEditor.native.js.map +1 -1
- package/dist/cjs/components/DataTableCell/index.cjs +1 -33
- package/dist/cjs/components/DataTableCell/index.native.js +1 -34
- package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableColumnPinning/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableDateFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableExport/index.cjs +1 -0
- package/dist/cjs/components/DataTableExport/index.native.js +1 -0
- package/dist/cjs/components/DataTableExport/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilterButton/index.cjs +1 -1
- package/dist/cjs/components/DataTableFilterButton/index.native.js +5 -2
- package/dist/cjs/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilterChips/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilters/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableGrouping/index.cjs +1 -0
- package/dist/cjs/components/DataTableGrouping/index.native.js +1 -0
- package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/cjs/components/DataTablePagination/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableToolbar/index.cjs +1 -0
- package/dist/cjs/components/DataTableToolbar/index.native.js +2 -1
- package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
- package/dist/cjs/components/FilterManager.cjs +18 -14
- package/dist/cjs/components/FilterManager.native.js +18 -14
- package/dist/cjs/components/FilterManager.native.js.map +1 -1
- package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
- package/dist/cjs/components/KnobDialog.native.js.map +1 -1
- package/dist/cjs/components/SavedFilterDropdown.native.js.map +1 -1
- package/dist/cjs/components/SimpleTable/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/index.native.js +4 -1
- package/dist/cjs/components/TableInput/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/shared.native.js.map +1 -1
- package/dist/cjs/hooks/useDataTable.cjs +17 -1
- package/dist/cjs/hooks/useDataTable.native.js +19 -1
- package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
- package/dist/cjs/hooks/useDataTableKeyboard.native.js.map +1 -1
- package/dist/cjs/hooks/useDragScroll.native.js.map +1 -1
- package/dist/cjs/hooks/useInfiniteRows.native.js.map +1 -1
- package/dist/cjs/hooks/useSavedFilters.native.js.map +1 -1
- package/dist/cjs/hooks/useTableColumnPinning.native.js.map +1 -1
- package/dist/cjs/hooks/useTableFiltering.native.js.map +1 -1
- package/dist/cjs/hooks/useTableGrouping.native.js.map +1 -1
- package/dist/cjs/hooks/useTableKeyboard.native.js.map +1 -1
- package/dist/cjs/hooks/useTablePagination.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSearch.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSelection.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSorting.native.js.map +1 -1
- package/dist/cjs/hooks/useTableUrlSync.native.js.map +1 -1
- package/dist/cjs/hooks/useTableViews.native.js.map +1 -1
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/cjs/shims/primitives.native.js.map +1 -1
- package/dist/cjs/shims/tableCellContext.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/utils/columnAlign.native.js.map +1 -1
- package/dist/cjs/utils/filterFields.native.js.map +1 -1
- package/dist/cjs/utils/filterUtils.native.js.map +1 -1
- package/dist/cjs/utils/paginationDefaults.native.js.map +1 -1
- package/dist/cjs/utils/rowPress.cjs +44 -0
- package/dist/cjs/utils/rowPress.native.js +56 -0
- package/dist/cjs/utils/rowPress.native.js.map +1 -0
- package/dist/cjs/utils/urlStateUtils.native.js.map +1 -1
- package/dist/cjs/utils/viewData.native.js.map +1 -1
- package/dist/cjs/utils/withInterp.native.js.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.mjs +1 -0
- package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.native.js +1 -0
- package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableMobileCards.mjs +9 -3
- package/dist/esm/components/DataTable/DataTableMobileCards.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/esm/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.mjs +133 -13
- package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.native.js +159 -13
- package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.mjs +3 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/esm/components/DataTable/index.native.js +39 -2
- package/dist/esm/components/DataTable/index.native.js.map +1 -1
- package/dist/esm/components/DataTableBulkActions/index.mjs +16 -1
- package/dist/esm/components/DataTableBulkActions/index.mjs.map +1 -1
- package/dist/esm/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/esm/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs +4 -8
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.mjs +1 -33
- package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.native.js +1 -34
- package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
- package/dist/esm/components/DataTableExport/index.mjs +1 -0
- package/dist/esm/components/DataTableExport/index.mjs.map +1 -1
- package/dist/esm/components/DataTableExport/index.native.js +1 -0
- package/dist/esm/components/DataTableExport/index.native.js.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs +1 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js +4 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.mjs +1 -0
- package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.native.js +1 -0
- package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.mjs +1 -0
- package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.native.js +1 -0
- package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/esm/components/FilterManager.mjs +18 -14
- package/dist/esm/components/FilterManager.mjs.map +1 -1
- package/dist/esm/components/FilterManager.native.js +18 -14
- package/dist/esm/components/FilterManager.native.js.map +1 -1
- package/dist/esm/components/TableInput/index.native.js +3 -0
- package/dist/esm/components/TableInput/index.native.js.map +1 -1
- package/dist/esm/hooks/useDataTable.mjs +17 -1
- package/dist/esm/hooks/useDataTable.mjs.map +1 -1
- package/dist/esm/hooks/useDataTable.native.js +19 -1
- package/dist/esm/hooks/useDataTable.native.js.map +1 -1
- package/dist/esm/utils/rowPress.mjs +19 -0
- package/dist/esm/utils/rowPress.mjs.map +1 -0
- package/dist/esm/utils/rowPress.native.js +28 -0
- package/dist/esm/utils/rowPress.native.js.map +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableMobileCards.mjs +9 -3
- package/dist/jsx/components/DataTable/DataTableMobileCards.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/jsx/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.mjs +133 -13
- package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.native.js +159 -13
- package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.mjs +3 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/jsx/components/DataTable/index.native.js +39 -2
- package/dist/jsx/components/DataTable/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableBulkActions/index.mjs +16 -1
- package/dist/jsx/components/DataTableBulkActions/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/jsx/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs +4 -8
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.mjs +1 -33
- package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.native.js +1 -34
- package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableExport/index.mjs +1 -0
- package/dist/jsx/components/DataTableExport/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableExport/index.native.js +1 -0
- package/dist/jsx/components/DataTableExport/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.native.js +4 -1
- package/dist/jsx/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.mjs +1 -0
- package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.native.js +1 -0
- package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.mjs +1 -0
- package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.native.js +1 -0
- package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/jsx/components/FilterManager.mjs +18 -14
- package/dist/jsx/components/FilterManager.mjs.map +1 -1
- package/dist/jsx/components/FilterManager.native.js +18 -14
- package/dist/jsx/components/FilterManager.native.js.map +1 -1
- package/dist/jsx/components/TableInput/index.native.js +3 -0
- package/dist/jsx/components/TableInput/index.native.js.map +1 -1
- package/dist/jsx/hooks/useDataTable.mjs +17 -1
- package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
- package/dist/jsx/hooks/useDataTable.native.js +19 -1
- package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
- package/dist/jsx/utils/rowPress.mjs +19 -0
- package/dist/jsx/utils/rowPress.mjs.map +1 -0
- package/dist/jsx/utils/rowPress.native.js +56 -0
- package/dist/jsx/utils/rowPress.native.js.map +1 -0
- package/package.json +12 -12
- package/src/components/ColumnHeaderFilter/index.tsx +1 -0
- package/src/components/DataTable/DataTable.spec.tsx +4 -1
- package/src/components/DataTable/DataTable.stories.tsx +190 -5
- package/src/components/DataTable/DataTableMobileCards.tsx +30 -4
- package/src/components/DataTable/DataTableRoot.tsx +195 -8
- package/src/components/DataTable/DataTableVirtualized.tsx +8 -0
- package/src/components/DataTable/cardLayout.spec.tsx +4 -1
- package/src/components/DataTable/customCellAlign.spec.tsx +124 -0
- package/src/components/DataTable/disabledContract.spec.tsx +4 -3
- package/src/components/DataTable/index.native.tsx +47 -1
- package/src/components/DataTable/rowPress.spec.tsx +229 -0
- package/src/components/DataTable/selectionSemantics.spec.tsx +156 -0
- package/src/components/DataTableBulkActions/index.tsx +16 -2
- package/src/components/DataTableBulkActions/selectionCountI18n.spec.tsx +94 -0
- package/src/components/DataTableCell/CellModalEditor.tsx +3 -1
- package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +10 -2
- package/src/components/DataTableCell/dataTableCell.spec.tsx +10 -2
- package/src/components/DataTableCell/index.tsx +5 -36
- package/src/components/DataTableColumnPinning/DataTableColumnPinning.stories.tsx +49 -17
- package/src/components/DataTableDateFilter/DataTableDateFilter.stories.tsx +4 -3
- package/src/components/DataTableExport/DataTableExport.stories.tsx +3 -2
- package/src/components/DataTableExport/index.tsx +1 -0
- package/src/components/DataTableFilterButton/index.tsx +4 -1
- package/src/components/DataTableGrouping/DataTableGrouping.spec.tsx +59 -2
- package/src/components/DataTableGrouping/DataTableGrouping.stories.tsx +17 -5
- package/src/components/DataTableGrouping/index.tsx +1 -0
- package/src/components/DataTablePagination/DataTablePagination.stories.tsx +34 -20
- package/src/components/DataTableToolbar/index.tsx +1 -0
- package/src/components/DataTableViewOptions/DataTableViewOptions.stories.tsx +2 -1
- package/src/components/FilterManager.tsx +21 -13
- package/src/components/TableInput/index.native.tsx +3 -0
- package/src/components/__scratch__/scratch.spec.tsx +46 -0
- package/src/hooks/useDataTable.ts +27 -1
- package/src/types.ts +25 -0
- package/src/utils/rowPress.ts +58 -0
- package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
- package/types/components/DataTable/DataTableMobileCards.d.ts +13 -1
- package/types/components/DataTable/DataTableMobileCards.d.ts.map +1 -1
- package/types/components/DataTable/DataTableRoot.d.ts +1 -1
- package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
- package/types/components/DataTable/DataTableVirtualized.d.ts +7 -1
- package/types/components/DataTable/DataTableVirtualized.d.ts.map +1 -1
- package/types/components/DataTable/index.native.d.ts +1 -1
- package/types/components/DataTable/index.native.d.ts.map +1 -1
- package/types/components/DataTableBulkActions/index.d.ts.map +1 -1
- package/types/components/DataTableCell/CellModalEditor.d.ts.map +1 -1
- package/types/components/DataTableCell/index.d.ts.map +1 -1
- package/types/components/DataTableFilterButton/index.d.ts.map +1 -1
- package/types/components/DataTableGrouping/index.d.ts.map +1 -1
- package/types/components/DataTableToolbar/index.d.ts.map +1 -1
- package/types/components/FilterManager.d.ts +7 -1
- package/types/components/FilterManager.d.ts.map +1 -1
- package/types/components/TableInput/index.native.d.ts.map +1 -1
- package/types/hooks/useDataTable.d.ts.map +1 -1
- package/types/types.d.ts +23 -0
- package/types/types.d.ts.map +1 -1
- package/types/utils/rowPress.d.ts +20 -0
- package/types/utils/rowPress.d.ts.map +1 -0
|
@@ -62,6 +62,7 @@ import { DataTableVirtualized } from "./DataTableVirtualized";
|
|
|
62
62
|
import { alignToFlex, getDefaultColumnAlign } from "../../utils/columnAlign";
|
|
63
63
|
import { mapFieldToFilterType } from "../../utils/filterFields";
|
|
64
64
|
import { INFINITE_TRIGGER_ROWS } from "../../utils/paginationDefaults";
|
|
65
|
+
import { isInteractiveInnerTarget } from "../../utils/rowPress";
|
|
65
66
|
import { bidiIsolate, withInterp } from "../../utils/withInterp";
|
|
66
67
|
|
|
67
68
|
const SELECTION_COL_WIDTH = 40;
|
|
@@ -134,6 +135,8 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
134
135
|
maxPinnedColumns = 3,
|
|
135
136
|
enableGrouping = false,
|
|
136
137
|
enableKeyboardNavigation = false,
|
|
138
|
+
onRowPress,
|
|
139
|
+
rowLabel,
|
|
137
140
|
enableRowSelection = false,
|
|
138
141
|
selectAllScope: selectAllScopeProp,
|
|
139
142
|
bulkActions,
|
|
@@ -473,6 +476,159 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
473
476
|
: undefined,
|
|
474
477
|
});
|
|
475
478
|
|
|
479
|
+
const isSSR = typeof window === "undefined";
|
|
480
|
+
|
|
481
|
+
// ---- Row press (onRowPress): the ROW is one pressable unit ----
|
|
482
|
+
|
|
483
|
+
const rowPressActive = Boolean(onRowPress) && !disabled;
|
|
484
|
+
// LC-49 roving anchor: the row set exposes exactly one tab stop — the
|
|
485
|
+
// focused row while focus is inside the set, else the first row.
|
|
486
|
+
const [rowPressAnchor, setRowPressAnchor] = useState(0);
|
|
487
|
+
const effectiveRowPressAnchor = Math.min(rowPressAnchor, Math.max(tableRows.length - 1, 0));
|
|
488
|
+
|
|
489
|
+
// Row identity for accessible names (DG-A11Y-06): consumer accessor →
|
|
490
|
+
// first visible column's cell value → undefined (caller picks its own
|
|
491
|
+
// positional fallback). Shared by the row-press label and the selection
|
|
492
|
+
// checkbox name so "which row is this control for" reads the same
|
|
493
|
+
// everywhere.
|
|
494
|
+
const deriveRowIdentity = useCallback(
|
|
495
|
+
(row: any): string | undefined => {
|
|
496
|
+
const fromAccessor = rowLabel?.(row.original as TData);
|
|
497
|
+
if (fromAccessor) return fromAccessor;
|
|
498
|
+
let firstCellValue: unknown;
|
|
499
|
+
try {
|
|
500
|
+
firstCellValue = row.getVisibleCells()[0]?.getValue();
|
|
501
|
+
} catch {
|
|
502
|
+
firstCellValue = undefined;
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
(typeof firstCellValue === "string" && firstCellValue.length > 0) ||
|
|
506
|
+
typeof firstCellValue === "number"
|
|
507
|
+
) {
|
|
508
|
+
return String(firstCellValue);
|
|
509
|
+
}
|
|
510
|
+
return undefined;
|
|
511
|
+
},
|
|
512
|
+
[rowLabel],
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// Accessible name per pressable row: identity, else positional "Row {{row}}".
|
|
516
|
+
const deriveRowLabel = useCallback(
|
|
517
|
+
(row: any, rowIndex: number): string =>
|
|
518
|
+
deriveRowIdentity(row) ??
|
|
519
|
+
withInterp(t("Row {{row}}", { row: rowIndex + 1 }), { row: rowIndex + 1 }),
|
|
520
|
+
[deriveRowIdentity, t],
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// Selection-toggle name: row identity beats a bare index ("Select row
|
|
524
|
+
// Bulbasaur", falling back to "Select row 3") so AT and automation can
|
|
525
|
+
// target a specific row's checkbox by name.
|
|
526
|
+
const deriveSelectRowLabel = useCallback(
|
|
527
|
+
(row: any, rowIndex: number): string => {
|
|
528
|
+
const identity = deriveRowIdentity(row) ?? String(rowIndex + 1);
|
|
529
|
+
return withInterp(t("Select row {{row}}", { row: identity }), { row: identity });
|
|
530
|
+
},
|
|
531
|
+
[deriveRowIdentity, t],
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
const activateRowPress = useCallback(
|
|
535
|
+
(row: any, rowIndex: number) => {
|
|
536
|
+
onRowPress?.(row.original as TData, rowIndex);
|
|
537
|
+
},
|
|
538
|
+
[onRowPress],
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Press/keyboard/AT props for one pressable row (grid `<tr>`, virtualized
|
|
543
|
+
* row host, or mobile card). Spread LAST so the roving tabIndex overrides
|
|
544
|
+
* the static -1 the grid row carries otherwise.
|
|
545
|
+
*
|
|
546
|
+
* - Pointer: activation skips presses originating on inner interactive
|
|
547
|
+
* elements (isInteractiveInnerTarget — the stopPropagation contract's
|
|
548
|
+
* defensive arm).
|
|
549
|
+
* - Keyboard (LC-50): Enter AND Space each synthesize exactly one
|
|
550
|
+
* activation (repeat + defaultPrevented guarded); arrows/Home/End rove
|
|
551
|
+
* within the row set; Shift+Tab exits (no key trap).
|
|
552
|
+
*/
|
|
553
|
+
const getRowPressProps = useCallback(
|
|
554
|
+
(row: any, rowIndex: number, variant: "row" | "card" = "row") => {
|
|
555
|
+
if (!rowPressActive) return {};
|
|
556
|
+
const handlePress = (event: any) => {
|
|
557
|
+
if (event?.defaultPrevented) return;
|
|
558
|
+
const targetEl = event?.target ?? event?.nativeEvent?.target;
|
|
559
|
+
if (isWeb && typeof Element !== "undefined" && targetEl instanceof Element) {
|
|
560
|
+
const boundary = targetEl.closest('[data-row-press="true"]');
|
|
561
|
+
if (isInteractiveInnerTarget(targetEl, boundary)) return;
|
|
562
|
+
}
|
|
563
|
+
activateRowPress(row, rowIndex);
|
|
564
|
+
};
|
|
565
|
+
return {
|
|
566
|
+
"aria-label": deriveRowLabel(row, rowIndex),
|
|
567
|
+
onPress: handlePress,
|
|
568
|
+
cursor: "pointer" as const,
|
|
569
|
+
pressStyle: { backgroundColor: "$color4" },
|
|
570
|
+
...(variant === "card" ? { role: "button" as const } : {}),
|
|
571
|
+
...(isWeb && !isSSR
|
|
572
|
+
? {
|
|
573
|
+
tabIndex: rowIndex === effectiveRowPressAnchor ? 0 : -1,
|
|
574
|
+
focusVisibleStyle: ensureFocusVisibleRing({ outlineOffset: -2 }),
|
|
575
|
+
"data-row-press": "true",
|
|
576
|
+
onFocus: (e: React.FocusEvent) => {
|
|
577
|
+
// Only a focused ROW moves the anchor; focus on an inner
|
|
578
|
+
// control (own tab stop) leaves the roving state alone.
|
|
579
|
+
if (e.target === e.currentTarget) setRowPressAnchor(rowIndex);
|
|
580
|
+
},
|
|
581
|
+
onBlur: (e: React.FocusEvent) => {
|
|
582
|
+
const next = e.relatedTarget;
|
|
583
|
+
if (!(next instanceof Element) || !next.closest('[data-row-press="true"]')) {
|
|
584
|
+
setRowPressAnchor(0);
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
588
|
+
if (e.defaultPrevented) return;
|
|
589
|
+
// Keys on focused inner controls belong to those controls.
|
|
590
|
+
if (e.target !== e.currentTarget) return;
|
|
591
|
+
const key = e.key;
|
|
592
|
+
if (key === "Enter" || key === " " || key === "Spacebar") {
|
|
593
|
+
// preventDefault stops Space page-scroll; stopPropagation
|
|
594
|
+
// keeps the container-level keyboard nav (Excel mode) from
|
|
595
|
+
// double-activating the same physical key.
|
|
596
|
+
e.preventDefault();
|
|
597
|
+
e.stopPropagation();
|
|
598
|
+
if (e.repeat) return;
|
|
599
|
+
activateRowPress(row, rowIndex);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End") {
|
|
603
|
+
e.preventDefault();
|
|
604
|
+
e.stopPropagation();
|
|
605
|
+
const rowEl = e.currentTarget as HTMLElement;
|
|
606
|
+
const scope =
|
|
607
|
+
rowEl.closest('[data-row-press-scope="true"]') ?? rowEl.ownerDocument;
|
|
608
|
+
const pressRows = Array.from(
|
|
609
|
+
scope.querySelectorAll('[data-row-press="true"]'),
|
|
610
|
+
) as HTMLElement[];
|
|
611
|
+
const currentIdx = pressRows.indexOf(rowEl);
|
|
612
|
+
if (currentIdx < 0) return;
|
|
613
|
+
const nextIdx =
|
|
614
|
+
key === "Home"
|
|
615
|
+
? 0
|
|
616
|
+
: key === "End"
|
|
617
|
+
? pressRows.length - 1
|
|
618
|
+
: Math.min(
|
|
619
|
+
Math.max(currentIdx + (key === "ArrowDown" ? 1 : -1), 0),
|
|
620
|
+
pressRows.length - 1,
|
|
621
|
+
);
|
|
622
|
+
pressRows[nextIdx]?.focus();
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
}
|
|
626
|
+
: {}),
|
|
627
|
+
};
|
|
628
|
+
},
|
|
629
|
+
[rowPressActive, deriveRowLabel, activateRowPress, effectiveRowPressAnchor, isSSR],
|
|
630
|
+
);
|
|
631
|
+
|
|
476
632
|
const handleCellChange = useCallback(
|
|
477
633
|
async (rowIndex: number, columnId: string, newValue: any) => {
|
|
478
634
|
if (readOnly || disabled) return;
|
|
@@ -519,7 +675,28 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
519
675
|
const value = cell.getValue();
|
|
520
676
|
|
|
521
677
|
if (columnDef.cell && !columnDef.field) {
|
|
522
|
-
|
|
678
|
+
// DG-TBL-01 for CUSTOM cell renderers: the cell box's own
|
|
679
|
+
// justifyContent no-ops in the semantic-table displayMode
|
|
680
|
+
// (display: table-cell is not a flex container), so the column's
|
|
681
|
+
// resolved alignment rides this full-width flex container instead —
|
|
682
|
+
// consumers never self-align. The built-in DataTableCell branch
|
|
683
|
+
// below aligns internally (CellWrapper) and stays unwrapped.
|
|
684
|
+
const customAlign = resolveColumnAlign({
|
|
685
|
+
...(columnDef as DataTableColumn),
|
|
686
|
+
id: cell.column.id,
|
|
687
|
+
});
|
|
688
|
+
return (
|
|
689
|
+
<View
|
|
690
|
+
width="100%"
|
|
691
|
+
minWidth={0}
|
|
692
|
+
flexDirection="row"
|
|
693
|
+
alignItems="center"
|
|
694
|
+
justifyContent={alignToFlex(customAlign)}
|
|
695
|
+
{...({ "data-cell-align": customAlign } as any)}
|
|
696
|
+
>
|
|
697
|
+
{flexRender(columnDef.cell, cell.getContext())}
|
|
698
|
+
</View>
|
|
699
|
+
);
|
|
523
700
|
}
|
|
524
701
|
|
|
525
702
|
const columnField = getColumnField(columnDef);
|
|
@@ -556,8 +733,6 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
556
733
|
[handleCellChange, readOnly, disabled, knobProps.sizeToken, isCompact],
|
|
557
734
|
);
|
|
558
735
|
|
|
559
|
-
const isSSR = typeof window === "undefined";
|
|
560
|
-
|
|
561
736
|
const extraColumnsBefore =
|
|
562
737
|
(enableRowSelection ? 1 : 0) + (showRowNumbers ? 1 : 0) + (enableRowExpansion ? 1 : 0);
|
|
563
738
|
const extraColumnsAfter = showDeleteRow && !readOnly ? 1 : 0;
|
|
@@ -651,10 +826,9 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
651
826
|
disabled={disabled}
|
|
652
827
|
checkboxProps={{
|
|
653
828
|
// Row identity in the name so repeated per-row actions are
|
|
654
|
-
// differentiated for screen readers (DG-A11Y-06).
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}),
|
|
829
|
+
// differentiated for screen readers (DG-A11Y-06). The
|
|
830
|
+
// aria-label rides the role="checkbox" frame itself.
|
|
831
|
+
"aria-label": deriveSelectRowLabel(row, rowIndex),
|
|
658
832
|
}}
|
|
659
833
|
compact={isCompact}
|
|
660
834
|
/>
|
|
@@ -840,6 +1014,7 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
840
1014
|
minRows,
|
|
841
1015
|
isCompact,
|
|
842
1016
|
t,
|
|
1017
|
+
deriveSelectRowLabel,
|
|
843
1018
|
ExpandCaretIcon,
|
|
844
1019
|
expandedCaretRotation,
|
|
845
1020
|
clipBodyCells,
|
|
@@ -1084,6 +1259,8 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
1084
1259
|
selectAllIndeterminate={selectAllIndeterminate}
|
|
1085
1260
|
onToggleSelectAll={toggleSelectAll}
|
|
1086
1261
|
compact={isCompact}
|
|
1262
|
+
selectRowLabel={deriveSelectRowLabel}
|
|
1263
|
+
{...(rowPressActive ? { getRowPressProps } : {})}
|
|
1087
1264
|
/>
|
|
1088
1265
|
</>
|
|
1089
1266
|
) : (
|
|
@@ -1099,7 +1276,13 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
1099
1276
|
}}
|
|
1100
1277
|
flexDirection="column"
|
|
1101
1278
|
width="100%"
|
|
1102
|
-
|
|
1279
|
+
// Grow 0 / shrink 1 / basis auto: the grid frame hugs its
|
|
1280
|
+
// rows and only compresses (into internal scroll) when the
|
|
1281
|
+
// parent is shorter. flexGrow 1 stretched the frame to fill
|
|
1282
|
+
// tall parents, painting a dead zone between the last row
|
|
1283
|
+
// and the frame's bottom border on short pages (the frame
|
|
1284
|
+
// claimed data area that held no data — Axiom 6/LC-37).
|
|
1285
|
+
flexGrow={0}
|
|
1103
1286
|
flexShrink={1}
|
|
1104
1287
|
flexBasis="auto"
|
|
1105
1288
|
minHeight={0}
|
|
@@ -1144,6 +1327,8 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
1144
1327
|
// to it instead of the scrolling content View. Content
|
|
1145
1328
|
// fits the max-content box, so nothing needs the clip.
|
|
1146
1329
|
{...(hScroll && enableColumnPinning ? { overflow: "visible" as any } : {})}
|
|
1330
|
+
// Roving-focus DOM scope for onRowPress arrow keys (LC-49).
|
|
1331
|
+
{...(rowPressActive ? ({ "data-row-press-scope": "true" } as any) : {})}
|
|
1147
1332
|
>
|
|
1148
1333
|
{/* Table Header */}
|
|
1149
1334
|
<Table.Head zIndex="$1" aria-label={t("Table headers")}>
|
|
@@ -1553,6 +1738,7 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
1553
1738
|
extraColumnsAfter={extraColumnsAfter}
|
|
1554
1739
|
table={table}
|
|
1555
1740
|
hasInfiniteTail={hasInfiniteTail}
|
|
1741
|
+
{...(rowPressActive ? { getRowPressProps } : {})}
|
|
1556
1742
|
/>
|
|
1557
1743
|
</Table.Cell>
|
|
1558
1744
|
</Table.Row>
|
|
@@ -1646,6 +1832,7 @@ export function DataTableRoot<TData extends Record<string, any> = Record<string,
|
|
|
1646
1832
|
: "odd"
|
|
1647
1833
|
: "off",
|
|
1648
1834
|
} as any)}
|
|
1835
|
+
{...(getRowPressProps(row, index) as any)}
|
|
1649
1836
|
>
|
|
1650
1837
|
{renderRowContent(row, index, isLastRow)}
|
|
1651
1838
|
</Table.Row>
|
|
@@ -42,6 +42,12 @@ export interface DataTableVirtualizedProps {
|
|
|
42
42
|
* scroll range so appended pages grow geometry seamlessly.
|
|
43
43
|
*/
|
|
44
44
|
hasInfiniteTail?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* onRowPress support: press/keyboard/AT props for one pressable row
|
|
47
|
+
* (built by DataTableRoot; spread on the row host so virtual rows keep
|
|
48
|
+
* the same activation + roving-focus contract as grid rows).
|
|
49
|
+
*/
|
|
50
|
+
getRowPressProps?: (row: any, rowIndex: number, variant?: "row" | "card") => Record<string, any>;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export function DataTableVirtualized({
|
|
@@ -60,6 +66,7 @@ export function DataTableVirtualized({
|
|
|
60
66
|
getColSizeProps,
|
|
61
67
|
renderRowContent,
|
|
62
68
|
hasInfiniteTail = false,
|
|
69
|
+
getRowPressProps,
|
|
63
70
|
}: DataTableVirtualizedProps) {
|
|
64
71
|
return (
|
|
65
72
|
<div
|
|
@@ -188,6 +195,7 @@ export function DataTableVirtualized({
|
|
|
188
195
|
}
|
|
189
196
|
role="row"
|
|
190
197
|
aria-rowindex={virtualRow.index + 1}
|
|
198
|
+
{...(getRowPressProps ? (getRowPressProps(row, virtualRow.index) as any) : {})}
|
|
191
199
|
>
|
|
192
200
|
{/* div cells (role="cell" via the primitive): a real <td> is
|
|
193
201
|
not a valid child of the absolutely-positioned div rows */}
|
|
@@ -213,7 +213,10 @@ describe("DataTable card layout", () => {
|
|
|
213
213
|
onStateChange={vi.fn()}
|
|
214
214
|
/>,
|
|
215
215
|
);
|
|
216
|
-
|
|
216
|
+
// Selection toggles are named by ROW IDENTITY (first cell value /
|
|
217
|
+
// rowLabel), not a bare position — "Select row Alice", not "Select
|
|
218
|
+
// row 1" (DG-A11Y-06; selectionSemantics.spec pins the derivation).
|
|
219
|
+
expect(getAllByLabelText(/^Select row .+$/).length).toBe(sampleData.length);
|
|
217
220
|
expect(container.querySelector('[data-testid="data-table-cards"]')).not.toBeNull();
|
|
218
221
|
});
|
|
219
222
|
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom-cell alignment (DG-TBL-01 / LC-44 ROW-LINE).
|
|
3
|
+
*
|
|
4
|
+
* Custom `cell` renderers historically LOST the column's `align` on web:
|
|
5
|
+
* in the semantic-table displayMode (columnLayout="auto") `Table.Cell` is
|
|
6
|
+
* `display: table-cell`, where the cell box's own flex `justifyContent` is
|
|
7
|
+
* a no-op — only the built-in DataTableCell self-aligned, and every custom
|
|
8
|
+
* renderer had to hand-roll its own alignment wrapper (the InsightsRowLink
|
|
9
|
+
* class of bug). The fix: DataTable wraps flexRender output in a
|
|
10
|
+
* full-width flex container driven by the resolved column align
|
|
11
|
+
* (`data-cell-align`), in every displayMode, so custom cells align like
|
|
12
|
+
* built-ins with zero consumer code.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
16
|
+
import { screen } from "@testing-library/react";
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
import { Text } from "tamagui";
|
|
19
|
+
import type { DataTableColumn } from "../../types";
|
|
20
|
+
import { DataTable } from "./index";
|
|
21
|
+
|
|
22
|
+
type Pokemon = {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
xp: number;
|
|
26
|
+
active: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const sampleData: Pokemon[] = [
|
|
30
|
+
{ id: 1, name: "Bulbasaur", xp: 64, active: true },
|
|
31
|
+
{ id: 2, name: "Charmander", xp: 62, active: false },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const customColumns: DataTableColumn<Pokemon>[] = [
|
|
35
|
+
{
|
|
36
|
+
id: "name",
|
|
37
|
+
accessorKey: "name",
|
|
38
|
+
header: "Name",
|
|
39
|
+
cell: ({ getValue }) => <Text>{String(getValue())}</Text>,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "xp",
|
|
43
|
+
accessorKey: "xp",
|
|
44
|
+
header: "XP",
|
|
45
|
+
align: "right",
|
|
46
|
+
cell: ({ getValue }) => <Text>{String(getValue())}</Text>,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "active",
|
|
50
|
+
accessorKey: "active",
|
|
51
|
+
header: "Active",
|
|
52
|
+
align: "center",
|
|
53
|
+
cell: ({ getValue }) => <Text>{getValue() ? "yes" : "no"}</Text>,
|
|
54
|
+
},
|
|
55
|
+
// Built-in field path (no custom cell) — must NOT grow a wrapper.
|
|
56
|
+
// (accessor `id`, not `xp`: the custom XP column also paints its value,
|
|
57
|
+
// and duplicated cell text breaks the single-match queries below.)
|
|
58
|
+
{ id: "plain_id", accessorKey: "id", header: "Plain ID", field: "stepper" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function alignContainerOf(el: HTMLElement): HTMLElement | null {
|
|
62
|
+
return el.closest("[data-cell-align]") as HTMLElement | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderTable(columnLayout: "auto" | "equal") {
|
|
66
|
+
return renderWithProviders(
|
|
67
|
+
<DataTable
|
|
68
|
+
data={sampleData}
|
|
69
|
+
columns={customColumns as DataTableColumn<Record<string, any>>[]}
|
|
70
|
+
layout="table"
|
|
71
|
+
columnLayout={columnLayout}
|
|
72
|
+
enablePagination={false}
|
|
73
|
+
readOnly
|
|
74
|
+
/>,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe.each(["auto", "equal"] as const)(
|
|
79
|
+
"DataTable custom-cell alignment (columnLayout=%s)",
|
|
80
|
+
(columnLayout) => {
|
|
81
|
+
it("wraps custom cells in an alignment container driven by the column align", () => {
|
|
82
|
+
renderTable(columnLayout);
|
|
83
|
+
|
|
84
|
+
const nameContainer = alignContainerOf(screen.getByText("Bulbasaur"));
|
|
85
|
+
expect(nameContainer).not.toBeNull();
|
|
86
|
+
expect(nameContainer!.getAttribute("data-cell-align")).toBe("left");
|
|
87
|
+
|
|
88
|
+
// The wrapper is a full-width flex row whose justifyContent follows
|
|
89
|
+
// the resolved align (this is what display:table-cell's dead
|
|
90
|
+
// justifyContent could not do). happy-dom does not resolve tamagui's
|
|
91
|
+
// class-based styles, so computed flex geometry (display:flex,
|
|
92
|
+
// justify-content, shared ink edges ±1px) is pinned in the live
|
|
93
|
+
// browser probe `p-results/table-rowpress/probe.mjs`; here the
|
|
94
|
+
// structural contract is the container + its declared alignment.
|
|
95
|
+
const xpContainer = alignContainerOf(screen.getByText("64"));
|
|
96
|
+
expect(xpContainer).not.toBeNull();
|
|
97
|
+
expect(xpContainer!.getAttribute("data-cell-align")).toBe("right");
|
|
98
|
+
|
|
99
|
+
const centerContainer = alignContainerOf(screen.getAllByText("yes")[0]);
|
|
100
|
+
expect(centerContainer).not.toBeNull();
|
|
101
|
+
expect(centerContainer!.getAttribute("data-cell-align")).toBe("center");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("headers carry the same resolved alignment as their cells", () => {
|
|
105
|
+
const { container } = renderTable(columnLayout);
|
|
106
|
+
const xpHeader = container.querySelector("#xp-header");
|
|
107
|
+
expect(xpHeader?.getAttribute("data-align")).toBe("right");
|
|
108
|
+
const nameHeader = container.querySelector("#name-header");
|
|
109
|
+
expect(nameHeader?.getAttribute("data-align")).toBe("left");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("built-in field cells stay unwrapped (they self-align internally)", () => {
|
|
113
|
+
const { container } = renderTable(columnLayout);
|
|
114
|
+
// Body cells for the plain field column contain no alignment wrapper.
|
|
115
|
+
const bodyCells = Array.from(
|
|
116
|
+
container.querySelectorAll('[data-align="right"]'),
|
|
117
|
+
) as HTMLElement[];
|
|
118
|
+
const plainFieldCells = bodyCells.filter(
|
|
119
|
+
(cell) => cell.id === "" && !cell.querySelector("[data-cell-align]"),
|
|
120
|
+
);
|
|
121
|
+
expect(plainFieldCells.length).toBeGreaterThan(0);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
);
|
|
@@ -156,7 +156,8 @@ describe("DataTable disabled contract (grid layout)", () => {
|
|
|
156
156
|
onStateChange={vi.fn()}
|
|
157
157
|
/>,
|
|
158
158
|
);
|
|
159
|
-
|
|
159
|
+
// Selection toggles are named by row identity (first cell value).
|
|
160
|
+
const rowCheckbox = getAllByLabelText(/^Select row John$/)[0];
|
|
160
161
|
fireEvent.click(rowCheckbox);
|
|
161
162
|
await waitFor(() => {
|
|
162
163
|
expect(container.querySelectorAll('[aria-checked="true"]').length).toBe(0);
|
|
@@ -233,8 +234,8 @@ describe("DataTable disabled contract (card layout)", () => {
|
|
|
233
234
|
expect(onDataChange).not.toHaveBeenCalled();
|
|
234
235
|
});
|
|
235
236
|
|
|
236
|
-
// Selection does not toggle.
|
|
237
|
-
fireEvent.click(getAllByLabelText(/^Select row
|
|
237
|
+
// Selection does not toggle. (Toggles are named by row identity.)
|
|
238
|
+
fireEvent.click(getAllByLabelText(/^Select row John$/)[0]);
|
|
238
239
|
await waitFor(() => {
|
|
239
240
|
expect(container.querySelectorAll('[aria-checked="true"]').length).toBe(0);
|
|
240
241
|
});
|
|
@@ -134,6 +134,8 @@ function DataTableRoot<TData extends Record<string, any> = Record<string, any>>(
|
|
|
134
134
|
enableColumnPinning = false,
|
|
135
135
|
maxPinnedColumns = 3,
|
|
136
136
|
enableKeyboardNavigation: _enableKeyboardNavigation = false,
|
|
137
|
+
onRowPress,
|
|
138
|
+
rowLabel,
|
|
137
139
|
readOnly = false,
|
|
138
140
|
onCellChange,
|
|
139
141
|
name,
|
|
@@ -456,6 +458,47 @@ function DataTableRoot<TData extends Record<string, any> = Record<string, any>>(
|
|
|
456
458
|
|
|
457
459
|
const tableZebraOn = knobProps.tableZebra === "on";
|
|
458
460
|
|
|
461
|
+
// ---- Row press (onRowPress): the ROW is one pressable unit ----
|
|
462
|
+
// Native twin of the web contract: press anywhere on the row activates.
|
|
463
|
+
// Inner controls need no isolation walk here — native nested pressables
|
|
464
|
+
// claim the gesture responder, so a child press never bubbles to the row
|
|
465
|
+
// (see utils/rowPress.ts). Roving tabindex is a web-only concern.
|
|
466
|
+
const rowPressActive = Boolean(onRowPress) && !disabled;
|
|
467
|
+
|
|
468
|
+
const deriveRowLabel = useCallback(
|
|
469
|
+
(row: (typeof tableRows)[0], index: number): string => {
|
|
470
|
+
const fromAccessor = rowLabel?.(row.original as TData);
|
|
471
|
+
if (fromAccessor) return fromAccessor;
|
|
472
|
+
let firstCellValue: unknown;
|
|
473
|
+
try {
|
|
474
|
+
firstCellValue = row.getVisibleCells()[0]?.getValue();
|
|
475
|
+
} catch {
|
|
476
|
+
firstCellValue = undefined;
|
|
477
|
+
}
|
|
478
|
+
if (
|
|
479
|
+
(typeof firstCellValue === "string" && firstCellValue.length > 0) ||
|
|
480
|
+
typeof firstCellValue === "number"
|
|
481
|
+
) {
|
|
482
|
+
return String(firstCellValue);
|
|
483
|
+
}
|
|
484
|
+
return withInterp(t("Row {{row}}", { row: index + 1 }), { row: index + 1 });
|
|
485
|
+
},
|
|
486
|
+
[rowLabel, t],
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const getRowPressProps = useCallback(
|
|
490
|
+
(row: (typeof tableRows)[0], index: number) => {
|
|
491
|
+
if (!rowPressActive) return {};
|
|
492
|
+
return {
|
|
493
|
+
onPress: () => onRowPress?.(row.original as TData, index),
|
|
494
|
+
role: "button" as const,
|
|
495
|
+
"aria-label": deriveRowLabel(row, index),
|
|
496
|
+
pressStyle: { backgroundColor: "$color4" },
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
[rowPressActive, onRowPress, deriveRowLabel],
|
|
500
|
+
);
|
|
501
|
+
|
|
459
502
|
const renderRow = useCallback(
|
|
460
503
|
(row: (typeof tableRows)[0], index: number) => (
|
|
461
504
|
<Table.Row
|
|
@@ -467,11 +510,12 @@ function DataTableRoot<TData extends Record<string, any> = Record<string, any>>(
|
|
|
467
510
|
{...({
|
|
468
511
|
"data-table-zebra": tableZebraOn ? (index % 2 === 0 ? "even" : "odd") : "off",
|
|
469
512
|
} as any)}
|
|
513
|
+
{...(getRowPressProps(row, index) as any)}
|
|
470
514
|
>
|
|
471
515
|
{renderRowContent(row, index)}
|
|
472
516
|
</Table.Row>
|
|
473
517
|
),
|
|
474
|
-
[renderRowContent, tableZebraOn, t],
|
|
518
|
+
[renderRowContent, tableZebraOn, t, getRowPressProps],
|
|
475
519
|
);
|
|
476
520
|
|
|
477
521
|
const renderMobileCard = useCallback(
|
|
@@ -487,6 +531,7 @@ function DataTableRoot<TData extends Record<string, any> = Record<string, any>>(
|
|
|
487
531
|
borderColor="$borderColor"
|
|
488
532
|
padding="$3"
|
|
489
533
|
marginBottom="$2"
|
|
534
|
+
{...(getRowPressProps(row, index) as any)}
|
|
490
535
|
>
|
|
491
536
|
{showRowNumbers && (
|
|
492
537
|
<View
|
|
@@ -563,6 +608,7 @@ function DataTableRoot<TData extends Record<string, any> = Record<string, any>>(
|
|
|
563
608
|
minRows,
|
|
564
609
|
renderCellContent,
|
|
565
610
|
t,
|
|
611
|
+
getRowPressProps,
|
|
566
612
|
],
|
|
567
613
|
);
|
|
568
614
|
|