@wordpress/dataviews 0.7.0 → 0.8.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +48 -15
  3. package/build/constants.js +28 -7
  4. package/build/constants.js.map +1 -1
  5. package/build/dataviews.js +11 -5
  6. package/build/dataviews.js.map +1 -1
  7. package/build/filter-summary.js +33 -12
  8. package/build/filter-summary.js.map +1 -1
  9. package/build/filters.js +2 -1
  10. package/build/filters.js.map +1 -1
  11. package/build/item-actions.js +20 -39
  12. package/build/item-actions.js.map +1 -1
  13. package/build/pagination.js +2 -2
  14. package/build/pagination.js.map +1 -1
  15. package/build/search-widget.js +34 -10
  16. package/build/search-widget.js.map +1 -1
  17. package/build/utils.js +24 -2
  18. package/build/utils.js.map +1 -1
  19. package/build/view-grid.js +4 -1
  20. package/build/view-grid.js.map +1 -1
  21. package/build/view-table.js +35 -4
  22. package/build/view-table.js.map +1 -1
  23. package/build-module/constants.js +27 -6
  24. package/build-module/constants.js.map +1 -1
  25. package/build-module/dataviews.js +11 -5
  26. package/build-module/dataviews.js.map +1 -1
  27. package/build-module/filter-summary.js +34 -13
  28. package/build-module/filter-summary.js.map +1 -1
  29. package/build-module/filters.js +3 -2
  30. package/build-module/filters.js.map +1 -1
  31. package/build-module/item-actions.js +20 -39
  32. package/build-module/item-actions.js.map +1 -1
  33. package/build-module/pagination.js +2 -2
  34. package/build-module/pagination.js.map +1 -1
  35. package/build-module/search-widget.js +35 -11
  36. package/build-module/search-widget.js.map +1 -1
  37. package/build-module/utils.js +25 -3
  38. package/build-module/utils.js.map +1 -1
  39. package/build-module/view-grid.js +4 -1
  40. package/build-module/view-grid.js.map +1 -1
  41. package/build-module/view-table.js +36 -5
  42. package/build-module/view-table.js.map +1 -1
  43. package/build-style/style-rtl.css +9 -3
  44. package/build-style/style.css +9 -3
  45. package/package.json +11 -11
  46. package/src/constants.js +35 -6
  47. package/src/dataviews.js +11 -5
  48. package/src/filter-summary.js +76 -23
  49. package/src/filters.js +10 -4
  50. package/src/item-actions.js +19 -55
  51. package/src/pagination.js +2 -2
  52. package/src/search-widget.js +63 -21
  53. package/src/stories/fixtures.js +12 -1
  54. package/src/stories/index.story.js +43 -4
  55. package/src/style.scss +9 -3
  56. package/src/utils.js +38 -4
  57. package/src/view-grid.js +4 -1
  58. package/src/view-table.js +50 -4
@@ -15,7 +15,7 @@ import {
15
15
  Icon,
16
16
  privateApis as componentsPrivateApis,
17
17
  } from '@wordpress/components';
18
- import { search } from '@wordpress/icons';
18
+ import { search, check } from '@wordpress/icons';
19
19
  import { SVG, Circle } from '@wordpress/primitives';
20
20
 
