@patternfly/react-data-view 6.3.0 → 6.4.0-prerelease.2

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.
Files changed (27) hide show
  1. package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +2 -0
  2. package/dist/cjs/DataViewTableTree/DataViewTableTree.js +28 -1
  3. package/dist/cjs/DataViewTableTree/DataViewTableTree.test.js +4 -0
  4. package/dist/cjs/Hooks/selection.d.ts +1 -0
  5. package/dist/cjs/Hooks/selection.js +5 -1
  6. package/dist/cjs/Hooks/selection.test.js +48 -0
  7. package/dist/cjs/InternalContext/InternalContext.d.ts +2 -0
  8. package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +2 -0
  9. package/dist/esm/DataViewTableTree/DataViewTableTree.js +29 -2
  10. package/dist/esm/DataViewTableTree/DataViewTableTree.test.js +4 -0
  11. package/dist/esm/Hooks/selection.d.ts +1 -0
  12. package/dist/esm/Hooks/selection.js +5 -1
  13. package/dist/esm/Hooks/selection.test.js +48 -0
  14. package/dist/esm/InternalContext/InternalContext.d.ts +2 -0
  15. package/dist/tsconfig.tsbuildinfo +1 -1
  16. package/package.json +2 -2
  17. package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableTreeExample.tsx +1 -0
  18. package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +2 -0
  19. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/SelectionExample.tsx +14 -3
  20. package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +1 -0
  21. package/release.config.js +1 -1
  22. package/src/DataViewTableTree/DataViewTableTree.test.tsx +9 -0
  23. package/src/DataViewTableTree/DataViewTableTree.tsx +35 -1
  24. package/src/DataViewTableTree/__snapshots__/DataViewTableTree.test.tsx.snap +965 -0
  25. package/src/Hooks/selection.test.tsx +65 -1
  26. package/src/Hooks/selection.ts +6 -1
  27. package/src/InternalContext/InternalContext.tsx +2 -0
@@ -18,6 +18,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
18
18
  expandedIcon?: React.ReactNode;
19
19
  /** Optional icon for the collapsed parent rows */
20
20
  collapsedIcon?: React.ReactNode;
21
+ /** Expand all expandable nodes on initial load */
22
+ expandAll?: boolean;
21
23
  /** Custom OUIA ID */
22
24
  ouiaId?: string;
23
25
  }
@@ -45,11 +45,38 @@ const getNodesAffectedBySelection = (allRows, node, isChecking, isSelected) => {
45
45
  return Array.from(affectedNodes);
46
46
  };
47
47
  const DataViewTableTree = (_a) => {
48
- var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "ouiaId"]);
48
+ var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, expandAll = false, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "expandAll", "ouiaId"]);
49
49
  const { selection, activeState } = (0, InternalContext_1.useInternalContext)();
50
50
  const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
51
51
  const [expandedNodeIds, setExpandedNodeIds] = (0, react_1.useState)([]);
52
52
  const [expandedDetailsNodeNames, setExpandedDetailsNodeIds] = (0, react_1.useState)([]);
