@wordpress/dataviews 0.4.1 → 0.5.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +1 -0
  3. package/build/add-filter.js +25 -108
  4. package/build/add-filter.js.map +1 -1
  5. package/build/constants.js +9 -18
  6. package/build/constants.js.map +1 -1
  7. package/build/dataviews.js +22 -16
  8. package/build/dataviews.js.map +1 -1
  9. package/build/dropdown-menu-helper.js +1 -2
  10. package/build/dropdown-menu-helper.js.map +1 -1
  11. package/build/filter-summary.js +146 -78
  12. package/build/filter-summary.js.map +1 -1
  13. package/build/filters.js +32 -18
  14. package/build/filters.js.map +1 -1
  15. package/build/pagination.js +1 -2
  16. package/build/pagination.js.map +1 -1
  17. package/build/reset-filters.js +4 -1
  18. package/build/reset-filters.js.map +1 -1
  19. package/build/search-widget.js +111 -0
  20. package/build/search-widget.js.map +1 -0
  21. package/build/search.js +2 -3
  22. package/build/search.js.map +1 -1
  23. package/build/single-selection-checkbox.js +54 -0
  24. package/build/single-selection-checkbox.js.map +1 -0
  25. package/build/utils.js +14 -1
  26. package/build/utils.js.map +1 -1
  27. package/build/view-actions.js +2 -3
  28. package/build/view-actions.js.map +1 -1
  29. package/build/view-grid.js +92 -22
  30. package/build/view-grid.js.map +1 -1
  31. package/build/view-list.js +2 -1
  32. package/build/view-list.js.map +1 -1
  33. package/build/view-table.js +43 -132
  34. package/build/view-table.js.map +1 -1
  35. package/build-module/add-filter.js +28 -111
  36. package/build-module/add-filter.js.map +1 -1
  37. package/build-module/dataviews.js +23 -17
  38. package/build-module/dataviews.js.map +1 -1
  39. package/build-module/filter-summary.js +147 -80
  40. package/build-module/filter-summary.js.map +1 -1
  41. package/build-module/filters.js +32 -17
  42. package/build-module/filters.js.map +1 -1
  43. package/build-module/reset-filters.js +4 -1
  44. package/build-module/reset-filters.js.map +1 -1
  45. package/build-module/search-widget.js +101 -0
  46. package/build-module/search-widget.js.map +1 -0
  47. package/build-module/search.js +1 -1
  48. package/build-module/search.js.map +1 -1
  49. package/build-module/single-selection-checkbox.js +47 -0
  50. package/build-module/single-selection-checkbox.js.map +1 -0
  51. package/build-module/utils.js +12 -0
  52. package/build-module/utils.js.map +1 -1
  53. package/build-module/view-actions.js +1 -1
  54. package/build-module/view-actions.js.map +1 -1
  55. package/build-module/view-grid.js +92 -22
  56. package/build-module/view-grid.js.map +1 -1
  57. package/build-module/view-list.js +2 -1
  58. package/build-module/view-list.js.map +1 -1
  59. package/build-module/view-table.js +43 -131
  60. package/build-module/view-table.js.map +1 -1
  61. package/build-style/style-rtl.css +248 -44
  62. package/build-style/style.css +248 -44
  63. package/package.json +12 -11
  64. package/src/add-filter.js +39 -230
  65. package/src/dataviews.js +31 -20
  66. package/src/filter-summary.js +190 -136
  67. package/src/filters.js +42 -29
  68. package/src/reset-filters.js +12 -2
  69. package/src/search-widget.js +128 -0
  70. package/src/search.js +1 -1
  71. package/src/single-selection-checkbox.js +59 -0
  72. package/src/style.scss +254 -44
  73. package/src/utils.js +15 -0
  74. package/src/view-actions.js +1 -2
  75. package/src/view-grid.js +127 -53
  76. package/src/view-list.js +5 -1
  77. package/src/view-table.js +57 -230
package/src/view-table.js CHANGED
@@ -6,7 +6,7 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { __, sprintf } from '@wordpress/i18n';
9
+ import { __ } from '@wordpress/i18n';
10
10
  import { useAsyncList } from '@wordpress/compose';
11
11
  import { unseen, funnel } from '@wordpress/icons';