21
21
  /**
@@ -39,6 +39,36 @@ function normalizeSearchInput( input = '' ) {
39
39
  return removeAccents( input.trim().toLowerCase() );
40
40
  }
41
41
 
42
+ const getCurrentValue = ( filterDefinition, currentFilter ) => {
43
+ if ( filterDefinition.singleSelection ) {
44
+ return currentFilter?.value;
45
+ }
46
+
47
+ if ( Array.isArray( currentFilter?.value ) ) {
48
+ return currentFilter.value;
49
+ }
50
+
51
+ if ( ! Array.isArray( currentFilter?.value ) && !! currentFilter?.value ) {
52
+ return [ currentFilter.value ];
53
+ }
54
+
55
+ return [];
56
+ };
57
+
58
+ const getNewValue = ( filterDefinition, currentFilter, value ) => {
59
+ if ( filterDefinition.singleSelection ) {
60
+ return value;
61
+ }
62
+
63
+ if ( Array.isArray( currentFilter?.value ) ) {
64
+ return currentFilter.value.includes( value )
65
+ ? currentFilter.value.filter( ( v ) => v !== value )
66
+ : [ ...currentFilter.value, value ];
67
+ }
68
+
69
+ return [ value ];
70
+ };
71
+
42
72
  function ListBox( { view, filter, onChangeView } ) {
43
73
  const compositeStore = useCompositeStore( {
44
74
  virtualFocus: true,
@@ -48,10 +78,10 @@ function ListBox( { view, filter, onChangeView } ) {
48
78
  // so the first item is not selected, since the focus is on the operators control.
49
79
  defaultActiveId: filter.operators?.length === 1 ? undefined : null,
50
80
  } );
51
- const selectedFilter = view.filters.find(
52
- ( _filter ) => _filter.field === filter.field
81
+ const currentFilter = view.filters.find(
82
+ ( f ) => f.field === filter.field
53
83
  );
54
- const selectedValues = selectedFilter?.value;
84
+ const currentValue = getCurrentValue( filter, currentFilter );
55
85
  return (
56
86
  <Composite
57
87
  store={ compositeStore }
@@ -83,10 +113,6 @@ function ListBox( { view, filter, onChangeView } ) {
83
113
  />
84
114
  }
85
115
  onClick={ () => {
86
- const currentFilter = view.filters.find(
87
- ( _filter ) =>
88
- _filter.field === filter.field
89
- );
90
116
  const newFilters = currentFilter
91
117
  ? [
92
118
  ...view.filters.map(
@@ -101,7 +127,11 @@ function ListBox( { view, filter, onChangeView } ) {
101
127
  currentFilter.operator ||
102
128
  filter
103
129
  .operators[ 0 ],
104
- value: element.value,
130
+ value: getNewValue(
131
+ filter,
132
+ currentFilter,
133
+ element.value
134
+ ),
105
135
  };
106
136
  }
107
137
  return _filter;
@@ -113,7 +143,11 @@ function ListBox( { view, filter, onChangeView } ) {
113
143
  {
114
144
  field: filter.field,
115
145
  operator: filter.operators[ 0 ],
116
- value: element.value,
146
+ value: getNewValue(
147
+ filter,
148
+ currentFilter,
149
+ element.value
150
+ ),
117
151
  },
118
152
  ];
119
153
  onChangeView( {
@@ -126,9 +160,14 @@ function ListBox( { view, filter, onChangeView } ) {
126
160
  }
127
161
  >
128
162
  <span className="dataviews-search-widget-listitem-check">
129
- { selectedValues === element.value && (
130
- <Icon icon={ radioCheck } />
131
- ) }
163
+ { filter.singleSelection &&
164
+ currentValue === element.value && (
165
+ <Icon icon={ radioCheck } />
166
+ ) }
167
+ { ! filter.singleSelection &&
168
+ currentValue.includes( element.value ) && (
169
+ <Icon icon={ check } />
170
+ ) }
132
171
  </span>
133
172
  <span>
134
173
  { element.label }
@@ -147,10 +186,10 @@ function ListBox( { view, filter, onChangeView } ) {
147
186
  function ComboboxList( { view, filter, onChangeView } ) {
148
187
  const [ searchValue, setSearchValue ] = useState( '' );
149
188
  const deferredSearchValue = useDeferredValue( searchValue );
150
- const selectedFilter = view.filters.find(
189
+ const currentFilter = view.filters.find(
151
190
  ( _filter ) => _filter.field === filter.field
152
191
  );
153
- const selectedValues = selectedFilter?.value;
192
+ const currentValue = getCurrentValue( filter, currentFilter );
154
193
  const matches = useMemo( () => {
155
194
  const normalizedSearch = normalizeSearchInput( deferredSearchValue );
156
195
  return filter.elements.filter( ( item ) =>
@@ -160,10 +199,8 @@ function ComboboxList( { view, filter, onChangeView } ) {
160
199
  return (
161
200
  <Ariakit.ComboboxProvider
162
201
  value={ searchValue }
202
+ selectedValue={ currentValue }
163
203
  setSelectedValue={ ( value ) => {
164
- const currentFilter = view.filters.find(
165
- ( _filter ) => _filter.field === filter.field
166
- );
167
204
  const newFilters = currentFilter
168
205
  ? [
169
206
  ...view.filters.map( ( _filter ) => {
@@ -223,9 +260,14 @@ function ComboboxList( { view, filter, onChangeView } ) {
223
260
  focusOnHover
224
261
  >
225
262
  <span className="dataviews-search-widget-listitem-check">
226
- { selectedValues === element.value && (
227
- <Icon icon={ radioCheck } />
228
- ) }
263
+ { filter.singleSelection &&
264
+ currentValue === element.value && (
265
+ <Icon icon={ radioCheck } />
266
+ ) }
267
+ { ! filter.singleSelection &&
268
+ currentValue.includes( element.value ) && (
269
+ <Icon icon={ check } />
270
+ ) }
229
271
  </span>
230
272
  <span>
231
273
  <Ariakit.ComboboxItemValue
@@ -20,66 +20,77 @@ export const data = [
20
20
  title: 'Apollo',
21
21
  description: 'Apollo description',
22
22
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
23
+ type: 'Not a planet',
23
24
  },
24
25
  {
25
26
  id: 2,
26
27
  title: 'Space',
27
28
  description: 'Space description',
28
29
  image: 'https://live.staticflickr.com/5678/21911065441_92e2d44708_b.jpg',
30
+ type: 'Not a planet',
29
31
  },
30
32
  {
31
33
  id: 3,
32
34
  title: 'NASA',
33
35
  description: 'NASA photo',
34
36
  image: 'https://live.staticflickr.com/742/21712365770_8f70a2c91e_b.jpg',
37
+ type: 'Not a planet',
35
38
  },
36
39
  {
37
40
  id: 4,
38
41
  title: 'Neptune',
39
42
  description: 'Neptune description',
40
43
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
44
+ type: 'Ice giant',
41
45
  },
42
46
  {
43
47
  id: 5,
44
48
  title: 'Mercury',
45
49
  description: 'Mercury description',
46
50
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
51
+ type: 'Terrestrial',
47
52
  },
48
53
  {
49
54
  id: 6,
50
55
  title: 'Venus',
51
56
  description: 'Venus description',
52
57
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
58
+ type: 'Terrestrial',
53
59
  },
54
60
  {
55
61
  id: 7,
56
62
  title: 'Earth',
57
63
  description: 'Earth description',
58
64
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
65
+ type: 'Terrestrial',
59
66
  },
60
67
  {
61
68
  id: 8,
62
69
  title: 'Mars',
63
70
  description: 'Mars description',
64
71
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
72
+ type: 'Terrestrial',
65
73
  },
66
74
  {
67
75
  id: 9,
68
76
  title: 'Jupiter',
69
77
  description: 'Jupiter description',
70
78
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
79
+ type: 'Gas giant',
71
80
  },
72
81
  {
73
82
  id: 10,
74
83
  title: 'Saturn',
75
84
  description: 'Saturn description',
76
85
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
86
+ type: 'Gas giant',
77
87
  },
78
88
  {
79
89
  id: 11,
80
90
  title: 'Uranus',
81
91
  description: 'Uranus description',
82
92
  image: 'https://live.staticflickr.com/5725/21726228300_51333bd62c_b.jpg',
93
+ type: 'Ice giant',
83
94
  },
84
95
  ];
85
96
 
@@ -88,7 +99,7 @@ export const DEFAULT_VIEW = {
88
99
  search: '',
89
100
  page: 1,
90
101
  perPage: 10,
91
- hiddenFields: [ 'image' ],
102
+ hiddenFields: [ 'image', 'type' ],
92
103
  layout: {},
93
104
  filters: [],
94
105
  };
@@ -8,10 +8,15 @@ import { useState, useMemo, useCallback } from '@wordpress/element';
8
8
  */