53
+ // Helper function to collect all node IDs that have children (are expandable)
54
+ const getExpandableNodeIds = (nodes) => {
55
+ const expandableIds = [];
56
+ const traverse = (nodeList) => {
57
+ nodeList.forEach(node => {
58
+ if (node.children && node.children.length > 0) {
59
+ expandableIds.push(node.id);
60
+ traverse(node.children);
61
+ }
62
+ });
63
+ };
64
+ traverse(nodes);
65
+ return expandableIds;
66
+ };
67
+ // Effect to handle expandAll behavior
68
+ // Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
69
+ const expandableIds = (0, react_1.useMemo)(() => getExpandableNodeIds(rows), [rows]);
70
+ // Effect to handle expandAll behavior - only runs when IDs actually change
71
+ (0, react_1.useEffect)(() => {
72
+ if (expandAll) {
73
+ setExpandedNodeIds(expandableIds);
74
+ }
75
+ else {
76
+ setExpandedNodeIds([]);
77
+ }
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, [expandAll, expandableIds.join(',')]);
53
80
  const activeHeadState = (0, react_1.useMemo)(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
54
81
  const activeBodyState = (0, react_1.useMemo)(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
55
82
  const nodes = (0, react_1.useMemo)(() => {
@@ -76,6 +76,10 @@ describe('DataViewTableTree component', () => {
76
76
  const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { activeState: "error", children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { error: "Some error" }, rows: [] }) }));
77
77
  expect(container).toMatchSnapshot();
78
78
  });
79
+ test('should render tree table with all expandable nodes expanded', () => {
80
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { selection: mockSelection, children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, expandAll: true, rows: rows, leafIcon: (0, jsx_runtime_1.jsx)(react_icons_1.LeafIcon, {}), expandedIcon: (0, jsx_runtime_1.jsx)(react_icons_1.FolderOpenIcon, { "aria-hidden": true }), collapsedIcon: (0, jsx_runtime_1.jsx)(react_icons_1.FolderIcon, { "aria-hidden": true }) }) }));
81
+ expect(container).toMatchSnapshot();
82
+ });
79
83
  test('should render tree table with a loading state', () => {
80
84
  const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { activeState: "loading", children: (0, jsx_runtime_1.jsx)(DataViewTable_1.DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
81
85
  expect(container).toMatchSnapshot();
@@ -8,4 +8,5 @@ export declare const useDataViewSelection: (props?: UseDataViewSelectionProps) =
8
8
  selected: any[];
9
9
  onSelect: (isSelecting: boolean, items?: any[] | any) => void;
10
10
  isSelected: (item: any) => boolean;
11
+ setSelected: (items: any[]) => void;
11
12
  };
@@ -17,10 +17,14 @@ const useDataViewSelection = (props) => {
17
17
  : setSelected(items ? prev => prev.filter(prevSelected => !(Array.isArray(items) ? items : [items]).some(item => matchOption(item, prevSelected))) : []);
18
18
  };
19
19
  const isSelected = (item) => Boolean(selected.find(selected => matchOption(selected, item)));
20
+ const setSelectedItems = (items) => {
21
+ setSelected(items);
22
+ };
20
23
  return {
21
24
  selected,
22
25
  onSelect,
23
- isSelected
26
+ isSelected,
27
+ setSelected: setSelectedItems
24
28
  };
25
29
  };
26
30
  exports.useDataViewSelection = useDataViewSelection;
@@ -19,6 +19,7 @@ describe('useDataViewSelection', () => {
19
19
  selected: [],
20
20
  onSelect: expect.any(Function),
21
21
  isSelected: expect.any(Function),
22
+ setSelected: expect.any(Function),
22
23
  });
23
24
  });
24
25
  it('should get initial state correctly - with initialSelected', () => {
@@ -28,6 +29,7 @@ describe('useDataViewSelection', () => {
28
29
  selected: initialSelected,
29
30
  onSelect: expect.any(Function),
30
31
  isSelected: expect.any(Function),
32
+ setSelected: expect.any(Function),
31
33
  });
32
34
  });
33
35
  it('should select items correctly - objects', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -52,4 +54,50 @@ describe('useDataViewSelection', () => {
52
54
  expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
53
55
  expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
54
56
  });
57
+ it('should have setSelected function in return object', () => {
58
+ const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ matchOption: (a, b) => a.id === b.id }));
59
+ expect(result.current).toEqual({
60
+ selected: [],
61
+ onSelect: expect.any(Function),
62
+ isSelected: expect.any(Function),
63
+ setSelected: expect.any(Function),
64
+ });
65
+ });
66
+ it('should set selected items directly using setSelected - objects', () => __awaiter(void 0, void 0, void 0, function* () {
67
+ const initialSelected = [{ id: 1, name: 'test1' }];
68
+ const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
69
+ const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
70
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
71
+ result.current.setSelected(newSelected);
72
+ }));
73
+ expect(result.current.selected).toEqual(newSelected);
74
+ }));
75
+ it('should set selected items directly using setSelected - strings', () => __awaiter(void 0, void 0, void 0, function* () {
76
+ const initialSelected = ['test1', 'test2'];
77
+ const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a === b }));
78
+ const newSelected = ['test3', 'test4', 'test5'];
79
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
80
+ result.current.setSelected(newSelected);
81
+ }));
82
+ expect(result.current.selected).toEqual(newSelected);
83
+ }));
84
+ it('should clear all selections using setSelected with empty array', () => __awaiter(void 0, void 0, void 0, function* () {
85
+ const initialSelected = [{ id: 1, name: 'test1' }, { id: 2, name: 'test2' }];
86
+ const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
87
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
88
+ result.current.setSelected([]);
89
+ }));
90
+ expect(result.current.selected).toEqual([]);
91
+ }));
92
+ it('should update isSelected correctly after using setSelected', () => __awaiter(void 0, void 0, void 0, function* () {
93
+ const initialSelected = [{ id: 1, name: 'test1' }];
94
+ const { result } = (0, react_1.renderHook)(() => (0, selection_1.useDataViewSelection)({ initialSelected, matchOption: (a, b) => a.id === b.id }));
95
+ const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
96
+ yield (0, react_1.act)(() => __awaiter(void 0, void 0, void 0, function* () {
97
+ result.current.setSelected(newSelected);
98
+ }));
99
+ expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
100
+ expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
101
+ expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
102
+ }));
55
103
  });