12
12
  import {
@@ -16,75 +16,66 @@ import {
16
16
  CheckboxControl,
17
17
  } from '@wordpress/components';
18
18
  import {
19
- Children,
20
- Fragment,
21
19
  forwardRef,
22
20
  useEffect,
23
21
  useId,
24
22
  useRef,
25
23
  useState,
24
+ Children,
25
+ Fragment,
26
26
  } from '@wordpress/element';
27
27
 
28
28
  /**
29
29
  * Internal dependencies
30
30
  */
31
+ import SingleSelectionCheckbox from './single-selection-checkbox';
31
32
  import { unlock } from './lock-unlock';
32
33
  import ItemActions from './item-actions';
33
- import { ENUMERATION_TYPE, OPERATORS, SORTING_DIRECTIONS } from './constants';
34
- import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
34
+ import { sanitizeOperators } from './utils';
35
+ import { ENUMERATION_TYPE, SORTING_DIRECTIONS } from './constants';
35
36
 
36
37
  const {
37
38
  DropdownMenuV2: DropdownMenu,
38
39
  DropdownMenuGroupV2: DropdownMenuGroup,
39
40
  DropdownMenuItemV2: DropdownMenuItem,
40
41
  DropdownMenuRadioItemV2: DropdownMenuRadioItem,
41
- DropdownMenuSeparatorV2: DropdownMenuSeparator,
42
42
  DropdownMenuItemLabelV2: DropdownMenuItemLabel,
43
- DropdownMenuItemHelpTextV2: DropdownMenuItemHelpText,
43
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
44
44
  } = unlock( componentsPrivateApis );
45
45
 
46
- const sortArrows = { asc: '↑', desc: '↓' };
46
+ function WithSeparators( { children } ) {
47
+ return Children.toArray( children )
48
+ .filter( Boolean )
49
+ .map( ( child, i ) => (
50
+ <Fragment key={ i }>
51
+ { i > 0 && <DropdownMenuSeparator /> }
52
+ { child }
53
+ </Fragment>
54
+ ) );
55
+ }
47
56
 
48
- const sanitizeOperators = ( field ) => {
49
- let operators = field.filterBy?.operators;
50
- if ( ! operators || ! Array.isArray( operators ) ) {
51
- operators = Object.keys( OPERATORS );
52
- }
53
- return operators.filter( ( operator ) =>
54
- Object.keys( OPERATORS ).includes( operator )
55
- );
56
- };
57
+ const sortArrows = { asc: '↑', desc: '↓' };
57
58
 
58
59
  const HeaderMenu = forwardRef( function HeaderMenu(
59
- { field, view, onChangeView, onHide },
60
+ { field, view, onChangeView, onHide, setOpenedFilter },
60
61
  ref
61
62
  ) {
62
63
  const isHidable = field.enableHiding !== false;
63
-
64
64
  const isSortable = field.enableSorting !== false;
65
65
  const isSorted = view.sort?.field === field.id;
66
-
67
- let filter, filterInView, activeElement, activeOperator, otherFilters;
68
66
  const operators = sanitizeOperators( field );
69
- if ( field.type === ENUMERATION_TYPE && operators.length > 0 ) {
70
- filter = {
71
- field: field.id,
72
- operators,
73
- elements: field.elements || [],
74
- };
75
- filterInView = view.filters.find( ( f ) => f.field === filter.field );
76
- otherFilters = view.filters.filter( ( f ) => f.field !== filter.field );
77
- activeElement = filter.elements.find(
78
- ( element ) => element.value === filterInView?.value
79
- );
80
- activeOperator = filterInView?.operator || filter.operators[ 0 ];
81
- }
82
- const isFilterable = !! filter;
83
-
84
- if ( ! isSortable && ! isHidable && ! isFilterable ) {
67
+ // Filter can be added:
68
+ // 1. If the field is not already part of a view's filters.
69
+ // 2. If the field meets the type and operator requirements.
70
+ // 3. If it's not primary. If it is, it should be already visible.
71
+ const canAddFilter =
72
+ ! view.filters?.some( ( _filter ) => field.id === _filter.field ) &&
73
+ field.type === ENUMERATION_TYPE &&
74
+ !! operators.length &&
75
+ ! field.filterBy?.isPrimary;
76
+ if ( ! isSortable && ! isHidable && ! canAddFilter ) {
85
77
  return field.header;
86
78
  }
87
-
88
79
  return (
89
80
  <DropdownMenu
90
81
  align="start"
@@ -146,6 +137,32 @@ const HeaderMenu = forwardRef( function HeaderMenu(
146
137
  ) }
147
138
  </DropdownMenuGroup>
148
139
  ) }
140
+ { canAddFilter && (
141
+ <DropdownMenuGroup>
142
+ <DropdownMenuItem
143
+ prefix={ <Icon icon={ funnel } /> }
144
+ onClick={ () => {
145
+ setOpenedFilter( field.id );
146
+ onChangeView( {
147
+ ...view,
148
+ page: 1,
149
+ filters: [
150
+ ...( view.filters || [] ),
151
+ {
152
+ field: field.id,
153
+ value: undefined,
154
+ operator: operators[ 0 ],
155
+ },
156
+ ],
157
+ } );
158
+ } }
159
+ >
160
+ <DropdownMenuItemLabel>
161
+ { __( 'Add filter' ) }
162
+ </DropdownMenuItemLabel>
163
+ </DropdownMenuItem>
164
+ </DropdownMenuGroup>
165
+ ) }
149
166
  { isHidable && (
150
167
  <DropdownMenuItem
151
168
  prefix={ <Icon icon={ unseen } /> }
@@ -164,149 +181,11 @@ const HeaderMenu = forwardRef( function HeaderMenu(
164
181
  </DropdownMenuItemLabel>
165
182
  </DropdownMenuItem>
166
183
  ) }
167
- { isFilterable && (
168
- <DropdownMenuGroup>
169
- <DropdownMenu
170
- key={ filter.field }
171
- trigger={
172
- <DropdownMenuItem
173
- prefix={ <Icon icon={ funnel } /> }
174
- suffix={
175
- activeElement && (
176
- <span aria-hidden="true">
177
- { activeOperator in OPERATORS &&
178
- `${ OPERATORS[ activeOperator ].label } ` }
179
- { activeElement?.label }
180
- </span>
181
- )
182
- }
183
- >
184
- <DropdownMenuItemLabel>
185
- { __( 'Filter by' ) }
186
- </DropdownMenuItemLabel>
187
- </DropdownMenuItem>
188
- }
189
- >
190
- <WithSeparators>
191
- <DropdownMenuGroup>
192
- { filter.elements.map( ( element ) => {
193
- const isActive =
194
- activeElement?.value ===
195
- element.value;
196
- return (
197
- <DropdownMenuRadioItemCustom
198
- key={ element.value }
199
- name={ `view-table-${ filter.field }` }
200
- value={ element.value }
201
- checked={ isActive }
202
- onClick={ () => {
203
- onChangeView( {
204
- ...view,
205
- page: 1,
206
- filters: [
207
- ...otherFilters,
208
- {
209
- field: filter.field,
210
- operator:
211
- activeOperator,
212
- value: isActive
213
- ? undefined
214
- : element.value,
215
- },
216
- ],
217
- } );
218
- } }
219
- >
220
- <DropdownMenuItemLabel>
221
- { element.label }
222
- </DropdownMenuItemLabel>
223
- { !! element.description && (
224
- <DropdownMenuItemHelpText>
225
- { element.description }
226
- </DropdownMenuItemHelpText>
227
- ) }
228
- </DropdownMenuRadioItemCustom>
229
- );
230
- } ) }
231
- </DropdownMenuGroup>
232
- { filter.operators.length > 1 && (
233
- <DropdownMenu
234
- trigger={
235
- <DropdownMenuItem
236
- suffix={
237
- <span aria-hidden="true">
238
- {
239
- OPERATORS[
240
- activeOperator
241
- ]?.label
242
- }
243
- </span>
244
- }
245
- >
246
- <DropdownMenuItemLabel>
247
- { __( 'Conditions' ) }
248
- </DropdownMenuItemLabel>
249
- </DropdownMenuItem>
250
- }
251
- >
252
- { Object.entries( OPERATORS ).map(
253
- ( [
254
- operator,
255
- { label, key },
256
- ] ) => (
257
- <DropdownMenuRadioItem
258
- key={ key }
259
- name={ `view-table-${ filter.field }-conditions` }
260
- value={ operator }
261
- checked={
262
- activeOperator ===
263
- operator
264
- }
265
- onChange={ ( e ) =>
266
- onChangeView( {
267
- ...view,
268
- page: 1,
269
- filters: [
270
- ...otherFilters,
271
- {
272
- field: filter.field,
273
- operator:
274
- e.target
275
- .value,
276
- value: filterInView?.value,
277
- },
278
- ],
279
- } )
280
- }
281
- >
282
- <DropdownMenuItemLabel>
283
- { label }
284
- </DropdownMenuItemLabel>
285
- </DropdownMenuRadioItem>
286
- )
287
- ) }
288
- </DropdownMenu>
289
- ) }
290
- </WithSeparators>
291
- </DropdownMenu>
292
- </DropdownMenuGroup>
293
- ) }
294
184
  </WithSeparators>
295
185
  </DropdownMenu>
296
186
  );
297
187
  } );
298
188
 
299
- function WithSeparators( { children } ) {
300
- return Children.toArray( children )
301
- .filter( Boolean )
302
- .map( ( child, i ) => (
303
- <Fragment key={ i }>
304
- { i > 0 && <DropdownMenuSeparator /> }
305
- { child }
306
- </Fragment>
307
- ) );
308
- }
309
-
310
189
  function BulkSelectionCheckbox( { selection, onSelectionChange, data } ) {
311
190
  const areAllSelected = selection.length === data.length;
312
191
  return (
@@ -327,60 +206,6 @@ function BulkSelectionCheckbox( { selection, onSelectionChange, data } ) {
327
206
  );
328
207
  }
329
208
 
330
- function SingleSelectionCheckbox( {
331
- selection,
332
- onSelectionChange,
333
- item,
334
- data,
335
- getItemId,
336
- primaryField,
337
- } ) {
338
- const id = getItemId( item );
339
- const isSelected = selection.includes( id );
340
- let selectionLabel;
341
- if ( primaryField?.getValue && item ) {
342
- // eslint-disable-next-line @wordpress/valid-sprintf
343
- selectionLabel = sprintf(
344
- /* translators: %s: item title. */
345
- isSelected ? __( 'Deselect item: %s' ) : __( 'Select item: %s' ),
346
- primaryField.getValue( { item } )
347
- );
348
- } else {
349
- selectionLabel = isSelected
350
- ? __( 'Select a new item' )
351
- : __( 'Deselect item' );
352
- }
353
- return (
354
- <CheckboxControl
355
- className="dataviews-view-table-selection-checkbox"
356
- __nextHasNoMarginBottom
357
- checked={ isSelected }
358
- label={ selectionLabel }
359
- onChange={ () => {
360
- if ( ! isSelected ) {
361
- onSelectionChange(
362
- data.filter( ( _item ) => {
363
- const itemId = getItemId?.( _item );
364
- return (
365
- itemId === id || selection.includes( itemId )
366
- );
367
- } )
368
- );
369
- } else {
370
- onSelectionChange(
371
- data.filter( ( _item ) => {
372
- const itemId = getItemId?.( _item );
373
- return (
374
- itemId !== id && selection.includes( itemId )
375
- );
376
- } )
377
- );
378
- }
379
- } }
380
- />
381
- );
382
- }
383
-
384
209
  function ViewTable( {
385
210
  view,
386
211
  onChangeView,
@@ -392,6 +217,7 @@ function ViewTable( {
392
217
  deferredRendering,
393
218
  selection,
394
219
  onSelectionChange,
220
+ setOpenedFilter,
395
221
  } ) {
396
222
  const hasBulkActions = actions?.some( ( action ) => action.supportsBulk );
397
223
  const headerMenuRefs = useRef( new Map() );
@@ -502,6 +328,7 @@ function ViewTable( {
502
328
  view={ view }
503
329
  onChangeView={ onChangeView }
504
330
  onHide={ onHide }
331
+ setOpenedFilter={ setOpenedFilter }
505
332
  />
506
333
  </th>
507
334
  ) ) }