9
9
  import { DataViews } from '../index';
10
10
  import { DEFAULT_VIEW, actions, data } from './fixtures';
11
- import { LAYOUT_GRID, LAYOUT_TABLE } from '../constants';
11
+ import {
12
+ LAYOUT_GRID,
13
+ LAYOUT_TABLE,
14
+ OPERATOR_IS_NONE,
15
+ OPERATOR_IS_ANY,
16
+ } from '../constants';
12
17
 
13
18
  const meta = {
14
- title: 'DataViews (Experimental)/DataViews',
19
+ title: 'DataViews/DataViews',
15
20
  component: DataViews,
16
21
  };
17
22
  export default meta;
@@ -45,14 +50,25 @@ const fields = [
45
50
  {
46
51
  header: 'Title',
47
52
  id: 'title',
48
- getValue: ( { item } ) => item.title,
49
53
  maxWidth: 400,
50
54
  enableHiding: false,
51
55
  },
56
+ {
57
+ header: 'Type',
58
+ id: 'type',
59
+ maxWidth: 400,
60
+ enableHiding: false,
61
+ type: 'enumeration',
62
+ elements: [
63
+ { value: 'Not a planet', label: 'Not a planet' },
64
+ { value: 'Ice giant', label: 'Ice giant' },
65
+ { value: 'Terrestrial', label: 'Terrestrial' },
66
+ { value: 'Gas giant', label: 'Gas giant' },
67
+ ],
68
+ },
52
69
  {
53
70
  header: 'Description',
54
71
  id: 'description',
55
- getValue: ( { item } ) => item.description,
56
72
  maxWidth: 200,
57
73
  enableSorting: false,
58
74
  },
@@ -72,6 +88,29 @@ export const Default = ( props ) => {
72
88
  ].some( ( field ) => field.includes( normalizedSearch ) );
73
89
  } );
