@patternfly/react-data-view 6.4.0-prerelease.9 → 6.5.0-prerelease.1
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/DataViewTextFilter/DataViewTextFilter.d.ts +2 -0
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.js +29 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/cjs/DataViewTextFilter/DataViewTextFilter.test.js +83 -0
- package/dist/cjs/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/cjs/DataViewTh/DataViewTh.js +8 -1
- package/dist/cjs/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.js +229 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/cjs/DataViewTreeFilter/DataViewTreeFilter.test.js +171 -0
- package/dist/cjs/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/cjs/DataViewTreeFilter/index.js +23 -0
- package/dist/cjs/Hooks/selection.d.ts +8 -8
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +4 -1
- package/dist/dynamic/DataViewTreeFilter/package.json +1 -0
- package/dist/dynamic-modules.json +2 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.d.ts +2 -0
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.js +29 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.d.ts +1 -1
- package/dist/esm/DataViewTextFilter/DataViewTextFilter.test.js +84 -1
- package/dist/esm/DataViewTh/DataViewTh.d.ts +4 -4
- package/dist/esm/DataViewTh/DataViewTh.js +8 -1
- package/dist/esm/DataViewToolbar/DataViewToolbar.js +13 -1
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.d.ts +26 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.js +225 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.d.ts +1 -0
- package/dist/esm/DataViewTreeFilter/DataViewTreeFilter.test.js +166 -0
- package/dist/esm/DataViewTreeFilter/index.d.ts +2 -0
- package/dist/esm/DataViewTreeFilter/index.js +2 -0
- package/dist/esm/Hooks/selection.d.ts +8 -8
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/DataView/PredefinedLayoutFullExample.tsx +2 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/FiltersExample.tsx +2 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/PaginationExample.tsx +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +8 -1
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/TreeFilterExample.tsx +248 -0
- package/src/DataViewTextFilter/DataViewTextFilter.test.tsx +129 -0
- package/src/DataViewTextFilter/DataViewTextFilter.tsx +58 -22
- package/src/DataViewTh/DataViewTh.tsx +15 -7
- package/src/DataViewToolbar/DataViewToolbar.tsx +17 -2
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +288 -280
- package/src/DataViewTreeFilter/DataViewTreeFilter.test.tsx +222 -0
- package/src/DataViewTreeFilter/DataViewTreeFilter.tsx +361 -0
- package/src/DataViewTreeFilter/__snapshots__/DataViewTreeFilter.test.tsx.snap +199 -0
- package/src/DataViewTreeFilter/index.ts +2 -0
- package/src/Hooks/selection.ts +8 -8
- package/src/index.ts +3 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.default = void 0;
|
|
21
|
+
var DataViewTreeFilter_1 = require("./DataViewTreeFilter");
|
|
22
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(DataViewTreeFilter_1).default; } });
|
|
23
|
+
__exportStar(require("./DataViewTreeFilter"), exports);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export interface UseDataViewSelectionProps {
|
|
1
|
+
export interface UseDataViewSelectionProps<T = any> {
|
|
2
2
|
/** Function to compare items when checking if item is selected */
|
|
3
|
-
matchOption: (item:
|
|
3
|
+
matchOption: (item: T, another: T) => boolean;
|
|
4
4
|
/** Array of initially selected entries */
|
|
5
|
-
initialSelected?: (
|
|
5
|
+
initialSelected?: (T)[];
|
|
6
6
|
}
|
|
7
|
-
export declare const useDataViewSelection: (props?: UseDataViewSelectionProps) => {
|
|
8
|
-
selected:
|
|
9
|
-
onSelect: (isSelecting: boolean, items?:
|
|
10
|
-
isSelected: (item:
|
|
11
|
-
setSelected: (items:
|
|
7
|
+
export declare const useDataViewSelection: <T = any>(props?: UseDataViewSelectionProps<T>) => {
|
|
8
|
+
selected: T[];
|
|
9
|
+
onSelect: (isSelecting: boolean, items?: T[] | T) => void;
|
|
10
|
+
isSelected: (item: T) => boolean;
|
|
11
|
+
setSelected: (items: T[]) => void;
|
|
12
12
|
};
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { default as InternalContext } from './InternalContext';
|
|
2
2
|
export * from './InternalContext';
|
|
3
3
|
export * from './Hooks';
|
|
4
|
+
export { default as DataViewTreeFilter } from './DataViewTreeFilter';
|
|
5
|
+
export * from './DataViewTreeFilter';
|
|
4
6
|
export { default as DataViewToolbar } from './DataViewToolbar';
|
|
5
7
|
export * from './DataViewToolbar';
|
|
6
8
|
export { default as DataViewTh } from './DataViewTh';
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,11 +18,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.DataView = exports.DataViewCheckboxFilter = exports.DataViewEventsContext = exports.DataViewFilters = exports.DataViewTable = exports.DataViewTableBasic = exports.DataViewTableHead = exports.DataViewTableTree = exports.DataViewTextFilter = exports.DataViewTh = exports.DataViewToolbar = exports.InternalContext = void 0;
|
|
21
|
+
exports.DataView = exports.DataViewCheckboxFilter = exports.DataViewEventsContext = exports.DataViewFilters = exports.DataViewTable = exports.DataViewTableBasic = exports.DataViewTableHead = exports.DataViewTableTree = exports.DataViewTextFilter = exports.DataViewTh = exports.DataViewToolbar = exports.DataViewTreeFilter = exports.InternalContext = void 0;
|
|
22
22
|
var InternalContext_1 = require("./InternalContext");
|
|
23
23
|
Object.defineProperty(exports, "InternalContext", { enumerable: true, get: function () { return __importDefault(InternalContext_1).default; } });
|
|
24
24
|
__exportStar(require("./InternalContext"), exports);
|
|
25
25
|
__exportStar(require("./Hooks"), exports);
|
|
26
|
+
var DataViewTreeFilter_1 = require("./DataViewTreeFilter");
|
|
27
|
+
Object.defineProperty(exports, "DataViewTreeFilter", { enumerable: true, get: function () { return __importDefault(DataViewTreeFilter_1).default; } });
|
|
28
|
+
__exportStar(require("./DataViewTreeFilter"), exports);
|
|
26
29
|
var DataViewToolbar_1 = require("./DataViewToolbar");
|
|
27
30
|
Object.defineProperty(exports, "DataViewToolbar", { enumerable: true, get: function () { return __importDefault(DataViewToolbar_1).default; } });
|
|
28
31
|
__exportStar(require("./DataViewToolbar"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"main":"../../cjs/DataViewTreeFilter/index.js","module":"../../esm/DataViewTreeFilter/index.js","typings":"../../esm/DataViewTreeFilter/index.d.ts"}
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"DataViewTr": "dist/dynamic/DataViewTable",
|
|
36
36
|
"DataViewTrObject": "dist/dynamic/DataViewTable",
|
|
37
37
|
"DataViewTrTree": "dist/dynamic/DataViewTable",
|
|
38
|
+
"DataViewTreeFilter": "dist/dynamic/DataViewTreeFilter",
|
|
39
|
+
"DataViewTreeFilterProps": "dist/dynamic/DataViewTreeFilter",
|
|
38
40
|
"EventTypes": "dist/dynamic/DataViewEventsContext",
|
|
39
41
|
"ExpandableContent": "dist/dynamic/DataViewTableBasic",
|
|
40
42
|
"InternalContext": "dist/dynamic/InternalContext",
|
|
@@ -16,6 +16,8 @@ export interface DataViewTextFilterProps extends SearchInputProps {
|
|
|
16
16
|
trimValue?: boolean;
|
|
17
17
|
/** Custom OUIA ID */
|
|
18
18
|
ouiaId?: string;
|
|
19
|
+
/** Enable keyboard shortcut (/) to focus the filter. Defaults to true. */
|
|
20
|
+
enableShortcut?: boolean;
|
|
19
21
|
}
|
|
20
22
|
export declare const DataViewTextFilter: FC<DataViewTextFilterProps>;
|
|
21
23
|
export default DataViewTextFilter;
|
|
@@ -10,9 +10,37 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { useEffect } from 'react';
|
|
13
14
|
import { SearchInput, ToolbarFilter } from '@patternfly/react-core';
|
|
14
15
|
export const DataViewTextFilter = (_a) => {
|
|
15
|
-
var { filterId, title, value = '', onChange, onClear = () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), showToolbarItem, trimValue = true, ouiaId = 'DataViewTextFilter' } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "onClear", "showToolbarItem", "trimValue", "ouiaId"]);
|
|
16
|
+
var { filterId, title, value = '', onChange, onClear = () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), showToolbarItem, trimValue = true, ouiaId = 'DataViewTextFilter', enableShortcut = true } = _a, props = __rest(_a, ["filterId", "title", "value", "onChange", "onClear", "showToolbarItem", "trimValue", "ouiaId", "enableShortcut"]);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!enableShortcut) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const handleKeyDown = (event) => {
|
|
22
|
+
// Only handle "/" key when not typing in an input, textarea, or contenteditable element
|
|
23
|
+
if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
24
|
+
const target = event.target;
|
|
25
|
+
const isInputElement = target.tagName === 'INPUT' ||
|
|
26
|
+
target.tagName === 'TEXTAREA' ||
|
|
27
|
+
target.isContentEditable;
|
|
28
|
+
// Only focus if the filter is visible and we're not already in an input field
|
|
29
|
+
if (showToolbarItem && !isInputElement) {
|
|
30
|
+
// Find the input element by its ID (searchInputId prop)
|
|
31
|
+
const inputElement = document.getElementById(filterId);
|
|
32
|
+
if (inputElement) {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
inputElement.focus();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
40
|
+
return () => {
|
|
41
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
42
|
+
};
|
|
43
|
+
}, [showToolbarItem, filterId, enableShortcut]);
|
|
16
44
|
return (_jsx(ToolbarFilter, { "data-ouia-component-id": ouiaId, labels: value.length > 0 ? [{ key: title, node: value }] : [], deleteLabel: () => onChange === null || onChange === void 0 ? void 0 : onChange(undefined, ''), categoryName: title, showToolbarItem: showToolbarItem, children: _jsx(SearchInput, Object.assign({ searchInputId: filterId, value: value, onChange: (e, inputValue) => onChange === null || onChange === void 0 ? void 0 : onChange(e, trimValue ? inputValue.trim() : inputValue), onClear: onClear, placeholder: `Filter by ${title}`, "aria-label": `${title !== null && title !== void 0 ? title : filterId} filter`, "data-ouia-component-id": `${ouiaId}-input` }, props)) }, ouiaId));
|
|
17
45
|
};
|
|
18
46
|
export default DataViewTextFilter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
3
4
|
import DataViewTextFilter from './DataViewTextFilter';
|
|
4
5
|
import DataViewToolbar from '../DataViewToolbar';
|
|
5
6
|
describe('DataViewTextFilter component', () => {
|
|
@@ -14,4 +15,86 @@ describe('DataViewTextFilter component', () => {
|
|
|
14
15
|
const { container } = render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps)) }));
|
|
15
16
|
expect(container).toMatchSnapshot();
|
|
16
17
|
});
|
|
18
|
+
it('should focus the search input when "/" key is pressed and filter is visible', () => {
|
|
19
|
+
render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps, { showToolbarItem: true })) }));
|
|
20
|
+
const input = document.getElementById('test-filter');
|
|
21
|
+
expect(input).toBeInTheDocument();
|
|
22
|
+
// Simulate pressing "/" key by creating and dispatching a KeyboardEvent
|
|
23
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
24
|
+
key: '/',
|
|
25
|
+
code: 'Slash',
|
|
26
|
+
bubbles: true,
|
|
27
|
+
cancelable: true,
|
|
28
|
+
});
|
|
29
|
+
window.dispatchEvent(keyEvent);
|
|
30
|
+
// Check that the input has focus
|
|
31
|
+
expect(document.activeElement).toBe(input);
|
|
32
|
+
});
|
|
33
|
+
it('should not focus the search input when "/" key is pressed if filter is not visible', () => {
|
|
34
|
+
render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps, { showToolbarItem: false })) }));
|
|
35
|
+
const input = document.getElementById('test-filter');
|
|
36
|
+
// Simulate pressing "/" key
|
|
37
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
38
|
+
key: '/',
|
|
39
|
+
code: 'Slash',
|
|
40
|
+
bubbles: true,
|
|
41
|
+
cancelable: true,
|
|
42
|
+
});
|
|
43
|
+
window.dispatchEvent(keyEvent);
|
|
44
|
+
if (input) {
|
|
45
|
+
expect(document.activeElement).not.toBe(input);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
it('should not focus the search input when "/" key is pressed while typing in another input', () => {
|
|
49
|
+
const { container } = render(_jsxs("div", { children: [_jsx("input", { "data-testid": "other-input" }), _jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps, { showToolbarItem: true })) })] }));
|
|
50
|
+
const otherInput = container.querySelector('[data-testid="other-input"]');
|
|
51
|
+
// Focus the other input first
|
|
52
|
+
otherInput.focus();
|
|
53
|
+
expect(document.activeElement).toBe(otherInput);
|
|
54
|
+
// Simulate pressing "/" key while focused on the other input
|
|
55
|
+
// The event target should be the input element
|
|
56
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
57
|
+
key: '/',
|
|
58
|
+
code: 'Slash',
|
|
59
|
+
bubbles: true,
|
|
60
|
+
cancelable: true,
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(keyEvent, 'target', {
|
|
63
|
+
value: otherInput,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
});
|
|
66
|
+
window.dispatchEvent(keyEvent);
|
|
67
|
+
// The search input should not be focused since we're already in an input field
|
|
68
|
+
expect(document.activeElement).toBe(otherInput);
|
|
69
|
+
});
|
|
70
|
+
it('should not focus the search input when enableShortcut is false', () => {
|
|
71
|
+
render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps, { showToolbarItem: true, enableShortcut: false })) }));
|
|
72
|
+
const input = document.getElementById('test-filter');
|
|
73
|
+
expect(input).toBeInTheDocument();
|
|
74
|
+
// Simulate pressing "/" key
|
|
75
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
76
|
+
key: '/',
|
|
77
|
+
code: 'Slash',
|
|
78
|
+
bubbles: true,
|
|
79
|
+
cancelable: true,
|
|
80
|
+
});
|
|
81
|
+
window.dispatchEvent(keyEvent);
|
|
82
|
+
// The input should not be focused since the shortcut is disabled
|
|
83
|
+
expect(document.activeElement).not.toBe(input);
|
|
84
|
+
});
|
|
85
|
+
it('should focus the search input when enableShortcut is true (default)', () => {
|
|
86
|
+
render(_jsx(DataViewToolbar, { filters: _jsx(DataViewTextFilter, Object.assign({}, defaultProps, { showToolbarItem: true })) }));
|
|
87
|
+
const input = document.getElementById('test-filter');
|
|
88
|
+
expect(input).toBeInTheDocument();
|
|
89
|
+
// Simulate pressing "/" key
|
|
90
|
+
const keyEvent = new KeyboardEvent('keydown', {
|
|
91
|
+
key: '/',
|
|
92
|
+
code: 'Slash',
|
|
93
|
+
bubbles: true,
|
|
94
|
+
cancelable: true,
|
|
95
|
+
});
|
|
96
|
+
window.dispatchEvent(keyEvent);
|
|
97
|
+
// The input should be focused since the shortcut is enabled by default
|
|
98
|
+
expect(document.activeElement).toBe(input);
|
|
99
|
+
});
|
|
17
100
|
});
|
|
@@ -5,13 +5,13 @@ export interface DataViewThResizableProps {
|
|
|
5
5
|
isResizable?: boolean;
|
|
6
6
|
/** Callback after the column is resized. Returns the triggering event, the column id passed in via cell props, and the new width of the column. */
|
|
7
7
|
onResize?: (event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent, id: string | number | undefined, width: number) => void;
|
|
8
|
-
/**
|
|
8
|
+
/** Starting width in pixels of the column */
|
|
9
9
|
width?: number;
|
|
10
|
-
/** Minimum width of the column */
|
|
10
|
+
/** Minimum resize width in pixels of the column */
|
|
11
11
|
minWidth?: number;
|
|
12
|
-
/** Increment for keyboard navigation */
|
|
12
|
+
/** Increment in pixels for keyboard navigation */
|
|
13
13
|
increment?: number;
|
|
14
|
-
/** Increment for keyboard navigation while shift is held */
|
|
14
|
+
/** Increment in pixels for keyboard navigation while shift is held */
|
|
15
15
|
shiftIncrement?: number;
|
|
16
16
|
/** Provides an accessible name for the resizable column via a human readable string. */
|
|
17
17
|
resizeButtonAriaLabel?: string;
|
|
@@ -210,6 +210,13 @@ export const DataViewTh = (_a) => {
|
|
|
210
210
|
onResize && onResize(e, thProps === null || thProps === void 0 ? void 0 : thProps.id, newSize);
|
|
211
211
|
};
|
|
212
212
|
const resizableContent = (_jsxs(Fragment, { children: [_jsx("div", { "aria-live": "polite", className: "pf-v6-screen-reader", children: screenReaderText }), _jsx(Button, { ref: resizeButtonRef, variant: "plain", hasNoPadding: true, icon: _jsx(ResizeIcon, {}), onMouseDown: handleMousedown, onKeyDown: handleKeys, onTouchStart: handleTouchStart, "aria-label": resizeButtonAriaLabel, className: classes.dataViewResizableButton })] }));
|
|
213
|
-
|
|
213
|
+
const classNames = [];
|
|
214
|
+
if (thProps === null || thProps === void 0 ? void 0 : thProps.className) {
|
|
215
|
+
classNames.push(thProps.className);
|
|
216
|
+
}
|
|
217
|
+
if (dataViewThClassName) {
|
|
218
|
+
classNames.push(dataViewThClassName);
|
|
219
|
+
}
|
|
220
|
+
return (_jsx(Th, Object.assign({ modifier: "truncate" }, thProps, props, { ref: thRef, style: width > 0 ? Object.assign(Object.assign({}, thProps === null || thProps === void 0 ? void 0 : thProps.style), { minWidth: width }) : thProps === null || thProps === void 0 ? void 0 : thProps.style, className: classNames.length > 0 ? classNames.join(' ') : undefined }, (isResizable && { additionalContent: resizableContent }), { children: content })));
|
|
214
221
|
};
|
|
215
222
|
export default DataViewTh;
|
|
@@ -12,9 +12,21 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { useRef } from 'react';
|
|
14
14
|
import { Button, Toolbar, ToolbarContent, ToolbarItem, ToolbarItemVariant } from '@patternfly/react-core';
|
|
15
|
+
import { createUseStyles } from 'react-jss';
|
|
16
|
+
const useStyles = createUseStyles({
|
|
17
|
+
dataViewToolbarPagination: {
|
|
18
|
+
flexBasis: '100%',
|
|
19
|
+
width: '100%'
|
|
20
|
+
},
|
|
21
|
+
dataViewToolbarPaginationWrapper: {
|
|
22
|
+
flexBasis: '100%',
|
|
23
|
+
width: '100%'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
15
26
|
export const DataViewToolbar = (_a) => {
|
|
16
27
|
var { className, ouiaId = 'DataViewToolbar', bulkSelect, actions, toggleGroup, pagination, filters, customLabelGroupContent, clearAllFilters, children } = _a, props = __rest(_a, ["className", "ouiaId", "bulkSelect", "actions", "toggleGroup", "pagination", "filters", "customLabelGroupContent", "clearAllFilters", "children"]);
|
|
28
|
+
const classes = useStyles();
|
|
17
29
|
const defaultClearFilters = useRef(_jsx(ToolbarItem, { children: _jsx(Button, { ouiaId: `${ouiaId}-clear-all-filters`, variant: "link", onClick: clearAllFilters, isInline: true, children: "Clear filters" }) }));
|
|
18
|
-
return (_jsx(Toolbar, Object.assign({ ouiaId: ouiaId, className: className, customLabelGroupContent: customLabelGroupContent !== null && customLabelGroupContent !== void 0 ? customLabelGroupContent : defaultClearFilters.current }, props, { children: _jsxs(ToolbarContent, { children: [bulkSelect && (_jsx(ToolbarItem, { "data-ouia-component-id": `${ouiaId}-bulk-select`, children: bulkSelect })), filters && (_jsx(ToolbarItem, { children: filters })), actions && (_jsx(ToolbarItem, { children: actions })), toggleGroup && (_jsx(ToolbarItem, { children: toggleGroup })), pagination && (_jsx(ToolbarItem, { variant: ToolbarItemVariant.pagination, "data-ouia-component-id": `${ouiaId}-pagination`, children: pagination })), children] }) })));
|
|
30
|
+
return (_jsx(Toolbar, Object.assign({ ouiaId: ouiaId, className: className, customLabelGroupContent: customLabelGroupContent !== null && customLabelGroupContent !== void 0 ? customLabelGroupContent : defaultClearFilters.current }, props, { children: _jsxs(ToolbarContent, { children: [bulkSelect && (_jsx(ToolbarItem, { "data-ouia-component-id": `${ouiaId}-bulk-select`, children: bulkSelect })), filters && (_jsx(ToolbarItem, { children: filters })), actions && (_jsx(ToolbarItem, { children: actions })), toggleGroup && (_jsx(ToolbarItem, { children: toggleGroup })), pagination && (_jsx(ToolbarItem, { variant: ToolbarItemVariant.pagination, "data-ouia-component-id": `${ouiaId}-pagination`, className: classes.dataViewToolbarPagination, children: _jsx("div", { className: classes.dataViewToolbarPaginationWrapper, children: pagination }) })), children] }) })));
|
|
19
31
|
};
|
|
20
32
|
export default DataViewToolbar;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ToolbarFilterProps, TreeViewDataItem } from '@patternfly/react-core';
|
|
2
|
+
import React, { FC } from 'react';
|
|
3
|
+
export interface DataViewTreeFilterProps {
|
|
4
|
+
/** Unique key for the filter attribute */
|
|
5
|
+
filterId: string;
|
|
6
|
+
/** Array of current filter values */
|
|
7
|
+
value?: string[];
|
|
8
|
+
/** Filter title displayed in the toolbar */
|
|
9
|
+
title: string;
|
|
10
|
+
/** Callback for when the selection changes */
|
|
11
|
+
onChange?: (event?: React.MouseEvent, values?: string[]) => void;
|
|
12
|
+
/** Controls visibility of the filter in the toolbar */
|
|
13
|
+
showToolbarItem?: ToolbarFilterProps['showToolbarItem'];
|
|
14
|
+
/** Custom OUIA ID */
|
|
15
|
+
ouiaId?: string;
|
|
16
|
+
/** Hierarchical data items for the tree structure */
|
|
17
|
+
items?: TreeViewDataItem[];
|
|
18
|
+
/** When true, expands all tree nodes by default */
|
|
19
|
+
defaultExpanded?: boolean;
|
|
20
|
+
/** Callback for when tree items are selected/deselected, provides all currently selected nodes */
|
|
21
|
+
onSelect?: (selectedItems: TreeViewDataItem[]) => void;
|
|
22
|
+
/** Array of pre-selected item id's to be checked on initial render */
|
|
23
|
+
defaultSelected?: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare const DataViewTreeFilter: FC<DataViewTreeFilterProps>;
|
|
26
|
+
export default DataViewTreeFilter;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Dropdown, MenuToggle, ToolbarFilter, TreeView } from '@patternfly/react-core';
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { createUseStyles } from 'react-jss';
|
|
5
|
+
/** This style is needed so the tree filter dropdown looks like the basic filter dropdow */
|
|
6
|
+
const useStyles = createUseStyles({
|
|
7
|
+
dataViewTreeFilterTreeView: {
|
|
8
|
+
'& .pf-v6-c-tree-view__node::after': {
|
|
9
|
+
borderRadius: 0,
|
|
10
|
+
borderRightStyle: 'none',
|
|
11
|
+
borderLeftStyle: 'none'
|
|
12
|
+
},
|
|
13
|
+
'& .pf-v6-c-tree-view__content': {
|
|
14
|
+
borderRadius: 0
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
// Generic helper to collect items from tree based on predicate
|
|
19
|
+
const collectTreeItems = (items, predicate, leafOnly = false) => {
|
|
20
|
+
const collected = [];
|
|
21
|
+
const collect = (item) => {
|
|
22
|
+
var _a;
|
|
23
|
+
const isLeaf = !item.children || item.children.length === 0;
|
|
24
|
+
if (predicate(item) && (!leafOnly || isLeaf)) {
|
|
25
|
+
collected.push(item);
|
|
26
|
+
}
|
|
27
|
+
(_a = item.children) === null || _a === void 0 ? void 0 : _a.forEach(child => collect(child));
|
|
28
|
+
};
|
|
29
|
+
items.forEach(item => collect(item));
|
|
30
|
+
return collected;
|
|
31
|
+
};
|
|
32
|
+
// Helper function to get all checked items (not just leaf nodes)
|
|
33
|
+
const getAllCheckedItems = (items) => collectTreeItems(items, item => { var _a; return ((_a = item.checkProps) === null || _a === void 0 ? void 0 : _a.checked) === true; }, false);
|
|
34
|
+
// Get all checked leaf items (returns array of names)
|
|
35
|
+
const getAllCheckedLeafItems = (items) => collectTreeItems(items, item => { var _a; return ((_a = item.checkProps) === null || _a === void 0 ? void 0 : _a.checked) === true; }, true).map(item => String(item.name));
|
|
36
|
+
// Helper function to expand all nodes in the tree
|
|
37
|
+
const expandAllNodes = (items) => items.map(item => (Object.assign(Object.assign({}, item), { defaultExpanded: true, children: item.children ? expandAllNodes(item.children) : undefined })));
|
|
38
|
+
// Helper function to set pre-selected items
|
|
39
|
+
const setPreSelectedItems = (items, selectedIds) => items.map(item => {
|
|
40
|
+
var _a, _b;
|
|
41
|
+
const isSelected = selectedIds.includes(String(item.id));
|
|
42
|
+
const hasSelectedChildren = (_b = (_a = item.children) === null || _a === void 0 ? void 0 : _a.some(child => selectedIds.includes(String(child.id)))) !== null && _b !== void 0 ? _b : false;
|
|
43
|
+
return Object.assign(Object.assign({}, item), { checkProps: item.checkProps ? Object.assign(Object.assign({}, item.checkProps), { checked: isSelected || hasSelectedChildren }) : undefined, children: item.children ? setPreSelectedItems(item.children, selectedIds) : undefined });
|
|
44
|
+
});
|
|
45
|
+
// Helper function to uncheck all items recursively
|
|
46
|
+
const uncheckRecursive = (items) => items.map(item => (Object.assign(Object.assign({}, item), { checkProps: item.checkProps ? Object.assign(Object.assign({}, item.checkProps), { checked: false }) : undefined, children: item.children ? uncheckRecursive(item.children) : undefined })));
|
|
47
|
+
export const DataViewTreeFilter = ({ filterId, title, value = [], onChange, showToolbarItem, ouiaId = 'DataViewTreeFilter', items, defaultExpanded = false, onSelect, defaultSelected = [] }) => {
|
|
48
|
+
const classes = useStyles();
|
|
49
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
50
|
+
const [treeData, setTreeData] = useState(items || []);
|
|
51
|
+
const menuRef = useRef(null);
|
|
52
|
+
const isInitialMount = useRef(true);
|
|
53
|
+
const hasCalledInitialOnChange = useRef(false);
|
|
54
|
+
// Initialize tree data with defaultExpanded and defaultSelected (only on first mount)
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!items) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let initializedData = [...items];
|
|
60
|
+
// Apply default expansion
|
|
61
|
+
if (defaultExpanded) {
|
|
62
|
+
initializedData = expandAllNodes(initializedData);
|
|
63
|
+
}
|
|
64
|
+
// Apply pre-selected items only on initial mount
|
|
65
|
+
if (isInitialMount.current && defaultSelected.length > 0) {
|
|
66
|
+
initializedData = setPreSelectedItems(initializedData, defaultSelected);
|
|
67
|
+
}
|
|
68
|
+
setTreeData(initializedData);
|
|
69
|
+
if (isInitialMount.current) {
|
|
70
|
+
isInitialMount.current = false;
|
|
71
|
+
}
|
|
72
|
+
}, [items, defaultExpanded]);
|
|
73
|
+
// Call onChange and onSelect after tree data is initialized with default selections
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!hasCalledInitialOnChange.current && defaultSelected.length > 0 && treeData.length > 0) {
|
|
76
|
+
const selectedValues = getAllCheckedLeafItems(treeData);
|
|
77
|
+
// Only call if there are actually selected values
|
|
78
|
+
if (selectedValues.length > 0) {
|
|
79
|
+
// Calculate both values synchronously before calling callbacks
|
|
80
|
+
const selectedItems = getAllCheckedItems(treeData);
|
|
81
|
+
// useEffect already runs after render, so this is safe
|
|
82
|
+
if (onChange) {
|
|
83
|
+
onChange(undefined, selectedValues);
|
|
84
|
+
}
|
|
85
|
+
if (onSelect) {
|
|
86
|
+
onSelect(selectedItems);
|
|
87
|
+
}
|
|
88
|
+
hasCalledInitialOnChange.current = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}, [treeData, onChange, onSelect, defaultSelected.length]);
|
|
92
|
+
// Sync tree checkboxes when value prop changes (when clearAllFilters is called)
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (value.length === 0) {
|
|
95
|
+
setTreeData(currentTreeData => {
|
|
96
|
+
if (currentTreeData.length === 0) {
|
|
97
|
+
return currentTreeData;
|
|
98
|
+
}
|
|
99
|
+
const currentCheckedItems = getAllCheckedLeafItems(currentTreeData);
|
|
100
|
+
// Only update if there are checked items that need to be unchecked
|
|
101
|
+
if (currentCheckedItems.length > 0) {
|
|
102
|
+
return uncheckRecursive(currentTreeData);
|
|
103
|
+
}
|
|
104
|
+
return currentTreeData;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}, [value]);
|
|
108
|
+
// Check if all children are checked (recursive)
|
|
109
|
+
const areAllChildrenChecked = (item) => {
|
|
110
|
+
var _a, _b;
|
|
111
|
+
if (!((_a = item.children) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
112
|
+
return ((_b = item.checkProps) === null || _b === void 0 ? void 0 : _b.checked) === true;
|
|
113
|
+
}
|
|
114
|
+
return item.children.every(child => areAllChildrenChecked(child));
|
|
115
|
+
};
|
|
116
|
+
// Check if some children are checked (recursive)
|
|
117
|
+
const areSomeChildrenChecked = (item) => {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
if (!((_a = item.children) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
120
|
+
return ((_b = item.checkProps) === null || _b === void 0 ? void 0 : _b.checked) === true;
|
|
121
|
+
}
|
|
122
|
+
return item.children.some(child => areSomeChildrenChecked(child));
|
|
123
|
+
};
|
|
124
|
+
// Find tree item by name
|
|
125
|
+
const findItemByName = (items, name) => {
|
|
126
|
+
for (const item of items) {
|
|
127
|
+
if (item.name === name) {
|
|
128
|
+
return item;
|
|
129
|
+
}
|
|
130
|
+
if (item.children) {
|
|
131
|
+
const found = findItemByName(item.children, name);
|
|
132
|
+
if (found) {
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
139
|
+
// Find parent item by child ID
|
|
140
|
+
const findParentById = (items, childId) => {
|
|
141
|
+
var _a;
|
|
142
|
+
for (const item of items) {
|
|
143
|
+
if ((_a = item.children) === null || _a === void 0 ? void 0 : _a.some(child => child.id === childId)) {
|
|
144
|
+
return item;
|
|
145
|
+
}
|
|
146
|
+
if (item.children) {
|
|
147
|
+
const found = findParentById(item.children, childId);
|
|
148
|
+
if (found) {
|
|
149
|
+
return found;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
// Update parent checkbox states based on children (recursive)
|
|
156
|
+
const onCheckParentHandle = (childId) => {
|
|
157
|
+
const parent = findParentById(treeData, childId);
|
|
158
|
+
if (!parent) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (parent.checkProps) {
|
|
162
|
+
const allChildrenChecked = areAllChildrenChecked(parent);
|
|
163
|
+
const someChildrenChecked = areSomeChildrenChecked(parent);
|
|
164
|
+
if (allChildrenChecked) {
|
|
165
|
+
parent.checkProps.checked = true;
|
|
166
|
+
}
|
|
167
|
+
else if (someChildrenChecked) {
|
|
168
|
+
parent.checkProps.checked = null;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
parent.checkProps.checked = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (parent.id) {
|
|
175
|
+
onCheckParentHandle(parent.id);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
// Check/uncheck item and all its children (recursive)
|
|
179
|
+
const onCheckHandle = (treeViewItem, checked) => {
|
|
180
|
+
var _a;
|
|
181
|
+
if (treeViewItem.checkProps) {
|
|
182
|
+
treeViewItem.checkProps.checked = checked;
|
|
183
|
+
}
|
|
184
|
+
(_a = treeViewItem.children) === null || _a === void 0 ? void 0 : _a.forEach(child => onCheckHandle(child, checked));
|
|
185
|
+
};
|
|
186
|
+
// Handle checkbox change event
|
|
187
|
+
const onCheck = (event, treeViewItem) => {
|
|
188
|
+
const checked = event.target.checked;
|
|
189
|
+
onCheckHandle(treeViewItem, checked);
|
|
190
|
+
if (treeViewItem.id) {
|
|
191
|
+
onCheckParentHandle(treeViewItem.id);
|
|
192
|
+
}
|
|
193
|
+
setTreeData(prev => [...prev]);
|
|
194
|
+
const selectedValues = getAllCheckedLeafItems(treeData);
|
|
195
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event, selectedValues);
|
|
196
|
+
if (onSelect) {
|
|
197
|
+
const selectedItems = getAllCheckedItems(treeData);
|
|
198
|
+
onSelect(selectedItems);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
// Clear a specific filter by name (when label chip is removed)
|
|
202
|
+
const onFilterSelectorClear = (itemName) => {
|
|
203
|
+
const treeViewItem = findItemByName(treeData, itemName);
|
|
204
|
+
if (!treeViewItem) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
onCheckHandle(treeViewItem, false);
|
|
208
|
+
if (treeViewItem.id) {
|
|
209
|
+
onCheckParentHandle(treeViewItem.id);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
// Uncheck all items in the tree
|
|
213
|
+
const uncheckAllItems = () => {
|
|
214
|
+
const updatedTreeData = uncheckRecursive(treeData);
|
|
215
|
+
setTreeData(updatedTreeData);
|
|
216
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(undefined, []);
|
|
217
|
+
};
|
|
218
|
+
const dropdown = (_jsx(Dropdown, { ref: menuRef, isOpen: isOpen, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsOpen(!isOpen), isExpanded: isOpen, children: title })), ouiaId: ouiaId, shouldFocusToggleOnSelect: true, children: _jsx(TreeView, { hasAnimations: true, data: treeData, onCheck: onCheck, hasCheckboxes: true, className: classes.dataViewTreeFilterTreeView }) }));
|
|
219
|
+
return (_jsx(ToolbarFilter, { "data-ouia-component-id": ouiaId, labels: value.map(item => ({ key: item, node: item })), deleteLabel: (_, label) => {
|
|
220
|
+
const labelKey = typeof label === 'string' ? label : label.key;
|
|
221
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(undefined, value.filter(item => item !== labelKey));
|
|
222
|
+
onFilterSelectorClear(labelKey);
|
|
223
|
+
}, deleteLabelGroup: uncheckAllItems, categoryName: title, showToolbarItem: showToolbarItem, children: dropdown }, filterId));
|
|
224
|
+
};
|
|
225
|
+
export default DataViewTreeFilter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|