@@ -5,6 +5,8 @@ export interface DataViewSelection {
5
5
  onSelect: (isSelecting: boolean, items?: any[] | any) => void;
6
6
  /** Checks if a specific item is currently selected */
7
7
  isSelected: (item: any) => boolean;
8
+ /** Directly sets the selected items */
9
+ setSelected?: (items: any[]) => void;
8
10
  /** Determines if selection is disabled for a given item */
9
11
  isSelectDisabled?: (item: any) => boolean;
10
12
  }
@@ -18,6 +18,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
18
18
  expandedIcon?: React.ReactNode;
19
19
  /** Optional icon for the collapsed parent rows */
20
20
  collapsedIcon?: React.ReactNode;
21
+ /** Expand all expandable nodes on initial load */
22
+ expandAll?: boolean;
21
23
  /** Custom OUIA ID */
22
24
  ouiaId?: string;
23
25
  }
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { useState, useMemo } from 'react';
13
+ import { useState, useMemo, useEffect } from 'react';
14
14
  import { Table, Tbody, Td, TreeRowWrapper, } from '@patternfly/react-table';
15
15
  import { useInternalContext } from '../InternalContext';
16
16
  import { DataViewTableHead } from '../DataViewTableHead';
@@ -42,11 +42,38 @@ const getNodesAffectedBySelection = (allRows, node, isChecking, isSelected) => {
42
42
  return Array.from(affectedNodes);
43
43
  };
44
44
  export const DataViewTableTree = (_a) => {
45
- var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "ouiaId"]);
45
+ var { columns, rows, headStates, bodyStates, leafIcon = null, expandedIcon = null, collapsedIcon = null, expandAll = false, ouiaId = 'DataViewTableTree' } = _a, props = __rest(_a, ["columns", "rows", "headStates", "bodyStates", "leafIcon", "expandedIcon", "collapsedIcon", "expandAll", "ouiaId"]);
46
46
  const { selection, activeState } = useInternalContext();
47
47
  const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
48
48
  const [expandedNodeIds, setExpandedNodeIds] = useState([]);
49
49
  const [expandedDetailsNodeNames, setExpandedDetailsNodeIds] = useState([]);