74
90
  }
91
+
92
+ if ( view.filters.length > 0 ) {
93
+ view.filters.forEach( ( filter ) => {
94
+ if (
95
+ filter.field === 'type' &&
96
+ filter.operator === OPERATOR_IS_ANY &&
97
+ filter?.value?.length > 0
98
+ ) {
99
+ filteredData = filteredData.filter( ( item ) => {
100
+ return filter.value.includes( item.type );
101
+ } );
102
+ } else if (
103
+ filter.field === 'type' &&
104
+ filter.operator === OPERATOR_IS_NONE &&
105
+ filter?.value?.length > 0
106
+ ) {
107
+ filteredData = filteredData.filter( ( item ) => {
108
+ return ! filter.value.includes( item.type );
109
+ } );
110
+ }
111
+ } );
112
+ }
113
+
75
114
  // Handle sorting.
76
115
  if ( view.sort ) {
77
116
  const stringSortingFields = [ 'title' ];
package/src/style.scss CHANGED
@@ -1,5 +1,4 @@
1
1
  .dataviews-wrapper {
2
- width: 100%;
3
2
  height: 100%;
4
3
  overflow: auto;
5
4
  box-sizing: border-box;
@@ -7,6 +6,7 @@
7
6
  }
8
7
 
9
8
  .dataviews-filters__view-actions {
9
+ box-sizing: border-box;
10
10
  padding: $grid-unit-15 $grid-unit-40 0;
11
11
  margin-bottom: $grid-unit-15;
12
12
  flex-shrink: 0;
@@ -123,7 +123,7 @@
123
123
  border-bottom: 0;
124
124
  }
125
125
 
126
- &:hover {
126
+ &.is-hovered {
127
127
  background-color: #f8f8f8;
128
128
  }
129
129
 
@@ -137,9 +137,15 @@
137
137
  }
138
138
  }
139
139
 
140
+ .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) {
141
+ opacity: 0;
142
+ }
143
+
140
144
  &:focus-within,
145
+ &.is-hovered,
141
146
  &:hover {
142
- .components-checkbox-control__input {
147
+ .components-checkbox-control__input,
148
+ .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) {
143
149
  opacity: 1;
144
150
  }
145
151
  }
package/src/utils.js CHANGED
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import { OPERATORS } from './constants';
4
+ import {
5
+ ALL_OPERATORS,
6
+ OPERATOR_IS,
7
+ OPERATOR_IS_NOT,
8
+ OPERATOR_IS_ANY,
9
+ OPERATOR_IS_NONE,
10
+ } from './constants';
5
11
 
