@wordpress/dataviews 0.8.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 +10 -0
- package/README.md +3 -5
- package/build/constants.js +1 -4
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +2 -12
- 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/filters.js +11 -17
- package/build/filters.js.map +1 -1
- package/build/index.js +3 -9
- package/build/index.js.map +1 -1
- package/build/normalize-fields.js +25 -0
- package/build/normalize-fields.js.map +1 -0
- package/build/utils.js +1 -65
- package/build/utils.js.map +1 -1
- package/build/view-grid.js +21 -11
- 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 +27 -13
- package/build/view-table.js.map +1 -1
- package/build-module/constants.js +0 -3
- package/build-module/constants.js.map +1 -1
- package/build-module/dataviews.js +2 -12
- 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/filters.js +12 -18
- 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/normalize-fields.js +19 -0
- package/build-module/normalize-fields.js.map +1 -0
- package/build-module/utils.js +0 -63
- package/build-module/utils.js.map +1 -1
- package/build-module/view-grid.js +22 -12
- 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 +28 -14
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +32 -8
- package/build-style/style.css +32 -8
- package/package.json +11 -11
- package/src/constants.js +0 -3
- package/src/dataviews.js +2 -12
- package/src/filter-and-sort-data-view.js +154 -0
- package/src/filters.js +20 -32
- package/src/index.js +1 -1
- package/src/normalize-fields.js +17 -0
- package/src/stories/fixtures.js +75 -1
- package/src/stories/index.story.js +5 -113
- package/src/style.scss +44 -11
- package/src/test/filter-and-sort-data-view.js +276 -0
- package/src/utils.js +0 -52
- package/src/view-grid.js +32 -10
- package/src/view-list.js +159 -69
- package/src/view-table.js +29 -13
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
|
@@ -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,
|
|
@@ -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 ) {
|
|
@@ -237,7 +237,6 @@ function TableRow( {
|
|
|
237
237
|
data,
|
|
238
238
|
} ) {
|
|
239
239
|
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
|
|
240
|
-
|
|
241
240
|
const isSelected = selection.includes( id );
|
|
242
241
|
|
|
243
242
|
const [ isHovered, setIsHovered ] = useState( false );
|
|
@@ -250,22 +249,28 @@ function TableRow( {
|
|
|
250
249
|
setIsHovered( false );
|
|
251
250
|
};
|
|
252
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
|
+
|
|
253
257
|
return (
|
|
254
258
|
<tr
|
|
255
259
|
className={ classnames( 'dataviews-view-table__row', {
|
|
256
|
-
'is-selected':
|
|
257
|
-
hasPossibleBulkAction && selection.includes( id ),
|
|
260
|
+
'is-selected': hasPossibleBulkAction && isSelected,
|
|
258
261
|
'is-hovered': isHovered,
|
|
262
|
+
'has-bulk-actions': hasPossibleBulkAction,
|
|
259
263
|
} ) }
|
|
260
264
|
onMouseEnter={ handleMouseEnter }
|
|
261
265
|
onMouseLeave={ handleMouseLeave }
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
onTouchStart={ () => {
|
|
267
|
+
isTouchDevice.current = true;
|
|
268
|
+
} }
|
|
269
|
+
onClick={ () => {
|
|
270
|
+
if (
|
|
271
|
+
! isTouchDevice.current &&
|
|
272
|
+
document.getSelection().type !== 'Range'
|
|
273
|
+
) {
|
|
269
274
|
if ( ! isSelected ) {
|
|
270
275
|
onSelectionChange(
|
|
271
276
|
data.filter( ( _item ) => {
|
|
@@ -337,9 +342,20 @@ function TableRow( {
|
|
|
337
342
|
</td>
|
|
338
343
|
) ) }
|
|
339
344
|
{ !! actions?.length && (
|
|
340
|
-
|
|
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
|
+
>
|
|
341
356
|
<ItemActions item={ item } actions={ actions } />
|
|
342
357
|
</td>
|
|
358
|
+
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
|
|
343
359
|
) }
|
|
344
360
|
</tr>
|
|
345
361
|
);
|