50
+ // Helper function to collect all node IDs that have children (are expandable)
51
+ const getExpandableNodeIds = (nodes) => {
52
+ const expandableIds = [];
53
+ const traverse = (nodeList) => {
54
+ nodeList.forEach(node => {
55
+ if (node.children && node.children.length > 0) {
56
+ expandableIds.push(node.id);
57
+ traverse(node.children);
58
+ }
59
+ });
60
+ };
61
+ traverse(nodes);
62
+ return expandableIds;
63
+ };
64
+ // Effect to handle expandAll behavior
65
+ // Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
66
+ const expandableIds = useMemo(() => getExpandableNodeIds(rows), [rows]);
67
+ // Effect to handle expandAll behavior - only runs when IDs actually change
68
+ useEffect(() => {
69
+ if (expandAll) {
70
+ setExpandedNodeIds(expandableIds);
71
+ }
72
+ else {
73
+ setExpandedNodeIds([]);
74
+ }
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [expandAll, expandableIds.join(',')]);
50
77
  const activeHeadState = useMemo(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
51
78
  const activeBodyState = useMemo(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
52
79
  const nodes = useMemo(() => {
@@ -74,6 +74,10 @@ describe('DataViewTableTree component', () => {
74
74
  const { container } = render(_jsx(DataView, { activeState: "error", children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { error: "Some error" }, rows: [] }) }));
75
75
  expect(container).toMatchSnapshot();
76
76
  });
77
+ test('should render tree table with all expandable nodes expanded', () => {
78
+ const { container } = render(_jsx(DataView, { selection: mockSelection, children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, expandAll: true, rows: rows, leafIcon: _jsx(LeafIcon, {}), expandedIcon: _jsx(FolderOpenIcon, { "aria-hidden": true }), collapsedIcon: _jsx(FolderIcon, { "aria-hidden": true }) }) }));
79
+ expect(container).toMatchSnapshot();
80
+ });
77
81
  test('should render tree table with a loading state', () => {
78
82
  const { container } = render(_jsx(DataView, { activeState: "loading", children: _jsx(DataViewTable, { isTreeTable: true, "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
79
83
  expect(container).toMatchSnapshot();
@@ -8,4 +8,5 @@ export declare const useDataViewSelection: (props?: UseDataViewSelectionProps) =
8
8
  selected: any[];
9
9
  onSelect: (isSelecting: boolean, items?: any[] | any) => void;
10
10
  isSelected: (item: any) => boolean;
11
+ setSelected: (items: any[]) => void;
11
12
  };
@@ -14,9 +14,13 @@ export const useDataViewSelection = (props) => {
14
14
  : setSelected(items ? prev => prev.filter(prevSelected => !(Array.isArray(items) ? items : [items]).some(item => matchOption(item, prevSelected))) : []);
15
15
  };
16
16
  const isSelected = (item) => Boolean(selected.find(selected => matchOption(selected, item)));
17
+ const setSelectedItems = (items) => {
18
+ setSelected(items);
19
+ };
17
20
  return {
18
21
  selected,
19
22
  onSelect,
20
- isSelected
23
+ isSelected,
24
+ setSelected: setSelectedItems
21
25
  };
22
26
  };
@@ -17,6 +17,7 @@ describe('useDataViewSelection', () => {
17
17
  selected: [],
18
18
  onSelect: expect.any(Function),
19
19
  isSelected: expect.any(Function),
20
+ setSelected: expect.any(Function),
20
21
  });
21
22
  });
22
23
  it('should get initial state correctly - with initialSelected', () => {
@@ -26,6 +27,7 @@ describe('useDataViewSelection', () => {
26
27
  selected: initialSelected,
27
28
  onSelect: expect.any(Function),
28
29
  isSelected: expect.any(Function),
30
+ setSelected: expect.any(Function),
29
31
  });
30
32
  });
31
33
  it('should select items correctly - objects', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -50,4 +52,50 @@ describe('useDataViewSelection', () => {
50
52
  expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
51
53
  expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
52
54
  });
55
+ it('should have setSelected function in return object', () => {
56
+ const { result } = renderHook(() => useDataViewSelection({ matchOption: (a, b) => a.id === b.id }));
57
+ expect(result.current).toEqual({
58
+ selected: [],
59
+ onSelect: expect.any(Function),
60
+ isSelected: expect.any(Function),
61
+ setSelected: expect.any(Function),
62
+ });
63
+ });
64
+ it('should set selected items directly using setSelected - objects', () => __awaiter(void 0, void 0, void 0, function* () {
65
+ const initialSelected = [{ id: 1, name: 'test1' }];
66
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
67
+ const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
68
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
69
+ result.current.setSelected(newSelected);
70
+ }));
71
+ expect(result.current.selected).toEqual(newSelected);
72
+ }));
73
+ it('should set selected items directly using setSelected - strings', () => __awaiter(void 0, void 0, void 0, function* () {
74
+ const initialSelected = ['test1', 'test2'];
75
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a === b }));
76
+ const newSelected = ['test3', 'test4', 'test5'];
77
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
78
+ result.current.setSelected(newSelected);
79
+ }));
80
+ expect(result.current.selected).toEqual(newSelected);
81
+ }));
82
+ it('should clear all selections using setSelected with empty array', () => __awaiter(void 0, void 0, void 0, function* () {
83
+ const initialSelected = [{ id: 1, name: 'test1' }, { id: 2, name: 'test2' }];
84
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
85
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
86
+ result.current.setSelected([]);
87
+ }));
88
+ expect(result.current.selected).toEqual([]);
89
+ }));
90
+ it('should update isSelected correctly after using setSelected', () => __awaiter(void 0, void 0, void 0, function* () {
91
+ const initialSelected = [{ id: 1, name: 'test1' }];
92
+ const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }));
93
+ const newSelected = [{ id: 2, name: 'test2' }, { id: 3, name: 'test3' }];
94
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
95
+ result.current.setSelected(newSelected);
96
+ }));
97
+ expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
98
+ expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
99
+ expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
100
+ }));
53
101
  });