6
12
  /**
7
13
  * Helper util to sort data by text fields, when sorting is done client side.
@@ -57,10 +63,38 @@ export function getPaginationResults( { data, view } ) {
57
63
 
58
64
  export const sanitizeOperators = ( field ) => {
59
65
  let operators = field.filterBy?.operators;
66
+
67
+ // Assign default values.
60
68
  if ( ! operators || ! Array.isArray( operators ) ) {
61
- operators = Object.keys( OPERATORS );
69
+ operators = [ OPERATOR_IS_ANY, OPERATOR_IS_NONE ];
70
+ }
71
+
72
+ // Transform legacy in, notIn operators to is, isNot.
73
+ // To be removed in the future.
74
+ if ( operators.includes( 'in' ) ) {
75
+ operators = operators.filter( ( operator ) => operator !== 'is' );
76
+ operators.push( 'is' );
77
+ }
78
+ if ( operators.includes( 'notIn' ) ) {
79
+ operators = operators.filter( ( operator ) => operator !== 'notIn' );
80
+ operators.push( 'isNot' );
62
81
  }
63
- return operators.filter( ( operator ) =>
64
- Object.keys( OPERATORS ).includes( operator )
82
+
83
+ // Make sure only valid operators are used.
84
+ operators = operators.filter( ( operator ) =>
85
+ ALL_OPERATORS.includes( operator )
65
86
  );
87
+
88
+ // Do not allow mixing single & multiselection operators.
89
+ // Remove multiselection operators if any of the single selection ones is present.
90
+ if (
91
+ operators.includes( OPERATOR_IS ) ||
92
+ operators.includes( OPERATOR_IS_NOT )
93
+ ) {
94
+ operators = operators.filter( ( operator ) =>
95
+ [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( operator )
96
+ );
97
+ }
98
+
99
+ return operators;
66
100
  };
package/src/view-grid.js CHANGED
@@ -46,9 +46,12 @@ function GridItem( {
46
46
  'is-selected': hasBulkAction && isSelected,
47
47
  } ) }
48
48
  onClickCapture={ ( event ) => {
49
- if ( hasBulkAction && ( event.ctrlKey || event.metaKey ) ) {
49
+ if ( event.ctrlKey || event.metaKey ) {
50
50
  event.stopPropagation();
51
51
  event.preventDefault();
52
+ if ( ! hasBulkAction ) {
53
+ return;
54
+ }
52
55
  if ( ! isSelected ) {
53
56
  onSelectionChange(
54
57
  data.filter( ( _item ) => {
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
  /**
@@ -49,7 +49,7 @@ const {
49
49
  DropdownMenuSeparatorV2: DropdownMenuSeparator,
50
50
  } = unlock( componentsPrivateApis );
51
51
 
52
- function WithSeparators( { children } ) {
52
+ function WithDropDownMenuSeparators( { children } ) {
53
53
  return Children.toArray( children )
54
54
  .filter( Boolean )
55
55
  .map( ( child, i ) => (
@@ -102,7 +102,7 @@ const HeaderMenu = forwardRef( function HeaderMenu(
102
102
  }
103
103
  style={ { minWidth: '240px' } }
104
104
  >
105
- <WithSeparators>
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
- </WithSeparators>
190
+ </WithDropDownMenuSeparators>
191
191
  </DropdownMenu>
192
192
  );
193
193
  } );
@@ -237,12 +237,58 @@ function TableRow( {
237
237
  data,
238
238
  } ) {
239
239
  const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
240
+
241
+ const isSelected = selection.includes( id );
242
+
243
+ const [ isHovered, setIsHovered ] = useState( false );
244
+
245
+ const handleMouseEnter = () => {
246
+ setIsHovered( true );
247
+ };
248
+
249
+ const handleMouseLeave = () => {
250
+ setIsHovered( false );
251
+ };
252
+
240
253
  return (
241
254
  <tr
242
255
  className={ classnames( 'dataviews-view-table__row', {
243
256
  'is-selected':
244
257
  hasPossibleBulkAction && selection.includes( id ),
258
+ 'is-hovered': isHovered,
245
259
  } ) }
260
+ onMouseEnter={ handleMouseEnter }
261
+ onMouseLeave={ handleMouseLeave }
262
+ onClickCapture={ ( event ) => {
263
+ if ( event.ctrlKey || event.metaKey ) {
264
+ event.stopPropagation();
265
+ event.preventDefault();
266
+ if ( ! hasPossibleBulkAction ) {
267
+ return;
268
+ }
269
+ if ( ! isSelected ) {
270
+ onSelectionChange(
271
+ data.filter( ( _item ) => {
272
+ const itemId = getItemId?.( _item );
273
+ return (
274
+ itemId === id ||
275
+ selection.includes( itemId )
276
+ );
277
+ } )
278
+ );
279
+ } else {
280
+ onSelectionChange(
281
+ data.filter( ( _item ) => {
282
+ const itemId = getItemId?.( _item );
283
+ return (
284
+ itemId !== id &&
285
+ selection.includes( itemId )
286
+ );
287
+ } )
288
+ );
289
+ }
290
+ }
291
+ } }
246
292
  >
247
293
  { hasBulkActions && (
248
294
  <td