@wordpress/dataviews 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +49 -18
- package/build/constants.js +28 -10
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +3 -7
- package/build/dataviews.js.map +1 -1
- package/build/filter-and-sort-data-view.js +147 -0
- package/build/filter-and-sort-data-view.js.map +1 -0
- package/build/filter-summary.js +33 -12
- package/build/filter-summary.js.map +1 -1
- package/build/filters.js +11 -16
- package/build/filters.js.map +1 -1
- package/build/index.js +3 -9
- package/build/index.js.map +1 -1
- package/build/item-actions.js +20 -39
- package/build/item-actions.js.map +1 -1
- package/build/normalize-fields.js +25 -0
- package/build/normalize-fields.js.map +1 -0
- package/build/pagination.js +2 -2
- package/build/pagination.js.map +1 -1
- package/build/search-widget.js +34 -10
- package/build/search-widget.js.map +1 -1
- package/build/utils.js +25 -67
- package/build/utils.js.map +1 -1
- package/build/view-grid.js +25 -12
- package/build/view-grid.js.map +1 -1
- package/build/view-list.js +122 -58
- package/build/view-list.js.map +1 -1
- package/build/view-table.js +53 -8
- package/build/view-table.js.map +1 -1
- package/build-module/constants.js +27 -9
- package/build-module/constants.js.map +1 -1
- package/build-module/dataviews.js +3 -7
- package/build-module/dataviews.js.map +1 -1
- package/build-module/filter-and-sort-data-view.js +139 -0
- package/build-module/filter-and-sort-data-view.js.map +1 -0
- package/build-module/filter-summary.js +34 -13
- package/build-module/filter-summary.js.map +1 -1
- package/build-module/filters.js +12 -17
- package/build-module/filters.js.map +1 -1
- package/build-module/index.js +1 -1
- package/build-module/index.js.map +1 -1
- package/build-module/item-actions.js +20 -39
- package/build-module/item-actions.js.map +1 -1
- package/build-module/normalize-fields.js +19 -0
- package/build-module/normalize-fields.js.map +1 -0
- package/build-module/pagination.js +2 -2
- package/build-module/pagination.js.map +1 -1
- package/build-module/search-widget.js +35 -11
- package/build-module/search-widget.js.map +1 -1
- package/build-module/utils.js +25 -66
- package/build-module/utils.js.map +1 -1
- package/build-module/view-grid.js +26 -13
- package/build-module/view-grid.js.map +1 -1
- package/build-module/view-list.js +124 -60
- package/build-module/view-list.js.map +1 -1
- package/build-module/view-table.js +55 -10
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +41 -11
- package/build-style/style.css +41 -11
- package/package.json +11 -11
- package/src/constants.js +35 -9
- package/src/dataviews.js +3 -7
- package/src/filter-and-sort-data-view.js +154 -0
- package/src/filter-summary.js +76 -23
- package/src/filters.js +20 -26
- package/src/index.js +1 -1
- package/src/item-actions.js +19 -55
- package/src/normalize-fields.js +17 -0
- package/src/pagination.js +2 -2
- package/src/search-widget.js +63 -21
- package/src/stories/fixtures.js +87 -2
- package/src/stories/index.story.js +5 -74
- package/src/style.scss +53 -14
- package/src/test/filter-and-sort-data-view.js +276 -0
- package/src/utils.js +38 -56
- package/src/view-grid.js +36 -11
- package/src/view-list.js +159 -69
- package/src/view-table.js +71 -9
package/src/view-list.js
CHANGED
|
@@ -6,17 +6,135 @@ import classNames from 'classnames';
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
8
8
|
*/
|
|
9
|
-
import { useAsyncList } from '@wordpress/compose';
|
|
9
|
+
import { useAsyncList, useInstanceId } from '@wordpress/compose';
|
|
10
10
|
import {
|
|
11
11
|
__experimentalHStack as HStack,
|
|
12
12
|
__experimentalVStack as VStack,
|
|
13
|
+
privateApis as componentsPrivateApis,
|
|
13
14
|
Button,
|
|
14
15
|
Spinner,
|
|
16
|
+
VisuallyHidden,
|
|
15
17
|
} from '@wordpress/components';
|
|
16
|
-
import {
|
|
18
|
+
import { useCallback, useEffect, useRef } from '@wordpress/element';
|
|
17
19
|
import { info } from '@wordpress/icons';
|
|
18
20
|
import { __ } from '@wordpress/i18n';
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Internal dependencies
|
|
24
|
+
*/
|
|
25
|
+
import { unlock } from './lock-unlock';
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
useCompositeStoreV2: useCompositeStore,
|
|
29
|
+
CompositeV2: Composite,
|
|
30
|
+
CompositeItemV2: CompositeItem,
|
|
31
|
+
CompositeRowV2: CompositeRow,
|
|
32
|
+
} = unlock( componentsPrivateApis );
|
|
33
|
+
|
|
34
|
+
function ListItem( {
|
|
35
|
+
id,
|
|
36
|
+
item,
|
|
37
|
+
isSelected,
|
|
38
|
+
onSelect,
|
|
39
|
+
onDetailsChange,
|
|
40
|
+
mediaField,
|
|
41
|
+
primaryField,
|
|
42
|
+
visibleFields,
|
|
43
|
+
} ) {
|
|
44
|
+
const itemRef = useRef( null );
|
|
45
|
+
const labelId = `${ id }-label`;
|
|
46
|
+
const descriptionId = `${ id }-description`;
|
|
47
|
+
|
|
48
|
+
useEffect( () => {
|
|
49
|
+
if ( isSelected ) {
|
|
50
|
+
itemRef.current?.scrollIntoView( {
|
|
51
|
+
behavior: 'auto',
|
|
52
|
+
block: 'nearest',
|
|
53
|
+
inline: 'nearest',
|
|
54
|
+
} );
|
|
55
|
+
}
|
|
56
|
+
}, [ isSelected ] );
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<CompositeRow
|
|
60
|
+
ref={ itemRef }
|
|
61
|
+
render={ <li /> }
|
|
62
|
+
role="row"
|
|
63
|
+
className={ classNames( {
|
|
64
|
+
'is-selected': isSelected,
|
|
65
|
+
} ) }
|
|
66
|
+
>
|
|
67
|
+
<HStack className="dataviews-view-list__item-wrapper">
|
|
68
|
+
<div role="gridcell">
|
|
69
|
+
<CompositeItem
|
|
70
|
+
render={ <div /> }
|
|
71
|
+
role="button"
|
|
72
|
+
id={ id }
|
|
73
|
+
aria-pressed={ isSelected }
|
|
74
|
+
aria-labelledby={ labelId }
|
|
75
|
+
aria-describedby={ descriptionId }
|
|
76
|
+
className="dataviews-view-list__item"
|
|
77
|
+
onClick={ () => onSelect( item ) }
|
|
78
|
+
>
|
|
79
|
+
<HStack
|
|
80
|
+
spacing={ 3 }
|
|
81
|
+
justify="start"
|
|
82
|
+
alignment="flex-start"
|
|
83
|
+
>
|
|
84
|
+
<div className="dataviews-view-list__media-wrapper">
|
|
85
|
+
{ mediaField?.render( { item } ) || (
|
|
86
|
+
<div className="dataviews-view-list__media-placeholder"></div>
|
|
87
|
+
) }
|
|
88
|
+
</div>
|
|
89
|
+
<VStack spacing={ 1 }>
|
|
90
|
+
<span
|
|
91
|
+
className="dataviews-view-list__primary-field"
|
|
92
|
+
id={ labelId }
|
|
93
|
+
>
|
|
94
|
+
{ primaryField?.render( { item } ) }
|
|
95
|
+
</span>
|
|
96
|
+
<div
|
|
97
|
+
className="dataviews-view-list__fields"
|
|
98
|
+
id={ descriptionId }
|
|
99
|
+
>
|
|
100
|
+
{ visibleFields.map( ( field ) => (
|
|
101
|
+
<div
|
|
102
|
+
key={ field.id }
|
|
103
|
+
className="dataviews-view-list__field"
|
|
104
|
+
>
|
|
105
|
+
<VisuallyHidden
|
|
106
|
+
as="span"
|
|
107
|
+
className="dataviews-view-list__field-label"
|
|
108
|
+
>
|
|
109
|
+
{ field.header }
|
|
110
|
+
</VisuallyHidden>
|
|
111
|
+
<span className="dataviews-view-list__field-value">
|
|
112
|
+
{ field.render( { item } ) }
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
) ) }
|
|
116
|
+
</div>
|
|
117
|
+
</VStack>
|
|
118
|
+
</HStack>
|
|
119
|
+
</CompositeItem>
|
|
120
|
+
</div>
|
|
121
|
+
{ onDetailsChange && (
|
|
122
|
+
<div role="gridcell">
|
|
123
|
+
<CompositeItem
|
|
124
|
+
render={ <Button /> }
|
|
125
|
+
className="dataviews-view-list__details-button"
|
|
126
|
+
onClick={ () => onDetailsChange( [ item ] ) }
|
|
127
|
+
icon={ info }
|
|
128
|
+
label={ __( 'View details' ) }
|
|
129
|
+
size="compact"
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
) }
|
|
133
|
+
</HStack>
|
|
134
|
+
</CompositeRow>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
20
138
|
export default function ViewList( {
|
|
21
139
|
view,
|
|
22
140
|
fields,
|
|
@@ -27,9 +145,15 @@ export default function ViewList( {
|
|
|
27
145
|
onDetailsChange,
|
|
28
146
|
selection,
|
|
29
147
|
deferredRendering,
|
|
148
|
+
id: preferredId,
|
|
30
149
|
} ) {
|
|
150
|
+
const baseId = useInstanceId( ViewList, 'view-list', preferredId );
|
|
31
151
|
const shownData = useAsyncList( data, { step: 3 } );
|
|
32
152
|
const usedData = deferredRendering ? shownData : data;
|
|
153
|
+
const selectedItem = usedData?.findLast( ( item ) =>
|
|
154
|
+
selection.includes( item.id )
|
|
155
|
+
);
|
|
156
|
+
|
|
33
157
|
const mediaField = fields.find(
|
|
34
158
|
( field ) => field.id === view.layout.mediaField
|
|
35
159
|
);
|
|
@@ -44,12 +168,19 @@ export default function ViewList( {
|
|
|
44
168
|
)
|
|
45
169
|
);
|
|
46
170
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
171
|
+
const onSelect = useCallback(
|
|
172
|
+
( item ) => onSelectionChange( [ item ] ),
|
|
173
|
+
[ onSelectionChange ]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const getItemDomId = useCallback(
|
|
177
|
+
( item ) => ( item ? `${ baseId }-${ getItemId( item ) }` : undefined ),
|
|
178
|
+
[ baseId, getItemId ]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const store = useCompositeStore( {
|
|
182
|
+
defaultActiveId: getItemDomId( selectedItem ),
|
|
183
|
+
} );
|
|
53
184
|
|
|
54
185
|
const hasData = usedData?.length;
|
|
55
186
|
if ( ! hasData ) {
|
|
@@ -68,70 +199,29 @@ export default function ViewList( {
|
|
|
68
199
|
}
|
|
69
200
|
|
|
70
201
|
return (
|
|
71
|
-
<
|
|
202
|
+
<Composite
|
|
203
|
+
id={ baseId }
|
|
204
|
+
render={ <ul /> }
|
|
205
|
+
className="dataviews-view-list"
|
|
206
|
+
role="grid"
|
|
207
|
+
store={ store }
|
|
208
|
+
>
|
|
72
209
|
{ usedData.map( ( item ) => {
|
|
210
|
+
const id = getItemDomId( item );
|
|
73
211
|
return (
|
|
74
|
-
<
|
|
75
|
-
key={
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
onKeyDown={ onEnter( item ) }
|
|
86
|
-
className="dataviews-view-list__item"
|
|
87
|
-
onClick={ () => onSelectionChange( [ item ] ) }
|
|
88
|
-
>
|
|
89
|
-
<HStack
|
|
90
|
-
spacing={ 3 }
|
|
91
|
-
justify="start"
|
|
92
|
-
alignment="flex-start"
|
|
93
|
-
>
|
|
94
|
-
<div className="dataviews-view-list__media-wrapper">
|
|
95
|
-
{ mediaField?.render( { item } ) || (
|
|
96
|
-
<div className="dataviews-view-list__media-placeholder"></div>
|
|
97
|
-
) }
|
|
98
|
-
</div>
|
|
99
|
-
<VStack spacing={ 1 }>
|
|
100
|
-
<span className="dataviews-view-list__primary-field">
|
|
101
|
-
{ primaryField?.render( { item } ) }
|
|
102
|
-
</span>
|
|
103
|
-
<div className="dataviews-view-list__fields">
|
|
104
|
-
{ visibleFields.map( ( field ) => {
|
|
105
|
-
return (
|
|
106
|
-
<span
|
|
107
|
-
key={ field.id }
|
|
108
|
-
className="dataviews-view-list__field"
|
|
109
|
-
>
|
|
110
|
-
{ field.render( {
|
|
111
|
-
item,
|
|
112
|
-
} ) }
|
|
113
|
-
</span>
|
|
114
|
-
);
|
|
115
|
-
} ) }
|
|
116
|
-
</div>
|
|
117
|
-
</VStack>
|
|
118
|
-
</HStack>
|
|
119
|
-
</div>
|
|
120
|
-
{ onDetailsChange && (
|
|
121
|
-
<Button
|
|
122
|
-
className="dataviews-view-list__details-button"
|
|
123
|
-
onClick={ () =>
|
|
124
|
-
onDetailsChange( [ item ] )
|
|
125
|
-
}
|
|
126
|
-
icon={ info }
|
|
127
|
-
label={ __( 'View details' ) }
|
|
128
|
-
size="compact"
|
|
129
|
-
/>
|
|
130
|
-
) }
|
|
131
|
-
</HStack>
|
|
132
|
-
</li>
|
|
212
|
+
<ListItem
|
|
213
|
+
key={ id }
|
|
214
|
+
id={ id }
|
|
215
|
+
item={ item }
|
|
216
|
+
isSelected={ item === selectedItem }
|
|
217
|
+
onSelect={ onSelect }
|
|
218
|
+
onDetailsChange={ onDetailsChange }
|
|
219
|
+
mediaField={ mediaField }
|
|
220
|
+
primaryField={ primaryField }
|
|
221
|
+
visibleFields={ visibleFields }
|
|
222
|
+
/>
|
|
133
223
|
);
|
|
134
224
|
} ) }
|
|
135
|
-
</
|
|
225
|
+
</Composite>
|
|
136
226
|
);
|
|
137
227
|
}
|
package/src/view-table.js
CHANGED
|
@@ -22,9 +22,9 @@ import {
|
|
|
22
22
|
useId,
|
|
23
23
|
useRef,
|
|
24
24
|
useState,
|
|
25
|
+
useMemo,
|
|
25
26
|
Children,
|
|
26
27
|
Fragment,
|
|
27
|
-
useMemo,
|
|
28
28
|
} from '@wordpress/element';
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -34,7 +34,7 @@ import SingleSelectionCheckbox from './single-selection-checkbox';
|
|
|
34
34
|
import { unlock } from './lock-unlock';
|
|
35
35
|
import ItemActions from './item-actions';
|
|
36
36
|
import { sanitizeOperators } from './utils';
|
|
37
|
-
import {
|
|
37
|
+
import { SORTING_DIRECTIONS } from './constants';
|
|
38
38
|
import {
|
|
39
39
|
useSomeItemHasAPossibleBulkAction,
|
|
40
40
|
useHasAPossibleBulkAction,
|
|
@@ -49,7 +49,7 @@ const {
|
|
|
49
49
|
DropdownMenuSeparatorV2: DropdownMenuSeparator,
|
|
50
50
|
} = unlock( componentsPrivateApis );
|
|
51
51
|
|
|
52
|
-
function
|
|
52
|
+
function WithDropDownMenuSeparators( { children } ) {
|
|
53
53
|
return Children.toArray( children )
|
|
54
54
|
.filter( Boolean )
|
|
55
55
|
.map( ( child, i ) => (
|
|
@@ -76,7 +76,7 @@ const HeaderMenu = forwardRef( function HeaderMenu(
|
|
|
76
76
|
// 3. If it's not primary. If it is, it should be already visible.
|
|
77
77
|
const canAddFilter =
|
|
78
78
|
! view.filters?.some( ( _filter ) => field.id === _filter.field ) &&
|
|
79
|
-
field.
|
|
79
|
+
!! field.elements?.length &&
|
|
80
80
|
!! operators.length &&
|
|
81
81
|
! field.filterBy?.isPrimary;
|
|
82
82
|
if ( ! isSortable && ! isHidable && ! canAddFilter ) {
|
|
@@ -102,7 +102,7 @@ const HeaderMenu = forwardRef( function HeaderMenu(
|
|
|
102
102
|
}
|
|
103
103
|
style={ { minWidth: '240px' } }
|
|
104
104
|
>
|
|
105
|
-
<
|
|
105
|
+
<WithDropDownMenuSeparators>
|
|
106
106
|
{ isSortable && (
|
|
107
107
|
<DropdownMenuGroup>
|
|
108
108
|
{ Object.entries( SORTING_DIRECTIONS ).map(
|
|
@@ -187,7 +187,7 @@ const HeaderMenu = forwardRef( function HeaderMenu(
|
|
|
187
187
|
</DropdownMenuItemLabel>
|
|
188
188
|
</DropdownMenuItem>
|
|
189
189
|
) }
|
|
190
|
-
</
|
|
190
|
+
</WithDropDownMenuSeparators>
|
|
191
191
|
</DropdownMenu>
|
|
192
192
|
);
|
|
193
193
|
} );
|
|
@@ -237,12 +237,63 @@ function TableRow( {
|
|
|
237
237
|
data,
|
|
238
238
|
} ) {
|
|
239
239
|
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
|
|
240
|
+
const isSelected = selection.includes( id );
|
|
241
|
+
|
|
242
|
+
const [ isHovered, setIsHovered ] = useState( false );
|
|
243
|
+
|
|
244
|
+
const handleMouseEnter = () => {
|
|
245
|
+
setIsHovered( true );
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const handleMouseLeave = () => {
|
|
249
|
+
setIsHovered( false );
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Will be set to true if `onTouchStart` fires. This happens before
|
|
253
|
+
// `onClick` and can be used to exclude touchscreen devices from certain
|
|
254
|
+
// behaviours.
|
|
255
|
+
const isTouchDevice = useRef( false );
|
|
256
|
+
|
|
240
257
|
return (
|
|
241
258
|
<tr
|
|
242
259
|
className={ classnames( 'dataviews-view-table__row', {
|
|
243
|
-
'is-selected':
|
|
244
|
-
|
|
260
|
+
'is-selected': hasPossibleBulkAction && isSelected,
|
|
261
|
+
'is-hovered': isHovered,
|
|
262
|
+
'has-bulk-actions': hasPossibleBulkAction,
|
|
245
263
|
} ) }
|
|
264
|
+
onMouseEnter={ handleMouseEnter }
|
|
265
|
+
onMouseLeave={ handleMouseLeave }
|
|
266
|
+
onTouchStart={ () => {
|
|
267
|
+
isTouchDevice.current = true;
|
|
268
|
+
} }
|
|
269
|
+
onClick={ () => {
|
|
270
|
+
if (
|
|
271
|
+
! isTouchDevice.current &&
|
|
272
|
+
document.getSelection().type !== 'Range'
|
|
273
|
+
) {
|
|
274
|
+
if ( ! isSelected ) {
|
|
275
|
+
onSelectionChange(
|
|
276
|
+
data.filter( ( _item ) => {
|
|
277
|
+
const itemId = getItemId?.( _item );
|
|
278
|
+
return (
|
|
279
|
+
itemId === id ||
|
|
280
|
+
selection.includes( itemId )
|
|
281
|
+
);
|
|
282
|
+
} )
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
onSelectionChange(
|
|
286
|
+
data.filter( ( _item ) => {
|
|
287
|
+
const itemId = getItemId?.( _item );
|
|
288
|
+
return (
|
|
289
|
+
itemId !== id &&
|
|
290
|
+
selection.includes( itemId )
|
|
291
|
+
);
|
|
292
|
+
} )
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} }
|
|
246
297
|
>
|
|
247
298
|
{ hasBulkActions && (
|
|
248
299
|
<td
|
|
@@ -291,9 +342,20 @@ function TableRow( {
|
|
|
291
342
|
</td>
|
|
292
343
|
) ) }
|
|
293
344
|
{ !! actions?.length && (
|
|
294
|
-
|
|
345
|
+
// Disable reason: we are not making the element interactive,
|
|
346
|
+
// but preventing any click events from bubbling up to the
|
|
347
|
+
// table row. This allows us to add a click handler to the row
|
|
348
|
+
// itself (to toggle row selection) without erroneously
|
|
349
|
+
// intercepting click events from ItemActions.
|
|
350
|
+
|
|
351
|
+
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
|
|
352
|
+
<td
|
|
353
|
+
className="dataviews-view-table__actions-column"
|
|
354
|
+
onClick={ ( e ) => e.stopPropagation() }
|
|
355
|
+
>
|
|
295
356
|
<ItemActions item={ item } actions={ actions } />
|
|
296
357
|
</td>
|
|
358
|
+
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
|
|
297
359
|
) }
|
|
298
360
|
</tr>
|
|
299
361
|
);
|