@@ -5,6 +5,8 @@ export interface DataViewSelection {
5
5
  onSelect: (isSelecting: boolean, items?: any[] | any) => void;
6
6
  /** Checks if a specific item is currently selected */
7
7
  isSelected: (item: any) => boolean;
8
+ /** Directly sets the selected items */
9
+ setSelected?: (items: any[]) => void;
8
10
  /** Determines if selection is disabled for a given item */
9
11
  isSelectDisabled?: (item: any) => boolean;
10
12
  }
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/dataview/dataview.test.tsx","../src/dataview/dataview.tsx","../src/dataview/index.ts","../src/dataviewcheckboxfilter/dataviewcheckboxfilter.test.tsx","../src/dataviewcheckboxfilter/dataviewcheckboxfilter.tsx","../src/dataviewcheckboxfilter/index.ts","../src/datavieweventscontext/datavieweventscontext.test.tsx","../src/datavieweventscontext/datavieweventscontext.tsx","../src/datavieweventscontext/index.ts","../src/dataviewfilters/dataviewfilters.test.tsx","../src/dataviewfilters/dataviewfilters.tsx","../src/dataviewfilters/index.tsx","../src/dataviewtable/dataviewtable.test.tsx","../src/dataviewtable/dataviewtable.tsx","../src/dataviewtable/index.ts","../src/dataviewtablebasic/dataviewtablebasic.test.tsx","../src/dataviewtablebasic/dataviewtablebasic.tsx","../src/dataviewtablebasic/index.ts","../src/dataviewtablehead/dataviewtablehead.test.tsx","../src/dataviewtablehead/dataviewtablehead.tsx","../src/dataviewtablehead/index.ts","../src/dataviewtabletree/dataviewtabletree.test.tsx","../src/dataviewtabletree/dataviewtabletree.tsx","../src/dataviewtabletree/index.ts","../src/dataviewtextfilter/dataviewtextfilter.test.tsx","../src/dataviewtextfilter/dataviewtextfilter.tsx","../src/dataviewtextfilter/index.ts","../src/dataviewtoolbar/dataviewtoolbar.test.tsx","../src/dataviewtoolbar/dataviewtoolbar.tsx","../src/dataviewtoolbar/index.ts","../src/hooks/filters.test.tsx","../src/hooks/filters.ts","../src/hooks/index.ts","../src/hooks/pagination.test.tsx","../src/hooks/pagination.ts","../src/hooks/selection.test.tsx","../src/hooks/selection.ts","../src/hooks/sort.test.tsx","../src/hooks/sort.ts","../src/internalcontext/internalcontext.test.tsx","../src/internalcontext/internalcontext.tsx","../src/internalcontext/index.ts"],"version":"5.8.3"}
1
+ {"root":["../src/index.ts","../src/DataView/DataView.test.tsx","../src/DataView/DataView.tsx","../src/DataView/index.ts","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx","../src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx","../src/DataViewCheckboxFilter/index.ts","../src/DataViewEventsContext/DataViewEventsContext.test.tsx","../src/DataViewEventsContext/DataViewEventsContext.tsx","../src/DataViewEventsContext/index.ts","../src/DataViewFilters/DataViewFilters.test.tsx","../src/DataViewFilters/DataViewFilters.tsx","../src/DataViewFilters/index.tsx","../src/DataViewTable/DataViewTable.test.tsx","../src/DataViewTable/DataViewTable.tsx","../src/DataViewTable/index.ts","../src/DataViewTableBasic/DataViewTableBasic.test.tsx","../src/DataViewTableBasic/DataViewTableBasic.tsx","../src/DataViewTableBasic/index.ts","../src/DataViewTableHead/DataViewTableHead.test.tsx","../src/DataViewTableHead/DataViewTableHead.tsx","../src/DataViewTableHead/index.ts","../src/DataViewTableTree/DataViewTableTree.test.tsx","../src/DataViewTableTree/DataViewTableTree.tsx","../src/DataViewTableTree/index.ts","../src/DataViewTextFilter/DataViewTextFilter.test.tsx","../src/DataViewTextFilter/DataViewTextFilter.tsx","../src/DataViewTextFilter/index.ts","../src/DataViewToolbar/DataViewToolbar.test.tsx","../src/DataViewToolbar/DataViewToolbar.tsx","../src/DataViewToolbar/index.ts","../src/Hooks/filters.test.tsx","../src/Hooks/filters.ts","../src/Hooks/index.ts","../src/Hooks/pagination.test.tsx","../src/Hooks/pagination.ts","../src/Hooks/selection.test.tsx","../src/Hooks/selection.ts","../src/Hooks/sort.test.tsx","../src/Hooks/sort.ts","../src/InternalContext/InternalContext.test.tsx","../src/InternalContext/InternalContext.tsx","../src/InternalContext/index.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-data-view",
3
- "version": "6.3.0",
3
+ "version": "6.4.0-prerelease.2",
4
4
  "description": "Data view used for Red Hat projects.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -55,6 +55,6 @@
55
55
  "react-router": "^6.30.1",
56
56
  "react-router-dom": "^6.30.1",
57
57
  "rimraf": "^6.0.1",
58
- "typescript": "^5.8.3"
58
+ "typescript": "^5.9.2"
59
59
  }
60
60
  }
