@patternfly/react-data-view 6.3.0 → 6.4.0-prerelease.3
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/DataViewTable/DataViewTable.d.ts +4 -0
- package/dist/cjs/DataViewTable/DataViewTable.js +21 -1
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +2 -0
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.js +2 -2
- package/dist/cjs/DataViewTableHead/DataViewTableHead.d.ts +2 -0
- package/dist/cjs/DataViewTableHead/DataViewTableHead.js +5 -4
- package/dist/cjs/DataViewTableTree/DataViewTableTree.d.ts +2 -0
- package/dist/cjs/DataViewTableTree/DataViewTableTree.js +28 -1
- package/dist/cjs/DataViewTableTree/DataViewTableTree.test.js +4 -0
- package/dist/cjs/DataViewTh/DataViewTh.d.ts +32 -0
- package/dist/cjs/DataViewTh/DataViewTh.js +222 -0
- package/dist/cjs/Hooks/selection.d.ts +1 -0
- package/dist/cjs/Hooks/selection.js +5 -1
- package/dist/cjs/Hooks/selection.test.js +48 -0
- package/dist/cjs/InternalContext/InternalContext.d.ts +2 -0
- package/dist/esm/DataViewTable/DataViewTable.d.ts +4 -0
- package/dist/esm/DataViewTable/DataViewTable.js +21 -1
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +2 -0
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.js +2 -2
- package/dist/esm/DataViewTableHead/DataViewTableHead.d.ts +2 -0
- package/dist/esm/DataViewTableHead/DataViewTableHead.js +5 -4
- package/dist/esm/DataViewTableTree/DataViewTableTree.d.ts +2 -0
- package/dist/esm/DataViewTableTree/DataViewTableTree.js +29 -2
- package/dist/esm/DataViewTableTree/DataViewTableTree.test.js +4 -0
- package/dist/esm/DataViewTh/DataViewTh.d.ts +32 -0
- package/dist/esm/DataViewTh/DataViewTh.js +215 -0
- package/dist/esm/Hooks/selection.d.ts +1 -0
- package/dist/esm/Hooks/selection.js +5 -1
- package/dist/esm/Hooks/selection.test.js +48 -0
- package/dist/esm/InternalContext/InternalContext.d.ts +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableResizableColumnsExample.tsx +155 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableTreeExample.tsx +1 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +52 -14
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/SelectionExample.tsx +14 -3
- package/patternfly-docs/content/extensions/data-view/examples/Toolbar/Toolbar.md +1 -0
- package/release.config.js +1 -1
- package/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap +0 -2
- package/src/DataViewFilters/__snapshots__/DataViewFilters.test.tsx.snap +0 -2
- package/src/DataViewTable/DataViewTable.tsx +48 -27
- package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +10 -18
- package/src/DataViewTableBasic/DataViewTableBasic.tsx +4 -1
- package/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +20 -20
- package/src/DataViewTableHead/DataViewTableHead.tsx +24 -23
- package/src/DataViewTableHead/__snapshots__/DataViewTableHead.test.tsx.snap +15 -15
- package/src/DataViewTableTree/DataViewTableTree.test.tsx +9 -0
- package/src/DataViewTableTree/DataViewTableTree.tsx +35 -1
- package/src/DataViewTableTree/__snapshots__/DataViewTableTree.test.tsx.snap +977 -28
- package/src/DataViewTextFilter/__snapshots__/DataViewTextFilter.test.tsx.snap +0 -3
- package/src/DataViewTh/DataViewTh.tsx +342 -0
- package/src/DataViewToolbar/__snapshots__/DataViewToolbar.test.tsx.snap +0 -10
- package/src/Hooks/selection.test.tsx +65 -1
- package/src/Hooks/selection.ts +6 -1
- package/src/InternalContext/InternalContext.tsx +2 -0
|
@@ -63,7 +63,6 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
|
|
|
63
63
|
class="pf-v6-c-text-input-group__utilities"
|
|
64
64
|
>
|
|
65
65
|
<button
|
|
66
|
-
aria-disabled="false"
|
|
67
66
|
aria-label="Reset"
|
|
68
67
|
class="pf-v6-c-button pf-m-plain"
|
|
69
68
|
data-ouia-component-id="OUIA-Generated-Button-plain-2"
|
|
@@ -141,7 +140,6 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
|
|
|
141
140
|
class="pf-v6-c-label__actions"
|
|
142
141
|
>
|
|
143
142
|
<button
|
|
144
|
-
aria-disabled="false"
|
|
145
143
|
aria-label="Close initial value"
|
|
146
144
|
class="pf-v6-c-button pf-m-plain pf-m-no-padding"
|
|
147
145
|
data-ouia-component-id="OUIA-Generated-Button-plain-3"
|
|
@@ -182,7 +180,6 @@ exports[`DataViewTextFilter component should render correctly 1`] = `
|
|
|
182
180
|
class="pf-v6-c-toolbar__item"
|
|
183
181
|
>
|
|
184
182
|
<button
|
|
185
|
-
aria-disabled="false"
|
|
186
183
|
class="pf-v6-c-button pf-m-link pf-m-inline"
|
|
187
184
|
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
188
185
|
data-ouia-component-type="PF6/Button"
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FC,
|
|
3
|
+
useState,
|
|
4
|
+
MouseEvent as ReactMouseEvent,
|
|
5
|
+
TouchEvent as ReactTouchEvent,
|
|
6
|
+
KeyboardEvent as ReactKeyboardEvent,
|
|
7
|
+
ReactNode,
|
|
8
|
+
useCallback,
|
|
9
|
+
useRef,
|
|
10
|
+
useEffect,
|
|
11
|
+
Fragment
|
|
12
|
+
} from 'react';
|
|
13
|
+
import { Th, ThProps } from '@patternfly/react-table';
|
|
14
|
+
import { Button, getLanguageDirection } from '@patternfly/react-core';
|
|
15
|
+
import { createUseStyles } from 'react-jss';
|
|
16
|
+
|
|
17
|
+
import tableCellPaddingBlockEnd from '@patternfly/react-tokens/dist/esm/c_table_cell_PaddingBlockEnd';
|
|
18
|
+
import tableCellPaddingInlineEnd from '@patternfly/react-tokens/dist/esm/c_table_cell_PaddingInlineEnd';
|
|
19
|
+
import globalFontSizeBodyDefault from '@patternfly/react-tokens/dist/esm/t_global_font_size_body_default';
|
|
20
|
+
|
|
21
|
+
const ResizeIcon = () => (
|
|
22
|
+
<svg
|
|
23
|
+
className="pf-v6-svg"
|
|
24
|
+
viewBox="0 0 1024 1024"
|
|
25
|
+
fill="currentColor"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
role="img"
|
|
28
|
+
width="1em"
|
|
29
|
+
height="1em"
|
|
30
|
+
>
|
|
31
|
+
<path
|
|
32
|
+
fillRule="evenodd"
|
|
33
|
+
d="M52.7,529.8l190.5,161.9c18.6,15.9,50.5,4.7,50.5-17.8v-324c0-22.4-31.8-33.6-50.5-17.8L52.7,494.2c-11.5,9.8-11.5,25.8,0,35.6ZM480.8,12.9h62.4v998.3h-62.4V12.9ZM971.3,529.8l-190.5,161.9c-18.6,15.9-50.5,4.7-50.5-17.8v-324c0-22.4,31.8-33.6,50.5-17.8l190.5,161.9c11.5,9.8,11.5,25.8,0,35.6Z"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const useStyles = createUseStyles({
|
|
39
|
+
dataViewResizeableTh: {
|
|
40
|
+
[tableCellPaddingInlineEnd.name]: `calc(${globalFontSizeBodyDefault.var} * 2)`
|
|
41
|
+
},
|
|
42
|
+
dataViewResizableButton: {
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
insetInlineEnd: `calc(${globalFontSizeBodyDefault.var} / 2)`,
|
|
45
|
+
insetBlockEnd: tableCellPaddingBlockEnd.var,
|
|
46
|
+
cursor: 'grab',
|
|
47
|
+
'&:active': {
|
|
48
|
+
cursor: 'grabbing'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
export interface DataViewThResizableProps {
|
|
53
|
+
/** Whether the column is resizable */
|
|
54
|
+
isResizable?: boolean;
|
|
55
|
+
/** 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. */
|
|
56
|
+
onResize?: (
|
|
57
|
+
event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent,
|
|
58
|
+
id: string | number | undefined,
|
|
59
|
+
width: number
|
|
60
|
+
) => void;
|
|
61
|
+
/** Width of the column */
|
|
62
|
+
width?: number;
|
|
63
|
+
/** Minimum width of the column */
|
|
64
|
+
minWidth?: number;
|
|
65
|
+
/** Increment for keyboard navigation */
|
|
66
|
+
increment?: number;
|
|
67
|
+
/** Increment for keyboard navigation while shift is held */
|
|
68
|
+
shiftIncrement?: number;
|
|
69
|
+
/** Provides an accessible name for the resizable column via a human readable string. */
|
|
70
|
+
resizeButtonAriaLabel?: string;
|
|
71
|
+
/** Screenreader text that gets announced when the column is resized. */
|
|
72
|
+
screenReaderText?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface DataViewThProps {
|
|
75
|
+
/** Cell content */
|
|
76
|
+
content: ReactNode;
|
|
77
|
+
/** Resizable props */
|
|
78
|
+
resizableProps?: DataViewThResizableProps;
|
|
79
|
+
/** @hide Indicates whether the table has resizable columns */
|
|
80
|
+
hasResizableColumns?: boolean;
|
|
81
|
+
/** Props passed to Th */
|
|
82
|
+
thProps?: ThProps;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const DataViewTh: FC<DataViewThProps> = ({
|
|
86
|
+
content,
|
|
87
|
+
resizableProps = {},
|
|
88
|
+
hasResizableColumns = false,
|
|
89
|
+
thProps,
|
|
90
|
+
...props
|
|
91
|
+
}: DataViewThProps) => {
|
|
92
|
+
const thRef = useRef<HTMLTableCellElement>(null);
|
|
93
|
+
|
|
94
|
+
const [width, setWidth] = useState(resizableProps?.width ? resizableProps.width : 0);
|
|
95
|
+
// Tracks the current column width for the onResize callback, because the width state is not updated until after the resize is complete
|
|
96
|
+
const trackedWidth = useRef(0);
|
|
97
|
+
const classes = useStyles();
|
|
98
|
+
|
|
99
|
+
const isResizable = resizableProps?.isResizable || false;
|
|
100
|
+
const increment = resizableProps?.increment || 5;
|
|
101
|
+
const shiftIncrement = resizableProps?.shiftIncrement || 25;
|
|
102
|
+
const resizeButtonAriaLabel = resizableProps?.resizeButtonAriaLabel;
|
|
103
|
+
const onResize = resizableProps?.onResize || undefined;
|
|
104
|
+
const screenReaderText = resizableProps?.screenReaderText || `Column ${width.toFixed(0)} pixels`;
|
|
105
|
+
const dataViewThClassName = isResizable ? classes.dataViewResizeableTh : undefined;
|
|
106
|
+
|
|
107
|
+
const resizeButtonRef = useRef<HTMLButtonElement>(null);
|
|
108
|
+
const setInitialVals = useRef(true);
|
|
109
|
+
const dragOffset = useRef(0);
|
|
110
|
+
const isResizing = useRef(false);
|
|
111
|
+
const isInView = useRef(true);
|
|
112
|
+
|
|
113
|
+
if (isResizable && !resizeButtonAriaLabel) {
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.warn(
|
|
116
|
+
'DataViewTh: Missing resizeButtonAriaLabel. An aria label must be passed to each resizable column to provide a context specific label for its resize button.'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!isResizable) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const observed = resizeButtonRef.current;
|
|
126
|
+
const observer = new IntersectionObserver(
|
|
127
|
+
([entry]) => {
|
|
128
|
+
isInView.current = entry.isIntersecting;
|
|
129
|
+
},
|
|
130
|
+
{ threshold: 0.3 }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (observed) {
|
|
134
|
+
observer.observe(observed);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
if (observed) {
|
|
139
|
+
observer.unobserve(observed);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}, []);
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if ((isResizable || hasResizableColumns) && setInitialVals.current && width === 0) {
|
|
146
|
+
setWidth(thRef.current?.getBoundingClientRect().width || 0);
|
|
147
|
+
setInitialVals.current = false;
|
|
148
|
+
}
|
|
149
|
+
}, [isResizable, hasResizableColumns, setInitialVals]);
|
|
150
|
+
|
|
151
|
+
const setDragOffset = (e: ReactMouseEvent | ReactTouchEvent) => {
|
|
152
|
+
const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl';
|
|
153
|
+
const startingMousePos = 'clientX' in e ? e.clientX : e.touches[0].clientX;
|
|
154
|
+
const startingColumnPos =
|
|
155
|
+
(isRTL ? thRef.current?.getBoundingClientRect().left : thRef.current?.getBoundingClientRect().right) || 0;
|
|
156
|
+
|
|
157
|
+
dragOffset.current = startingColumnPos - startingMousePos;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const handleMousedown = (e: ReactMouseEvent) => {
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
document.addEventListener('mousemove', callbackMouseMove);
|
|
164
|
+
document.addEventListener('mouseup', callbackMouseUp);
|
|
165
|
+
|
|
166
|
+
// When a user begins resizing a column, set the drag offset so the drag motion aligns with the column edge
|
|
167
|
+
if (dragOffset.current === 0) {
|
|
168
|
+
setDragOffset(e);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
isResizing.current = true;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
175
|
+
const mousePos = e.clientX;
|
|
176
|
+
|
|
177
|
+
handleControlMove(e, dragOffset.current + mousePos);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const handleTouchStart = (e: ReactTouchEvent) => {
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
document.addEventListener('touchmove', callbackTouchMove, { passive: false });
|
|
183
|
+
document.addEventListener('touchend', callbackTouchEnd);
|
|
184
|
+
|
|
185
|
+
// When a user begins resizing a column, set the drag offset so the drag motion aligns with the column edge
|
|
186
|
+
if (dragOffset.current === 0) {
|
|
187
|
+
setDragOffset(e);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
isResizing.current = true;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handleTouchMove = (e: TouchEvent) => {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
e.stopImmediatePropagation();
|
|
196
|
+
const touchPos = e.touches[0].clientX;
|
|
197
|
+
|
|
198
|
+
handleControlMove(e, touchPos);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const handleControlMove = (e: MouseEvent | TouchEvent, controlPosition: number) => {
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
|
|
204
|
+
if (!isResizing.current) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const columnRect = thRef.current?.getBoundingClientRect();
|
|
209
|
+
if (columnRect === undefined) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const mousePos = controlPosition;
|
|
214
|
+
const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl';
|
|
215
|
+
let newSize = isRTL ? columnRect?.right - mousePos : mousePos - columnRect?.left;
|
|
216
|
+
|
|
217
|
+
// Prevent the column from shrinking below a specified minimum width
|
|
218
|
+
if (resizableProps.minWidth && newSize < resizableProps.minWidth) {
|
|
219
|
+
newSize = resizableProps.minWidth;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
thRef.current?.style.setProperty('min-width', newSize + 'px');
|
|
223
|
+
trackedWidth.current = newSize;
|
|
224
|
+
setWidth(newSize);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleMouseup = (e: MouseEvent) => {
|
|
228
|
+
if (!isResizing.current) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Reset the isResizing and dragOffset values to their initial states
|
|
232
|
+
isResizing.current = false;
|
|
233
|
+
dragOffset.current = 0;
|
|
234
|
+
|
|
235
|
+
// Call the onResize callback with the new width
|
|
236
|
+
onResize && onResize(e, thProps?.id, trackedWidth.current);
|
|
237
|
+
|
|
238
|
+
// Handle scroll into view when column drag button is moved off screen
|
|
239
|
+
if (resizeButtonRef.current && !isInView.current) {
|
|
240
|
+
resizeButtonRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
document.removeEventListener('mousemove', callbackMouseMove);
|
|
244
|
+
document.removeEventListener('mouseup', callbackMouseUp);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const handleTouchEnd = (e: TouchEvent) => {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
if (!isResizing) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Reset the isResizing and dragOffset values to their initial states
|
|
253
|
+
isResizing.current = false;
|
|
254
|
+
dragOffset.current = 0;
|
|
255
|
+
|
|
256
|
+
// Call the onResize callback with the new width
|
|
257
|
+
onResize && onResize(e, thProps?.id, trackedWidth.current);
|
|
258
|
+
|
|
259
|
+
document.removeEventListener('touchmove', callbackTouchMove);
|
|
260
|
+
document.removeEventListener('touchend', callbackTouchEnd);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const callbackMouseMove = useCallback(handleMouseMove, []);
|
|
264
|
+
const callbackTouchEnd = useCallback(handleTouchEnd, []);
|
|
265
|
+
const callbackTouchMove = useCallback(handleTouchMove, []);
|
|
266
|
+
const callbackMouseUp = useCallback(handleMouseup, []);
|
|
267
|
+
|
|
268
|
+
const handleKeys = (e: ReactKeyboardEvent) => {
|
|
269
|
+
const key = e.key;
|
|
270
|
+
|
|
271
|
+
if (key === 'Tab') {
|
|
272
|
+
isResizing.current = false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (key !== 'ArrowLeft' && key !== 'ArrowRight') {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
|
|
280
|
+
isResizing.current = true;
|
|
281
|
+
const isRTL = getLanguageDirection(thRef.current as HTMLElement) === 'rtl';
|
|
282
|
+
const columnRect = thRef.current?.getBoundingClientRect();
|
|
283
|
+
|
|
284
|
+
let newSize = columnRect?.width || 0;
|
|
285
|
+
let delta = 0;
|
|
286
|
+
const _increment = e.shiftKey ? shiftIncrement : increment;
|
|
287
|
+
|
|
288
|
+
if (key === 'ArrowRight') {
|
|
289
|
+
if (isRTL) {
|
|
290
|
+
delta = -_increment;
|
|
291
|
+
} else {
|
|
292
|
+
delta = _increment;
|
|
293
|
+
}
|
|
294
|
+
} else if (key === 'ArrowLeft') {
|
|
295
|
+
if (isRTL) {
|
|
296
|
+
delta = _increment;
|
|
297
|
+
} else {
|
|
298
|
+
delta = -_increment;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
newSize = newSize + delta;
|
|
302
|
+
|
|
303
|
+
thRef.current?.style.setProperty('min-width', newSize + 'px');
|
|
304
|
+
setWidth(newSize);
|
|
305
|
+
onResize && onResize(e, thProps?.id, newSize);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const resizableContent = (
|
|
309
|
+
<Fragment>
|
|
310
|
+
<div aria-live="polite" className="pf-v6-screen-reader">
|
|
311
|
+
{screenReaderText}
|
|
312
|
+
</div>
|
|
313
|
+
<Button
|
|
314
|
+
ref={resizeButtonRef}
|
|
315
|
+
variant="plain"
|
|
316
|
+
hasNoPadding
|
|
317
|
+
icon={<ResizeIcon />}
|
|
318
|
+
onMouseDown={handleMousedown}
|
|
319
|
+
onKeyDown={handleKeys}
|
|
320
|
+
onTouchStart={handleTouchStart}
|
|
321
|
+
aria-label={resizeButtonAriaLabel}
|
|
322
|
+
className={classes.dataViewResizableButton}
|
|
323
|
+
/>
|
|
324
|
+
</Fragment>
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<Th
|
|
329
|
+
{...thProps}
|
|
330
|
+
{...props}
|
|
331
|
+
style={width > 0 ? { minWidth: width } : undefined}
|
|
332
|
+
ref={thRef}
|
|
333
|
+
modifier="truncate"
|
|
334
|
+
className={dataViewThClassName}
|
|
335
|
+
{...(isResizable && { additionalContent: resizableContent })}
|
|
336
|
+
>
|
|
337
|
+
{content}
|
|
338
|
+
</Th>
|
|
339
|
+
);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export default DataViewTh;
|
|
@@ -106,7 +106,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
106
106
|
class="pf-v6-c-pagination__nav-control pf-m-first"
|
|
107
107
|
>
|
|
108
108
|
<button
|
|
109
|
-
aria-disabled="false"
|
|
110
109
|
aria-label="Go to first page"
|
|
111
110
|
class="pf-v6-c-button pf-m-plain"
|
|
112
111
|
data-action="first"
|
|
@@ -139,7 +138,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
139
138
|
class="pf-v6-c-pagination__nav-control"
|
|
140
139
|
>
|
|
141
140
|
<button
|
|
142
|
-
aria-disabled="false"
|
|
143
141
|
aria-label="Go to previous page"
|
|
144
142
|
class="pf-v6-c-button pf-m-plain"
|
|
145
143
|
data-action="previous"
|
|
@@ -191,7 +189,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
191
189
|
class="pf-v6-c-pagination__nav-control"
|
|
192
190
|
>
|
|
193
191
|
<button
|
|
194
|
-
aria-disabled="false"
|
|
195
192
|
aria-label="Go to next page"
|
|
196
193
|
class="pf-v6-c-button pf-m-plain"
|
|
197
194
|
data-action="next"
|
|
@@ -223,7 +220,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
223
220
|
class="pf-v6-c-pagination__nav-control pf-m-last"
|
|
224
221
|
>
|
|
225
222
|
<button
|
|
226
|
-
aria-disabled="false"
|
|
227
223
|
aria-label="Go to last page"
|
|
228
224
|
class="pf-v6-c-button pf-m-plain"
|
|
229
225
|
data-action="last"
|
|
@@ -270,7 +266,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
270
266
|
class="pf-v6-c-toolbar__item"
|
|
271
267
|
>
|
|
272
268
|
<button
|
|
273
|
-
aria-disabled="false"
|
|
274
269
|
class="pf-v6-c-button pf-m-link pf-m-inline"
|
|
275
270
|
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
276
271
|
data-ouia-component-type="PF6/Button"
|
|
@@ -391,7 +386,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
391
386
|
class="pf-v6-c-pagination__nav-control pf-m-first"
|
|
392
387
|
>
|
|
393
388
|
<button
|
|
394
|
-
aria-disabled="false"
|
|
395
389
|
aria-label="Go to first page"
|
|
396
390
|
class="pf-v6-c-button pf-m-plain"
|
|
397
391
|
data-action="first"
|
|
@@ -424,7 +418,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
424
418
|
class="pf-v6-c-pagination__nav-control"
|
|
425
419
|
>
|
|
426
420
|
<button
|
|
427
|
-
aria-disabled="false"
|
|
428
421
|
aria-label="Go to previous page"
|
|
429
422
|
class="pf-v6-c-button pf-m-plain"
|
|
430
423
|
data-action="previous"
|
|
@@ -476,7 +469,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
476
469
|
class="pf-v6-c-pagination__nav-control"
|
|
477
470
|
>
|
|
478
471
|
<button
|
|
479
|
-
aria-disabled="false"
|
|
480
472
|
aria-label="Go to next page"
|
|
481
473
|
class="pf-v6-c-button pf-m-plain"
|
|
482
474
|
data-action="next"
|
|
@@ -508,7 +500,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
508
500
|
class="pf-v6-c-pagination__nav-control pf-m-last"
|
|
509
501
|
>
|
|
510
502
|
<button
|
|
511
|
-
aria-disabled="false"
|
|
512
503
|
aria-label="Go to last page"
|
|
513
504
|
class="pf-v6-c-button pf-m-plain"
|
|
514
505
|
data-action="last"
|
|
@@ -555,7 +546,6 @@ exports[`DataViewToolbar component should render correctly 1`] = `
|
|
|
555
546
|
class="pf-v6-c-toolbar__item"
|
|
556
547
|
>
|
|
557
548
|
<button
|
|
558
|
-
aria-disabled="false"
|
|
559
549
|
class="pf-v6-c-button pf-m-link pf-m-inline"
|
|
560
550
|
data-ouia-component-id="DataViewToolbar-clear-all-filters"
|
|
561
551
|
data-ouia-component-type="PF6/Button"
|
|
@@ -9,6 +9,7 @@ describe('useDataViewSelection', () => {
|
|
|
9
9
|
selected: [],
|
|
10
10
|
onSelect: expect.any(Function),
|
|
11
11
|
isSelected: expect.any(Function),
|
|
12
|
+
setSelected: expect.any(Function),
|
|
12
13
|
})
|
|
13
14
|
});
|
|
14
15
|
|
|
@@ -19,6 +20,7 @@ describe('useDataViewSelection', () => {
|
|
|
19
20
|
selected: initialSelected,
|
|
20
21
|
onSelect: expect.any(Function),
|
|
21
22
|
isSelected: expect.any(Function),
|
|
23
|
+
setSelected: expect.any(Function),
|
|
22
24
|
})
|
|
23
25
|
});
|
|
24
26
|
|
|
@@ -49,4 +51,66 @@ describe('useDataViewSelection', () => {
|
|
|
49
51
|
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(true);
|
|
50
52
|
expect(result.current.isSelected({ id: 3, name: 'test2' })).toBe(false);
|
|
51
53
|
});
|
|
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
|
+
|
|
65
|
+
it('should set selected items directly using setSelected - objects', async () => {
|
|
66
|
+
const initialSelected = [ { id: 1, name: 'test1' } ];
|
|
67
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
68
|
+
|
|
69
|
+
const newSelected = [ { id: 2, name: 'test2' }, { id: 3, name: 'test3' } ];
|
|
70
|
+
|
|
71
|
+
await act(async () => {
|
|
72
|
+
result.current.setSelected(newSelected);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should set selected items directly using setSelected - strings', async () => {
|
|
79
|
+
const initialSelected = [ 'test1', 'test2' ];
|
|
80
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a === b }))
|
|
81
|
+
|
|
82
|
+
const newSelected = [ 'test3', 'test4', 'test5' ];
|
|
83
|
+
|
|
84
|
+
await act(async () => {
|
|
85
|
+
result.current.setSelected(newSelected);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.current.selected).toEqual(newSelected);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should clear all selections using setSelected with empty array', async () => {
|
|
92
|
+
const initialSelected = [ { id: 1, name: 'test1' }, { id: 2, name: 'test2' } ];
|
|
93
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
94
|
+
|
|
95
|
+
await act(async () => {
|
|
96
|
+
result.current.setSelected([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result.current.selected).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should update isSelected correctly after using setSelected', async () => {
|
|
103
|
+
const initialSelected = [ { id: 1, name: 'test1' } ];
|
|
104
|
+
const { result } = renderHook(() => useDataViewSelection({ initialSelected, matchOption: (a, b) => a.id === b.id }))
|
|
105
|
+
|
|
106
|
+
const newSelected = [ { id: 2, name: 'test2' }, { id: 3, name: 'test3' } ];
|
|
107
|
+
|
|
108
|
+
await act(async () => {
|
|
109
|
+
result.current.setSelected(newSelected);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(result.current.isSelected({ id: 1, name: 'test1' })).toBe(false);
|
|
113
|
+
expect(result.current.isSelected({ id: 2, name: 'test2' })).toBe(true);
|
|
114
|
+
expect(result.current.isSelected({ id: 3, name: 'test3' })).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/Hooks/selection.ts
CHANGED
|
@@ -24,9 +24,14 @@ export const useDataViewSelection = (props?: UseDataViewSelectionProps) => {
|
|
|
24
24
|
|
|
25
25
|
const isSelected = (item: any): boolean => Boolean(selected.find(selected => matchOption(selected, item)));
|
|
26
26
|
|
|
27
|
+
const setSelectedItems = (items: any[]) => {
|
|
28
|
+
setSelected(items);
|
|
29
|
+
};
|
|
30
|
+
|
|
27
31
|
return {
|
|
28
32
|
selected,
|
|
29
33
|
onSelect,
|
|
30
|
-
isSelected
|
|
34
|
+
isSelected,
|
|
35
|
+
setSelected: setSelectedItems
|
|
31
36
|
};
|
|
32
37
|
};
|
|
@@ -6,6 +6,8 @@ export interface DataViewSelection {
|
|
|
6
6
|
onSelect: (isSelecting: boolean, items?: any[] | any) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
7
7
|
/** Checks if a specific item is currently selected */
|
|
8
8
|
isSelected: (item: any) => boolean; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
9
|
+
/** Directly sets the selected items */
|
|
10
|
+
setSelected?: (items: any[]) => void; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
9
11
|
/** Determines if selection is disabled for a given item */
|
|
10
12
|
isSelectDisabled?: (item: any) => boolean; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
11
13
|
}
|