@wordpress/dataviews 0.2.0 → 0.3.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 +2 -0
- package/LICENSE.md +1 -1
- package/README.md +30 -6
- package/build/add-filter.js +109 -49
- package/build/add-filter.js.map +1 -1
- package/build/constants.js +24 -2
- package/build/constants.js.map +1 -1
- package/build/dataviews.js +12 -9
- package/build/dataviews.js.map +1 -1
- package/build/dropdown-menu-helper.js +72 -0
- package/build/dropdown-menu-helper.js.map +1 -0
- package/build/filter-summary.js +43 -54
- package/build/filter-summary.js.map +1 -1
- package/build/filters.js +27 -17
- package/build/filters.js.map +1 -1
- package/build/index.js +13 -0
- package/build/index.js.map +1 -1
- package/build/item-actions.js +12 -12
- package/build/item-actions.js.map +1 -1
- package/build/pagination.js +31 -65
- package/build/pagination.js.map +1 -1
- package/build/reset-filters.js +8 -8
- package/build/reset-filters.js.map +1 -1
- package/build/search.js +8 -6
- package/build/search.js.map +1 -1
- package/build/utils.js +71 -0
- package/build/utils.js.map +1 -0
- package/build/view-actions.js +72 -95
- package/build/view-actions.js.map +1 -1
- package/build/view-grid.js +4 -6
- package/build/view-grid.js.map +1 -1
- package/build/view-list.js +26 -13
- package/build/view-list.js.map +1 -1
- package/build/view-table.js +153 -154
- package/build/view-table.js.map +1 -1
- package/build-module/add-filter.js +113 -53
- package/build-module/add-filter.js.map +1 -1
- package/build-module/constants.js +20 -0
- package/build-module/constants.js.map +1 -1
- package/build-module/dataviews.js +13 -10
- package/build-module/dataviews.js.map +1 -1
- package/build-module/dropdown-menu-helper.js +64 -0
- package/build-module/dropdown-menu-helper.js.map +1 -0
- package/build-module/filter-summary.js +45 -56
- package/build-module/filter-summary.js.map +1 -1
- package/build-module/filters.js +26 -17
- package/build-module/filters.js.map +1 -1
- package/build-module/index.js +1 -0
- package/build-module/index.js.map +1 -1
- package/build-module/item-actions.js +12 -12
- package/build-module/item-actions.js.map +1 -1
- package/build-module/pagination.js +35 -69
- package/build-module/pagination.js.map +1 -1
- package/build-module/reset-filters.js +6 -6
- package/build-module/reset-filters.js.map +1 -1
- package/build-module/search.js +7 -6
- package/build-module/search.js.map +1 -1
- package/build-module/utils.js +63 -0
- package/build-module/utils.js.map +1 -0
- package/build-module/view-actions.js +73 -97
- package/build-module/view-actions.js.map +1 -1
- package/build-module/view-grid.js +4 -6
- package/build-module/view-grid.js.map +1 -1
- package/build-module/view-list.js +27 -14
- package/build-module/view-list.js.map +1 -1
- package/build-module/view-table.js +156 -157
- package/build-module/view-table.js.map +1 -1
- package/build-style/style-rtl.css +180 -70
- package/build-style/style.css +180 -70
- package/package.json +11 -10
- package/src/add-filter.js +227 -68
- package/src/constants.js +16 -0
- package/src/dataviews.js +19 -12
- package/src/dropdown-menu-helper.js +61 -0
- package/src/filter-summary.js +70 -103
- package/src/filters.js +41 -24
- package/src/index.js +1 -0
- package/src/item-actions.js +30 -25
- package/src/pagination.js +75 -123
- package/src/reset-filters.js +5 -5
- package/src/search.js +8 -6
- package/src/style.scss +182 -48
- package/src/utils.js +51 -0
- package/src/view-actions.js +113 -114
- package/src/view-grid.js +4 -4
- package/src/view-list.js +42 -28
- package/src/view-table.js +280 -238
package/src/view-table.js
CHANGED
|
@@ -1,123 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* WordPress dependencies
|
|
3
8
|
*/
|
|
4
9
|
import { __ } from '@wordpress/i18n';
|
|
5
10
|
import { useAsyncList } from '@wordpress/compose';
|
|
6
|
-
import {
|
|
7
|
-
chevronDown,
|
|
8
|
-
chevronUp,
|
|
9
|
-
unseen,
|
|
10
|
-
check,
|
|
11
|
-
arrowUp,
|
|
12
|
-
arrowDown,
|
|
13
|
-
chevronRightSmall,
|
|
14
|
-
funnel,
|
|
15
|
-
} from '@wordpress/icons';
|
|
11
|
+
import { unseen, funnel } from '@wordpress/icons';
|
|
16
12
|
import {
|
|
17
13
|
Button,
|
|
18
14
|
Icon,
|
|
19
15
|
privateApis as componentsPrivateApis,
|
|
20
16
|
} from '@wordpress/components';
|
|
21
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Children,
|
|
19
|
+
Fragment,
|
|
20
|
+
forwardRef,
|
|
21
|
+
useEffect,
|
|
22
|
+
useId,
|
|
23
|
+
useRef,
|
|
24
|
+
useState,
|
|
25
|
+
} from '@wordpress/element';
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Internal dependencies
|
|
25
29
|
*/
|
|
26
30
|
import { unlock } from './lock-unlock';
|
|
27
31
|
import ItemActions from './item-actions';
|
|
28
|
-
import { ENUMERATION_TYPE,
|
|
32
|
+
import { ENUMERATION_TYPE, OPERATORS, SORTING_DIRECTIONS } from './constants';
|
|
33
|
+
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
|
|
29
34
|
|
|
30
35
|
const {
|
|
31
36
|
DropdownMenuV2: DropdownMenu,
|
|
32
37
|
DropdownMenuGroupV2: DropdownMenuGroup,
|
|
33
38
|
DropdownMenuItemV2: DropdownMenuItem,
|
|
39
|
+
DropdownMenuRadioItemV2: DropdownMenuRadioItem,
|
|
34
40
|
DropdownMenuSeparatorV2: DropdownMenuSeparator,
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
DropdownMenuItemLabelV2: DropdownMenuItemLabel,
|
|
42
|
+
DropdownMenuItemHelpTextV2: DropdownMenuItemHelpText,
|
|
37
43
|
} = unlock( componentsPrivateApis );
|
|
38
44
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const sortArrows = { asc: '↑', desc: '↓' };
|
|
46
|
+
|
|
47
|
+
const sanitizeOperators = ( field ) => {
|
|
48
|
+
let operators = field.filterBy?.operators;
|
|
49
|
+
if ( ! operators || ! Array.isArray( operators ) ) {
|
|
50
|
+
operators = Object.keys( OPERATORS );
|
|
51
|
+
}
|
|
52
|
+
return operators.filter( ( operator ) =>
|
|
53
|
+
Object.keys( OPERATORS ).includes( operator )
|
|
54
|
+
);
|
|
42
55
|
};
|
|
43
|
-
const sortIcons = { asc: chevronUp, desc: chevronDown };
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
const HeaderMenu = forwardRef( function HeaderMenu(
|
|
58
|
+
{ field, view, onChangeView, onHide },
|
|
59
|
+
ref
|
|
60
|
+
) {
|
|
47
61
|
const isHidable = field.enableHiding !== false;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
62
|
+
|
|
63
|
+
const isSortable = field.enableSorting !== false;
|
|
51
64
|
const isSorted = view.sort?.field === field.id;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
|
|
66
|
+
let filter, filterInView, activeElement, activeOperator, otherFilters;
|
|
67
|
+
const operators = sanitizeOperators( field );
|
|
68
|
+
if ( field.type === ENUMERATION_TYPE && operators.length > 0 ) {
|
|
69
|
+
filter = {
|
|
70
|
+
field: field.id,
|
|
71
|
+
operators,
|
|
72
|
+
elements: field.elements || [],
|
|
73
|
+
};
|
|
74
|
+
filterInView = view.filters.find( ( f ) => f.field === filter.field );
|
|
75
|
+
otherFilters = view.filters.filter( ( f ) => f.field !== filter.field );
|
|
76
|
+
activeElement = filter.elements.find(
|
|
77
|
+
( element ) => element.value === filterInView?.value
|
|
61
78
|
);
|
|
62
|
-
|
|
63
|
-
filter = {
|
|
64
|
-
field: field.id,
|
|
65
|
-
operators,
|
|
66
|
-
elements: field.elements || [],
|
|
67
|
-
};
|
|
68
|
-
filterInView = {
|
|
69
|
-
field: filter.field,
|
|
70
|
-
operator: filter.operators[ 0 ],
|
|
71
|
-
value: undefined,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
79
|
+
activeOperator = filterInView?.operator || filter.operators[ 0 ];
|
|
74
80
|
}
|
|
75
81
|
const isFilterable = !! filter;
|
|
76
82
|
|
|
77
|
-
if ( isFilterable ) {
|
|
78
|
-
|
|
79
|
-
columnFilters.forEach( ( columnFilter ) => {
|
|
80
|
-
if ( columnFilter.field === filter.field ) {
|
|
81
|
-
filterInView = {
|
|
82
|
-
...columnFilter,
|
|
83
|
-
};
|
|
84
|
-
} else {
|
|
85
|
-
otherFilters.push( columnFilter );
|
|
86
|
-
}
|
|
87
|
-
} );
|
|
83
|
+
if ( ! isSortable && ! isHidable && ! isFilterable ) {
|
|
84
|
+
return field.header;
|
|
88
85
|
}
|
|
86
|
+
|
|
89
87
|
return (
|
|
90
88
|
<DropdownMenu
|
|
91
89
|
align="start"
|
|
92
90
|
trigger={
|
|
93
91
|
<Button
|
|
94
|
-
icon={ isSorted && sortIcons[ view.sort.direction ] }
|
|
95
|
-
iconPosition="right"
|
|
96
|
-
text={ field.header }
|
|
97
|
-
style={ { padding: 0 } }
|
|
98
92
|
size="compact"
|
|
99
|
-
|
|
93
|
+
className="dataviews-view-table-header-button"
|
|
94
|
+
ref={ ref }
|
|
95
|
+
variant="tertiary"
|
|
96
|
+
>
|
|
97
|
+
{ field.header }
|
|
98
|
+
{ isSorted && (
|
|
99
|
+
<span aria-hidden="true">
|
|
100
|
+
{ isSorted && sortArrows[ view.sort.direction ] }
|
|
101
|
+
</span>
|
|
102
|
+
) }
|
|
103
|
+
</Button>
|
|
100
104
|
}
|
|
105
|
+
style={ { minWidth: '240px' } }
|
|
101
106
|
>
|
|
102
107
|
<WithSeparators>
|
|
103
108
|
{ isSortable && (
|
|
104
109
|
<DropdownMenuGroup>
|
|
105
|
-
{ Object.entries(
|
|
110
|
+
{ Object.entries( SORTING_DIRECTIONS ).map(
|
|
106
111
|
( [ direction, info ] ) => {
|
|
107
|
-
const
|
|
112
|
+
const isChecked =
|
|
108
113
|
isSorted &&
|
|
109
114
|
view.sort.direction === direction;
|
|
115
|
+
|
|
116
|
+
const value = `${ field.id }-${ direction }`;
|
|
117
|
+
|
|
110
118
|
return (
|
|
111
|
-
<
|
|
112
|
-
key={
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
<DropdownMenuRadioItem
|
|
120
|
+
key={ value }
|
|
121
|
+
// All sorting radio items share the same name, so that
|
|
122
|
+
// selecting a sorting option automatically deselects the
|
|
123
|
+
// previously selected one, even if it is displayed in
|
|
124
|
+
// another submenu. The field and direction are passed via
|
|
125
|
+
// the `value` prop.
|
|
126
|
+
name="view-table-sorting"
|
|
127
|
+
value={ value }
|
|
128
|
+
checked={ isChecked }
|
|
129
|
+
onChange={ () => {
|
|
121
130
|
onChangeView( {
|
|
122
131
|
...view,
|
|
123
132
|
sort: {
|
|
@@ -127,8 +136,10 @@ function HeaderMenu( { field, view, onChangeView } ) {
|
|
|
127
136
|
} );
|
|
128
137
|
} }
|
|
129
138
|
>
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
<DropdownMenuItemLabel>
|
|
140
|
+
{ info.label }
|
|
141
|
+
</DropdownMenuItemLabel>
|
|
142
|
+
</DropdownMenuRadioItem>
|
|
132
143
|
);
|
|
133
144
|
}
|
|
134
145
|
) }
|
|
@@ -136,11 +147,9 @@ function HeaderMenu( { field, view, onChangeView } ) {
|
|
|
136
147
|
) }
|
|
137
148
|
{ isHidable && (
|
|
138
149
|
<DropdownMenuItem
|
|
139
|
-
role="menuitemradio"
|
|
140
|
-
aria-checked={ false }
|
|
141
150
|
prefix={ <Icon icon={ unseen } /> }
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
onClick={ () => {
|
|
152
|
+
onHide( field );
|
|
144
153
|
onChangeView( {
|
|
145
154
|
...view,
|
|
146
155
|
hiddenFields: view.hiddenFields.concat(
|
|
@@ -149,57 +158,56 @@ function HeaderMenu( { field, view, onChangeView } ) {
|
|
|
149
158
|
} );
|
|
150
159
|
} }
|
|
151
160
|
>
|
|
152
|
-
|
|
161
|
+
<DropdownMenuItemLabel>
|
|
162
|
+
{ __( 'Hide' ) }
|
|
163
|
+
</DropdownMenuItemLabel>
|
|
153
164
|
</DropdownMenuItem>
|
|
154
165
|
) }
|
|
155
166
|
{ isFilterable && (
|
|
156
167
|
<DropdownMenuGroup>
|
|
157
|
-
<
|
|
168
|
+
<DropdownMenu
|
|
158
169
|
key={ filter.field }
|
|
159
170
|
trigger={
|
|
160
|
-
<
|
|
171
|
+
<DropdownMenuItem
|
|
161
172
|
prefix={ <Icon icon={ funnel } /> }
|
|
162
173
|
suffix={
|
|
163
|
-
|
|
174
|
+
activeElement && (
|
|
175
|
+
<span aria-hidden="true">
|
|
176
|
+
{ activeOperator in OPERATORS &&
|
|
177
|
+
`${ OPERATORS[ activeOperator ].label } ` }
|
|
178
|
+
{ activeElement?.label }
|
|
179
|
+
</span>
|
|
180
|
+
)
|
|
164
181
|
}
|
|
165
182
|
>
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
<DropdownMenuItemLabel>
|
|
184
|
+
{ __( 'Filter by' ) }
|
|
185
|
+
</DropdownMenuItemLabel>
|
|
186
|
+
</DropdownMenuItem>
|
|
168
187
|
}
|
|
169
188
|
>
|
|
170
189
|
<WithSeparators>
|
|
171
190
|
<DropdownMenuGroup>
|
|
172
191
|
{ filter.elements.map( ( element ) => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// This covers the case where a top-level filter for the same field converts a number into a string.
|
|
177
|
-
/* eslint-disable eqeqeq */
|
|
178
|
-
isActive =
|
|
179
|
-
element.value ==
|
|
180
|
-
filterInView.value;
|
|
181
|
-
/* eslint-enable eqeqeq */
|
|
182
|
-
}
|
|
183
|
-
|
|
192
|
+
const isActive =
|
|
193
|
+
activeElement?.value ===
|
|
194
|
+
element.value;
|
|
184
195
|
return (
|
|
185
|
-
<
|
|
196
|
+
<DropdownMenuRadioItemCustom
|
|
186
197
|
key={ element.value }
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<Icon icon={ check } />
|
|
192
|
-
)
|
|
193
|
-
}
|
|
194
|
-
onSelect={ () => {
|
|
198
|
+
name={ `view-table-${ filter.field }` }
|
|
199
|
+
value={ element.value }
|
|
200
|
+
checked={ isActive }
|
|
201
|
+
onClick={ () => {
|
|
195
202
|
onChangeView( {
|
|
196
203
|
...view,
|
|
204
|
+
page: 1,
|
|
197
205
|
filters: [
|
|
198
206
|
...otherFilters,
|
|
199
207
|
{
|
|
200
208
|
field: filter.field,
|
|
201
209
|
operator:
|
|
202
|
-
|
|
210
|
+
activeOperator,
|
|
203
211
|
value: isActive
|
|
204
212
|
? undefined
|
|
205
213
|
: element.value,
|
|
@@ -208,103 +216,84 @@ function HeaderMenu( { field, view, onChangeView } ) {
|
|
|
208
216
|
} );
|
|
209
217
|
} }
|
|
210
218
|
>
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
<DropdownMenuItemLabel>
|
|
220
|
+
{ element.label }
|
|
221
|
+
</DropdownMenuItemLabel>
|
|
222
|
+
{ !! element.description && (
|
|
223
|
+
<DropdownMenuItemHelpText>
|
|
224
|
+
{ element.description }
|
|
225
|
+
</DropdownMenuItemHelpText>
|
|
226
|
+
) }
|
|
227
|
+
</DropdownMenuRadioItemCustom>
|
|
213
228
|
);
|
|
214
229
|
} ) }
|
|
215
230
|
</DropdownMenuGroup>
|
|
216
231
|
{ filter.operators.length > 1 && (
|
|
217
|
-
<
|
|
232
|
+
<DropdownMenu
|
|
218
233
|
trigger={
|
|
219
|
-
<
|
|
234
|
+
<DropdownMenuItem
|
|
220
235
|
suffix={
|
|
221
|
-
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
chevronRightSmall
|
|
229
|
-
}
|
|
230
|
-
/>{ ' ' }
|
|
231
|
-
</>
|
|
236
|
+
<span aria-hidden="true">
|
|
237
|
+
{
|
|
238
|
+
OPERATORS[
|
|
239
|
+
activeOperator
|
|
240
|
+
]?.label
|
|
241
|
+
}
|
|
242
|
+
</span>
|
|
232
243
|
}
|
|
233
244
|
>
|
|
234
|
-
|
|
235
|
-
|
|
245
|
+
<DropdownMenuItemLabel>
|
|
246
|
+
{ __( 'Conditions' ) }
|
|
247
|
+
</DropdownMenuItemLabel>
|
|
248
|
+
</DropdownMenuItem>
|
|
236
249
|
}
|
|
237
250
|
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
prefix={
|
|
276
|
-
filterInView?.operator ===
|
|
277
|
-
OPERATOR_NOT_IN && (
|
|
278
|
-
<Icon icon={ check } />
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
onSelect={ () =>
|
|
282
|
-
onChangeView( {
|
|
283
|
-
...view,
|
|
284
|
-
filters: [
|
|
285
|
-
...otherFilters,
|
|
286
|
-
{
|
|
287
|
-
field: filter.field,
|
|
288
|
-
operator:
|
|
289
|
-
OPERATOR_NOT_IN,
|
|
290
|
-
value: filterInView?.value,
|
|
291
|
-
},
|
|
292
|
-
],
|
|
293
|
-
} )
|
|
294
|
-
}
|
|
295
|
-
>
|
|
296
|
-
{ __( 'Is not' ) }
|
|
297
|
-
</DropdownMenuItem>
|
|
298
|
-
</DropdownSubMenu>
|
|
251
|
+
{ Object.entries( OPERATORS ).map(
|
|
252
|
+
( [
|
|
253
|
+
operator,
|
|
254
|
+
{ label, key },
|
|
255
|
+
] ) => (
|
|
256
|
+
<DropdownMenuRadioItem
|
|
257
|
+
key={ key }
|
|
258
|
+
name={ `view-table-${ filter.field }-conditions` }
|
|
259
|
+
value={ operator }
|
|
260
|
+
checked={
|
|
261
|
+
activeOperator ===
|
|
262
|
+
operator
|
|
263
|
+
}
|
|
264
|
+
onChange={ ( e ) =>
|
|
265
|
+
onChangeView( {
|
|
266
|
+
...view,
|
|
267
|
+
page: 1,
|
|
268
|
+
filters: [
|
|
269
|
+
...otherFilters,
|
|
270
|
+
{
|
|
271
|
+
field: filter.field,
|
|
272
|
+
operator:
|
|
273
|
+
e.target
|
|
274
|
+
.value,
|
|
275
|
+
value: filterInView?.value,
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
} )
|
|
279
|
+
}
|
|
280
|
+
>
|
|
281
|
+
<DropdownMenuItemLabel>
|
|
282
|
+
{ label }
|
|
283
|
+
</DropdownMenuItemLabel>
|
|
284
|
+
</DropdownMenuRadioItem>
|
|
285
|
+
)
|
|
286
|
+
) }
|
|
287
|
+
</DropdownMenu>
|
|
299
288
|
) }
|
|
300
289
|
</WithSeparators>
|
|
301
|
-
</
|
|
290
|
+
</DropdownMenu>
|
|
302
291
|
</DropdownMenuGroup>
|
|
303
292
|
) }
|
|
304
293
|
</WithSeparators>
|
|
305
294
|
</DropdownMenu>
|
|
306
295
|
);
|
|
307
|
-
}
|
|
296
|
+
} );
|
|
308
297
|
|
|
309
298
|
function WithSeparators( { children } ) {
|
|
310
299
|
return Children.toArray( children )
|
|
@@ -327,6 +316,35 @@ function ViewTable( {
|
|
|
327
316
|
isLoading = false,
|
|
328
317
|
deferredRendering,
|
|
329
318
|
} ) {
|
|
319
|
+
const headerMenuRefs = useRef( new Map() );
|
|
320
|
+
const headerMenuToFocusRef = useRef();
|
|
321
|
+
const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] = useState();
|
|
322
|
+
|
|
323
|
+
useEffect( () => {
|
|
324
|
+
if ( headerMenuToFocusRef.current ) {
|
|
325
|
+
headerMenuToFocusRef.current.focus();
|
|
326
|
+
headerMenuToFocusRef.current = undefined;
|
|
327
|
+
}
|
|
328
|
+
} );
|
|
329
|
+
|
|
330
|
+
const asyncData = useAsyncList( data );
|
|
331
|
+
const tableNoticeId = useId();
|
|
332
|
+
|
|
333
|
+
if ( nextHeaderMenuToFocus ) {
|
|
334
|
+
// If we need to force focus, we short-circuit rendering here
|
|
335
|
+
// to prevent any additional work while we handle that.
|
|
336
|
+
// Clearing out the focus directive is necessary to make sure
|
|
337
|
+
// future renders don't cause unexpected focus jumps.
|
|
338
|
+
headerMenuToFocusRef.current = nextHeaderMenuToFocus;
|
|
339
|
+
setNextHeaderMenuToFocus();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const onHide = ( field ) => {
|
|
344
|
+
const hidden = headerMenuRefs.current.get( field.id );
|
|
345
|
+
const fallback = headerMenuRefs.current.get( hidden.fallback );
|
|
346
|
+
setNextHeaderMenuToFocus( fallback?.node );
|
|
347
|
+
};
|
|
330
348
|
const visibleFields = fields.filter(
|
|
331
349
|
( field ) =>
|
|
332
350
|
! view.hiddenFields.includes( field.id ) &&
|
|
@@ -334,56 +352,75 @@ function ViewTable( {
|
|
|
334
352
|
field.id
|
|
335
353
|
)
|
|
336
354
|
);
|
|
337
|
-
const
|
|
338
|
-
const usedData = deferredRendering ? shownData : data;
|
|
355
|
+
const usedData = deferredRendering ? asyncData : data;
|
|
339
356
|
const hasData = !! usedData?.length;
|
|
340
|
-
if ( isLoading ) {
|
|
341
|
-
// TODO:Add spinner or progress bar..
|
|
342
|
-
return (
|
|
343
|
-
<div className="dataviews-loading">
|
|
344
|
-
<h3>{ __( 'Loading' ) }</h3>
|
|
345
|
-
</div>
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
357
|
const sortValues = { asc: 'ascending', desc: 'descending' };
|
|
358
|
+
|
|
349
359
|
return (
|
|
350
|
-
<div
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
<div>
|
|
361
|
+
<table
|
|
362
|
+
className="dataviews-view-table"
|
|
363
|
+
aria-busy={ isLoading }
|
|
364
|
+
aria-describedby={ tableNoticeId }
|
|
365
|
+
>
|
|
366
|
+
<thead>
|
|
367
|
+
<tr>
|
|
368
|
+
{ visibleFields.map( ( field, index ) => (
|
|
369
|
+
<th
|
|
370
|
+
key={ field.id }
|
|
371
|
+
style={ {
|
|
372
|
+
width: field.width || undefined,
|
|
373
|
+
minWidth: field.minWidth || undefined,
|
|
374
|
+
maxWidth: field.maxWidth || undefined,
|
|
375
|
+
} }
|
|
376
|
+
data-field-id={ field.id }
|
|
377
|
+
aria-sort={
|
|
378
|
+
view.sort?.field === field.id &&
|
|
379
|
+
sortValues[ view.sort.direction ]
|
|
380
|
+
}
|
|
381
|
+
scope="col"
|
|
382
|
+
>
|
|
383
|
+
<HeaderMenu
|
|
384
|
+
ref={ ( node ) => {
|
|
385
|
+
if ( node ) {
|
|
386
|
+
headerMenuRefs.current.set(
|
|
387
|
+
field.id,
|
|
388
|
+
{
|
|
389
|
+
node,
|
|
390
|
+
fallback:
|
|
391
|
+
visibleFields[
|
|
392
|
+
index > 0
|
|
393
|
+
? index - 1
|
|
394
|
+
: 1
|
|
395
|
+
]?.id,
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
headerMenuRefs.current.delete(
|
|
400
|
+
field.id
|
|
401
|
+
);
|
|
402
|
+
}
|
|
362
403
|
} }
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
onChangeView={ onChangeView }
|
|
374
|
-
/>
|
|
375
|
-
</th>
|
|
376
|
-
) ) }
|
|
377
|
-
{ !! actions?.length && (
|
|
378
|
-
<th data-field-id="actions">
|
|
404
|
+
field={ field }
|
|
405
|
+
view={ view }
|
|
406
|
+
onChangeView={ onChangeView }
|
|
407
|
+
onHide={ onHide }
|
|
408
|
+
/>
|
|
409
|
+
</th>
|
|
410
|
+
) ) }
|
|
411
|
+
{ !! actions?.length && (
|
|
412
|
+
<th data-field-id="actions">
|
|
413
|
+
<span className="dataviews-view-table-header">
|
|
379
414
|
{ __( 'Actions' ) }
|
|
380
|
-
</
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
</
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
415
|
+
</span>
|
|
416
|
+
</th>
|
|
417
|
+
) }
|
|
418
|
+
</tr>
|
|
419
|
+
</thead>
|
|
420
|
+
<tbody>
|
|
421
|
+
{ hasData &&
|
|
422
|
+
usedData.map( ( item ) => (
|
|
423
|
+
<tr key={ getItemId( item ) }>
|
|
387
424
|
{ visibleFields.map( ( field ) => (
|
|
388
425
|
<td
|
|
389
426
|
key={ field.id }
|
|
@@ -410,14 +447,19 @@ function ViewTable( {
|
|
|
410
447
|
) }
|
|
411
448
|
</tr>
|
|
412
449
|
) ) }
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
450
|
+
</tbody>
|
|
451
|
+
</table>
|
|
452
|
+
<div
|
|
453
|
+
className={ classNames( {
|
|
454
|
+
'dataviews-loading': isLoading,
|
|
455
|
+
'dataviews-no-results': ! hasData && ! isLoading,
|
|
456
|
+
} ) }
|
|
457
|
+
id={ tableNoticeId }
|
|
458
|
+
>
|
|
459
|
+
{ ! hasData && (
|
|
460
|
+
<p>{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }</p>
|
|
461
|
+
) }
|
|
462
|
+
</div>
|
|
421
463
|
</div>
|
|
422
464
|
);
|
|
423
465
|
}
|