@@ -4,6 +4,7 @@ import { DataViewTable, DataViewTh, DataViewTrTree } from '@patternfly/react-dat
4
4
  import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
5
5
  import { FolderIcon, FolderOpenIcon, LeafIcon } from '@patternfly/react-icons';
6
6
 
7
+
7
8
  interface Repository {
8
9
  name: string;
9
10
  branches: string | null;
@@ -39,6 +39,8 @@ To define rows and columns for your table, use these props:
39
39
 
40
40
  It is also possible to disable row selection using the `isSelectDisabled` function, which can be passed to the wrapping `DataView` component through the `selection` prop.
41
41
 
42
+ If you want to have all expandable nodes open on initial load pass the `expandAll` prop to the DataViewTable component
43
+
42
44
  ### Table example
43
45
  ```js file="./DataViewTableExample.tsx"
44
46
 
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
3
3
  import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
4
+ import { Button } from '@patternfly/react-core';
4
5
  import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
5
6
  import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
6
7
  import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
@@ -30,13 +31,18 @@ const ouiaId = 'LayoutExample';
30
31
 
31
32
  export const BasicExample: React.FunctionComponent = () => {
32
33
  const selection = useDataViewSelection({ matchOption: (a, b) => a[0] === b[0] });
33
- const { selected, onSelect } = selection;
34
+ const { selected, onSelect, setSelected } = selection;
34
35
 
35
36
  const handleBulkSelect = (value: BulkSelectValue) => {
36
37
  value === BulkSelectValue.none && onSelect(false);
37
38
  value === BulkSelectValue.all && onSelect(true, rows);
38
39
  };
39
40
 
41
+ // Example: Select first two rows programmatically using setSelected
42
+ const handleSelectFirstTwo = () => {
43
+ setSelected(rows.slice(0, 2));
44
+ };
45
+
40
46
  return (
41
47
  <DataView selection={selection}>
42
48
  <DataViewToolbar
@@ -49,9 +55,14 @@ export const BasicExample: React.FunctionComponent = () => {
49
55
  selectedCount={selected.length}
50
56
  onSelect={handleBulkSelect}
51
57
  />
52
- }
58
+ }
59
+ actions={
60
+ <Button variant="secondary" onClick={handleSelectFirstTwo}>
61
+ Select First Two
62
+ </Button>
63
+ }
53
64
  />
54
65
  <DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={rows} />
55
66
  </DataView>
56
67
  );
57
- }
68
+ }
@@ -105,6 +105,7 @@ The `useDataViewSelection` hook manages the selection state of the data view.
105
105
  - `selected` array of currently selected records.
106
106
  - `isSelected` function returning the selection state for the record.
107
107
  - `onSelect` callback to modify the selection state. This accepts the `isSelecting` flag (indicates if records are being selected or deselected) and `items` (affected records).
108
+ - `setSelected` function to directly set the selected items array. This is useful for programmatically setting a specific selection state without needing to use the `onSelect` callback.
108
109
 
109
110
  ### Selection example
110
111
 
package/release.config.js CHANGED
@@ -18,5 +18,5 @@ module.exports = {
18
18
  '@semantic-release/npm'
19
19
  ],
20
20
  tagFormat: 'prerelease-v${version}',
21
- dryRun: true
21
+ dryRun: false
22
22
  };
@@ -101,6 +101,15 @@ describe('DataViewTableTree component', () => {
101
101
  expect(container).toMatchSnapshot();
102
102
  });
103
103
 
104
+ test('should render tree table with all expandable nodes expanded', () => {
105
+ const { container } = render(
106
+ <DataView selection={mockSelection}>
107
+ <DataViewTable isTreeTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} expandAll rows={rows} leafIcon={<LeafIcon/>} expandedIcon={<FolderOpenIcon aria-hidden />} collapsedIcon={<FolderIcon aria-hidden />} />
108
+ </DataView>
109
+ );
110
+ expect(container).toMatchSnapshot();
111
+ });
112
+
104
113
  test('should render tree table with a loading state', () => {
105
114
  const { container } = render(
106
115
  <DataView activeState="loading">
@@ -1,4 +1,4 @@
1
- import { FC, useState, useMemo, ReactNode } from 'react';
1
+ import { FC, useState, useMemo, useEffect, ReactNode } from 'react';
2
2
  import {
3
3
  Table,
4
4
  TableProps,
@@ -67,6 +67,8 @@ export interface DataViewTableTreeProps extends Omit<TableProps, 'onSelect' | 'r
67
67
  expandedIcon?: React.ReactNode;
68
68
  /** Optional icon for the collapsed parent rows */
69
69
  collapsedIcon?: React.ReactNode;
70
+ /** Expand all expandable nodes on initial load */
71
+ expandAll?: boolean;
70
72
  /** Custom OUIA ID */
71
73
  ouiaId?: string;
72
74
  }
@@ -79,6 +81,7 @@ export const DataViewTableTree: FC<DataViewTableTreeProps> = ({
79
81
  leafIcon = null,
80
82
  expandedIcon = null,
81
83
  collapsedIcon = null,
84
+ expandAll = false,
82
85
  ouiaId = 'DataViewTableTree',
83
86
  ...props
84
87
  }: DataViewTableTreeProps) => {
@@ -87,6 +90,37 @@ export const DataViewTableTree: FC<DataViewTableTreeProps> = ({
87
90
  const [ expandedNodeIds, setExpandedNodeIds ] = useState<string[]>([]);
88
91
  const [ expandedDetailsNodeNames, setExpandedDetailsNodeIds ] = useState<string[]>([]);
89
92
 
93
+ // Helper function to collect all node IDs that have children (are expandable)
94
+ const getExpandableNodeIds = (nodes: DataViewTrTree[]): string[] => {
95
+ const expandableIds: string[] = [];
96
+
97
+ const traverse = (nodeList: DataViewTrTree[]) => {
98
+ nodeList.forEach(node => {
99
+ if (node.children && node.children.length > 0) {
100
+ expandableIds.push(node.id);
101
+ traverse(node.children);
102
+ }
103
+ });
104
+ };
105
+
106
+ traverse(nodes);
107
+ return expandableIds;
108
+ };
109
+
110
+ // Effect to handle expandAll behavior
111
+ // Memoize the expandable IDs to avoid recalculating when rows object reference changes but structure is the same
112
+ const expandableIds = useMemo(() => getExpandableNodeIds(rows), [ rows ]);
113
+
114
+ // Effect to handle expandAll behavior - only runs when IDs actually change
115
+ useEffect(() => {
116
+ if (expandAll) {
117
+ setExpandedNodeIds(expandableIds);
118
+ } else {
119
+ setExpandedNodeIds([]);
120
+ }
121
+ // eslint-disable-next-line react-hooks/exhaustive-deps
122
+ }, [ expandAll, expandableIds.join(',') ]);
123
+
90
124
  const activeHeadState = useMemo(() => activeState ? headStates?.[activeState] : undefined, [ activeState, headStates ]);
91
125
  const activeBodyState = useMemo(() => activeState ? bodyStates?.[activeState] : undefined, [ activeState, bodyStates ]);
92
126