@wordpress/dataviews 0.5.2 → 0.5.4

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 (39) hide show
  1. package/build/bulk-actions.js +41 -3
  2. package/build/bulk-actions.js.map +1 -1
  3. package/build/dataviews.js +22 -12
  4. package/build/dataviews.js.map +1 -1
  5. package/build/filters.js +8 -1
  6. package/build/filters.js.map +1 -1
  7. package/build/pagination.js +2 -1
  8. package/build/pagination.js.map +1 -1
  9. package/build/single-selection-checkbox.js +3 -1
  10. package/build/single-selection-checkbox.js.map +1 -1
  11. package/build/view-grid.js +18 -6
  12. package/build/view-grid.js.map +1 -1
  13. package/build/view-table.js +76 -41
  14. package/build/view-table.js.map +1 -1
  15. package/build-module/bulk-actions.js +40 -4
  16. package/build-module/bulk-actions.js.map +1 -1
  17. package/build-module/dataviews.js +22 -12
  18. package/build-module/dataviews.js.map +1 -1
  19. package/build-module/filters.js +8 -1
  20. package/build-module/filters.js.map +1 -1
  21. package/build-module/pagination.js +2 -1
  22. package/build-module/pagination.js.map +1 -1
  23. package/build-module/single-selection-checkbox.js +3 -1
  24. package/build-module/single-selection-checkbox.js.map +1 -1
  25. package/build-module/view-grid.js +19 -7
  26. package/build-module/view-grid.js.map +1 -1
  27. package/build-module/view-table.js +77 -42
  28. package/build-module/view-table.js.map +1 -1
  29. package/build-style/style-rtl.css +16 -13
  30. package/build-style/style.css +16 -13
  31. package/package.json +4 -4
  32. package/src/bulk-actions.js +54 -4
  33. package/src/dataviews.js +43 -27
  34. package/src/filters.js +6 -1
  35. package/src/pagination.js +6 -1
  36. package/src/single-selection-checkbox.js +2 -0
  37. package/src/style.scss +20 -11
  38. package/src/view-grid.js +47 -25
  39. package/src/view-table.js +109 -75
@@ -117,19 +117,12 @@
117
117
  .dataviews-filters__view-actions {
118
118
  padding: 12px 32px 0;
119
119
  }
120
- .dataviews-filters__view-actions .components-search-control {
121
- flex-grow: 1;
122
- }
123
120
  .dataviews-filters__view-actions .components-search-control .components-base-control__field {
124
121
  max-width: 240px;
125
122
  }
126
123
 
127
124
  .dataviews-filters__container {
128
- padding: 0 32px;
129
- }
130
-
131
- .dataviews-filters__view-actions.components-h-stack {
132
- align-items: center;
125
+ padding-right: 32px;
133
126
  }
134
127
 
135
128
  .dataviews-filters-button {
@@ -148,6 +141,13 @@
148
141
  color: #757575;
149
142
  }
150
143
 
144
+ .dataviews-pagination__page-selection {
145
+ font-size: 11px;
146
+ text-transform: uppercase;
147
+ font-weight: 500;
148
+ color: #1e1e1e;
149
+ }
150
+
151
151
  .dataviews-filters-options {
152
152
  margin: 32px 0 16px;
153
153
  }
@@ -218,10 +218,10 @@
218
218
  .dataviews-view-table tr:hover {
219
219
  background-color: #f8f8f8;
220
220
  }
221
- .dataviews-view-table tr .components-checkbox-control__input {
221
+ .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input {
222
222
  opacity: 0;
223
223
  }
224
- .dataviews-view-table tr .components-checkbox-control__input:checked, .dataviews-view-table tr .components-checkbox-control__input:indeterminate, .dataviews-view-table tr .components-checkbox-control__input:focus {
224
+ .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:checked, .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:indeterminate, .dataviews-view-table tr .components-checkbox-control__input.components-checkbox-control__input:focus {
225
225
  opacity: 1;
226
226
  }
227
227
  .dataviews-view-table tr:focus-within .components-checkbox-control__input, .dataviews-view-table tr:hover .components-checkbox-control__input {
@@ -282,6 +282,9 @@
282
282
  .dataviews-view-table .dataviews-view-table__actions-column {
283
283
  width: 1%;
284
284
  }
285
+ .dataviews-view-table:has(tr.is-selected) .components-checkbox-control__input {
286
+ opacity: 1;
287
+ }
285
288
 
286
289
  .dataviews-view-list__primary-field,
287
290
  .dataviews-view-grid__primary-field,
@@ -705,7 +708,7 @@
705
708
  .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip.has-reset {
706
709
  padding-inline-end: 28px;
707
710
  }
708
- .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip:hover, .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip:focus-visible {
711
+ .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip:hover, .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip:focus-visible, .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip[aria-expanded=true] {
709
712
  background: #e0e0e0;
710
713
  color: #1e1e1e;
711
714
  }
@@ -713,8 +716,8 @@
713
716
  color: var(--wp-admin-theme-color);
714
717
  background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
715
718
  }
716
- .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip.has-values:hover {
717
- background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
719
+ .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip.has-values:hover, .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip.has-values[aria-expanded=true] {
720
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.12);
718
721
  }
719
722
  .dataviews-filter-summary__chip-container .dataviews-filter-summary__chip:focus-visible {
720
723
  outline: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/dataviews",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -30,11 +30,11 @@
30
30
  "@ariakit/react": "^0.3.12",
31
31
  "@babel/runtime": "^7.16.0",
32
32
  "@wordpress/a11y": "^3.51.1",
33
- "@wordpress/components": "^26.0.2",
33
+ "@wordpress/components": "^26.0.3",
34
34
  "@wordpress/compose": "^6.28.1",
35
35
  "@wordpress/element": "^5.28.1",
36
36
  "@wordpress/i18n": "^4.51.1",
37
- "@wordpress/icons": "^9.42.1",
37
+ "@wordpress/icons": "^9.42.2",
38
38
  "@wordpress/keycodes": "^3.51.1",
39
39
  "@wordpress/primitives": "^3.49.1",
40
40
  "@wordpress/private-apis": "^0.33.1",
@@ -47,5 +47,5 @@
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  },
50
- "gitHead": "730beb7fd33d3382d6032c3f33e451625a0fcf36"
50
+ "gitHead": "864c1c553cb284def3bd5c907998da45f5c143ea"
51
51
  }
@@ -7,7 +7,7 @@ import {
7
7
  Modal,
8
8
  } from '@wordpress/components';
9
9
  import { __, sprintf, _n } from '@wordpress/i18n';
10
- import { useMemo, useState, useCallback } from '@wordpress/element';
10
+ import { useMemo, useState, useCallback, useEffect } from '@wordpress/element';
11
11
 
12
12
  /**
13
13
  * Internal dependencies
@@ -21,6 +21,24 @@ const {
21
21
  DropdownMenuSeparatorV2: DropdownMenuSeparator,
22
22
  } = unlock( componentsPrivateApis );
23
23
 
24
+ export function useHasAPossibleBulkAction( actions, item ) {
25
+ return useMemo( () => {
26
+ return actions.some( ( action ) => {
27
+ return action.supportsBulk && action.isEligible( item );
28
+ } );
29
+ }, [ actions, item ] );
30
+ }
31
+
32
+ export function useSomeItemHasAPossibleBulkAction( actions, data ) {
33
+ return useMemo( () => {
34
+ return data.some( ( item ) => {
35
+ return actions.some( ( action ) => {
36
+ return action.supportsBulk && action.isEligible( item );
37
+ } );
38
+ } );
39
+ }, [ actions, data ] );
40
+ }
41
+
24
42
  function ActionWithModal( {
25
43
  action,
26
44
  selectedItems,
@@ -107,15 +125,47 @@ export default function BulkActions( {
107
125
  () => actions.filter( ( action ) => action.supportsBulk ),
108
126
  [ actions ]
109
127
  );
110
- const areAllSelected = selection && selection.length === data.length;
111
128
  const [ isMenuOpen, onMenuOpenChange ] = useState( false );
112
129
  const [ actionWithModal, setActionWithModal ] = useState();
130
+ const selectableItems = useMemo( () => {
131
+ return data.filter( ( item ) => {
132
+ return bulkActions.some( ( action ) => action.isEligible( item ) );
133
+ } );
134
+ }, [ data, bulkActions ] );
135
+
136
+ const numberSelectableItems = selectableItems.length;
137
+ const areAllSelected =
138
+ selection && selection.length === numberSelectableItems;
139
+
113
140
  const selectedItems = useMemo( () => {
114
141
  return data.filter( ( item ) =>
115
142
  selection.includes( getItemId( item ) )
116
143
  );
117
144
  }, [ selection, data, getItemId ] );
118
145
 
146
+ const hasNonSelectableItemSelected = useMemo( () => {
147
+ return selectedItems.some( ( item ) => {
148
+ return ! selectableItems.includes( item );
149
+ } );
150
+ }, [ selectedItems, selectableItems ] );
151
+ useEffect( () => {
152
+ if ( hasNonSelectableItemSelected ) {
153
+ onSelectionChange(
154
+ selectedItems.filter( ( selectedItem ) => {
155
+ return selectableItems.some( ( item ) => {
156
+ return getItemId( selectedItem ) === getItemId( item );
157
+ } );
158
+ } )
159
+ );
160
+ }
161
+ }, [
162
+ hasNonSelectableItemSelected,
163
+ selectedItems,
164
+ selectableItems,
165
+ getItemId,
166
+ onSelectionChange,
167
+ ] );
168
+
119
169
  if ( bulkActions.length === 0 ) {
120
170
  return null;
121
171
  }
@@ -157,9 +207,9 @@ export default function BulkActions( {
157
207
  disabled={ areAllSelected }
158
208
  hideOnClick={ false }
159
209
  onClick={ () => {
160
- onSelectionChange( data );
210
+ onSelectionChange( selectableItems );
161
211
  } }
162
- suffix={ data.length }
212
+ suffix={ numberSelectableItems }
163
213
  >
164
214
  { __( 'Select all' ) }
165
215
  </DropdownMenuItem>
package/src/dataviews.js CHANGED
@@ -20,6 +20,16 @@ import BulkActions from './bulk-actions';
20
20
  const defaultGetItemId = ( item ) => item.id;
21
21
  const defaultOnSelectionChange = () => {};
22
22
 
23
+ function useSomeItemHasAPossibleBulkAction( actions, data ) {
24
+ return useMemo( () => {
25
+ return data.some( ( item ) => {
26
+ return actions.some( ( action ) => {
27
+ return action.supportsBulk && action.isEligible( item );
28
+ } );
29
+ } );
30
+ }, [ actions, data ] );
31
+ }
32
+
23
33
  export default function DataViews( {
24
34
  view,
25
35
  onChangeView,
@@ -75,30 +85,49 @@ export default function DataViews( {
75
85
  render: field.render || field.getValue,
76
86
  } ) );
77
87
  }, [ fields ] );
88
+
89
+ const hasPossibleBulkAction = useSomeItemHasAPossibleBulkAction(
90
+ actions,
91
+ data
92
+ );
78
93
  return (
79
94
  <div className="dataviews-wrapper">
80
95
  <VStack spacing={ 3 } justify="flex-start">
81
96
  <HStack
82
- alignment="flex-start"
97
+ alignment="top"
83
98
  justify="start"
84
99
  className="dataviews-filters__view-actions"
85
100
  >
86
- { search && (
87
- <Search
88
- label={ searchLabel }
101
+ <HStack
102
+ justify="start"
103
+ className="dataviews-filters__container"
104
+ wrap
105
+ >
106
+ { search && (
107
+ <Search
108
+ label={ searchLabel }
109
+ view={ view }
110
+ onChangeView={ onChangeView }
111
+ />
112
+ ) }
113
+ <Filters
114
+ fields={ _fields }
89
115
  view={ view }
90
116
  onChangeView={ onChangeView }
117
+ openedFilter={ openedFilter }
118
+ setOpenedFilter={ setOpenedFilter }
91
119
  />
92
- ) }
93
- { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) && (
94
- <BulkActions
95
- actions={ actions }
96
- data={ data }
97
- onSelectionChange={ onSetSelection }
98
- selection={ selection }
99
- getItemId={ getItemId }
100
- />
101
- ) }
120
+ </HStack>
121
+ { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type ) &&
122
+ hasPossibleBulkAction && (
123
+ <BulkActions
124
+ actions={ actions }
125
+ data={ data }
126
+ onSelectionChange={ onSetSelection }
127
+ selection={ selection }
128
+ getItemId={ getItemId }
129
+ />
130
+ ) }
102
131
  <ViewActions
103
132
  fields={ _fields }
104
133
  view={ view }
@@ -106,19 +135,6 @@ export default function DataViews( {
106
135
  supportedLayouts={ supportedLayouts }
107
136
  />
108
137
  </HStack>
109
- <HStack
110
- justify="start"
111
- className="dataviews-filters__container"
112
- wrap
113
- >
114
- <Filters
115
- fields={ _fields }
116
- view={ view }
117
- onChangeView={ onChangeView }
118
- openedFilter={ openedFilter }
119
- setOpenedFilter={ setOpenedFilter }
120
- />
121
- </HStack>
122
138
  <ViewComponent
123
139
  fields={ _fields }
124
140
  view={ view }
package/src/filters.js CHANGED
@@ -11,6 +11,7 @@ import AddFilter from './add-filter';
11
11
  import ResetFilters from './reset-filters';
12
12
  import { sanitizeOperators } from './utils';
13
13
  import { ENUMERATION_TYPE, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
14
+ import { __experimentalHStack as HStack } from '@wordpress/components';
14
15
 
15
16
  const Filters = memo( function Filters( {
16
17
  fields,
@@ -108,7 +109,11 @@ const Filters = memo( function Filters( {
108
109
  );
109
110
  }
110
111
 
111
- return filterComponents;
112
+ return (
113
+ <HStack justify="flex-start" style={ { width: 'fit-content' } } wrap>
114
+ { filterComponents }
115
+ </HStack>
116
+ );
112
117
  } );
113
118
 
114
119
  export default Filters;
package/src/pagination.js CHANGED
@@ -27,7 +27,12 @@ const Pagination = memo( function Pagination( {
27
27
  justify="end"
28
28
  className="dataviews-pagination"
29
29
  >
30
- <HStack justify="flex-start" expanded={ false } spacing={ 2 }>
30
+ <HStack
31
+ justify="flex-start"
32
+ expanded={ false }
33
+ spacing={ 2 }
34
+ className="dataviews-pagination__page-selection"
35
+ >
31
36
  { createInterpolateElement(
32
37
  sprintf(
33
38
  // translators: %s: Total number of pages.
@@ -11,6 +11,7 @@ export default function SingleSelectionCheckbox( {
11
11
  data,
12
12
  getItemId,
13
13
  primaryField,
14
+ disabled,
14
15
  } ) {
15
16
  const id = getItemId( item );
16
17
  const isSelected = selection.includes( id );
@@ -33,6 +34,7 @@ export default function SingleSelectionCheckbox( {
33
34
  __nextHasNoMarginBottom
34
35
  checked={ isSelected }
35
36
  label={ selectionLabel }
37
+ disabled={ disabled }
36
38
  onChange={ () => {
37
39
  if ( ! isSelected ) {
38
40
  onSelectionChange(
package/src/style.scss CHANGED
@@ -13,8 +13,6 @@
13
13
  .dataviews-filters__view-actions {
14
14
  padding: $grid-unit-15 $grid-unit-40 0;
15
15
  .components-search-control {
16
- flex-grow: 1;
17
-
18
16
  .components-base-control__field {
19
17
  max-width: 240px;
20
18
  }
@@ -22,11 +20,7 @@
22
20
  }
23
21
 
24
22
  .dataviews-filters__container {
25
- padding: 0 $grid-unit-40;
26
- }
27
-
28
- .dataviews-filters__view-actions.components-h-stack {
29
- align-items: center;
23
+ padding-right: $grid-unit-40;
30
24
  }
31
25
 
32
26
  .dataviews-filters-button {
@@ -44,6 +38,13 @@
44
38
  color: $gray-700;
45
39
  }
46
40
 
41
+ .dataviews-pagination__page-selection {
42
+ font-size: 11px;
43
+ text-transform: uppercase;
44
+ font-weight: 500;
45
+ color: $gray-900;
46
+ }
47
+
47
48
  .dataviews-filters-options {
48
49
  margin: $grid-unit-40 0 $grid-unit-20;
49
50
  }
@@ -118,7 +119,7 @@
118
119
  background-color: #f8f8f8;
119
120
  }
120
121
 
121
- .components-checkbox-control__input {
122
+ .components-checkbox-control__input.components-checkbox-control__input {
122
123
  opacity: 0;
123
124
 
124
125
  &:checked,
@@ -202,6 +203,12 @@
202
203
  .dataviews-view-table__actions-column {
203
204
  width: 1%;
204
205
  }
206
+
207
+ &:has(tr.is-selected) {
208
+ .components-checkbox-control__input {
209
+ opacity: 1;
210
+ }
211
+ }
205
212
  }
206
213
 
207
214
  .dataviews-view-list__primary-field,
@@ -648,7 +655,8 @@
648
655
  }
649
656
 
650
657
  &:hover,
651
- &:focus-visible {
658
+ &:focus-visible,
659
+ &[aria-expanded="true"] {
652
660
  background: $gray-200;
653
661
  color: $gray-900;
654
662
  }
@@ -657,8 +665,9 @@
657
665
  color: var(--wp-admin-theme-color);
658
666
  background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
659
667
 
660
- &:hover {
661
- background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
668
+ &:hover,
669
+ &[aria-expanded="true"] {
670
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.12);
662
671
  }
663
672
  }
664
673
 
package/src/view-grid.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  __experimentalVStack as VStack,
13
13
  Tooltip,
14
14
  } from '@wordpress/components';
15
+ import { __ } from '@wordpress/i18n';
15
16
  import { useAsyncList } from '@wordpress/compose';
16
17
  import { useState } from '@wordpress/element';
17
18
 
@@ -21,6 +22,8 @@ import { useState } from '@wordpress/element';
21
22
  import ItemActions from './item-actions';
22
23
  import SingleSelectionCheckbox from './single-selection-checkbox';
23
24
 
25
+ import { useHasAPossibleBulkAction } from './bulk-actions';
26
+
24
27
  function GridItem( {
25
28
  selection,
26
29
  data,
@@ -33,6 +36,7 @@ function GridItem( {
33
36
  visibleFields,
34
37
  } ) {
35
38
  const [ hasNoPointerEvents, setHasNoPointerEvents ] = useState( false );
39
+ const hasBulkAction = useHasAPossibleBulkAction( actions, item );
36
40
  const id = getItemId( item );
37
41
  const isSelected = selection.includes( id );
38
42
  return (
@@ -40,11 +44,11 @@ function GridItem( {
40
44
  spacing={ 0 }
41
45
  key={ id }
42
46
  className={ classnames( 'dataviews-view-grid__card', {
43
- 'is-selected': isSelected,
47
+ 'is-selected': hasBulkAction && isSelected,
44
48
  'has-no-pointer-events': hasNoPointerEvents,
45
49
  } ) }
46
50
  onMouseDown={ ( event ) => {
47
- if ( event.ctrlKey || event.metaKey ) {
51
+ if ( hasBulkAction && ( event.ctrlKey || event.metaKey ) ) {
48
52
  setHasNoPointerEvents( true );
49
53
  if ( ! isSelected ) {
50
54
  onSelectionChange(
@@ -90,6 +94,7 @@ function GridItem( {
90
94
  getItemId={ getItemId }
91
95
  data={ data }
92
96
  primaryField={ primaryField }
97
+ disabled={ ! hasBulkAction }
93
98
  />
94
99
  <HStack className="dataviews-view-grid__primary-field">
95
100
  { primaryField?.render( { item } ) }
@@ -128,6 +133,7 @@ export default function ViewGrid( {
128
133
  fields,
129
134
  view,
130
135
  actions,
136
+ isLoading,
131
137
  getItemId,
132
138
  deferredRendering,
133
139
  selection,
@@ -148,29 +154,45 @@ export default function ViewGrid( {
148
154
  );
149
155
  const shownData = useAsyncList( data, { step: 3 } );
150
156
  const usedData = deferredRendering ? shownData : data;
157
+ const hasData = !! usedData?.length;
151
158
  return (
152
- <Grid
153
- gap={ 6 }
154
- columns={ 2 }
155
- alignment="top"
156
- className="dataviews-view-grid"
157
- >
158
- { usedData.map( ( item ) => {
159
- return (
160
- <GridItem
161
- key={ getItemId( item ) }
162
- selection={ selection }
163
- data={ data }
164
- onSelectionChange={ onSelectionChange }
165
- getItemId={ getItemId }
166
- item={ item }
167
- actions={ actions }
168
- mediaField={ mediaField }
169
- primaryField={ primaryField }
170
- visibleFields={ visibleFields }
171
- />
172
- );
173
- } ) }
174
- </Grid>
159
+ <>
160
+ { hasData && (
161
+ <Grid
162
+ gap={ 6 }
163
+ columns={ 2 }
164
+ alignment="top"
165
+ className="dataviews-view-grid"
166
+ aria-busy={ isLoading }
167
+ >
168
+ { usedData.map( ( item ) => {
169
+ return (
170
+ <GridItem
171
+ key={ getItemId( item ) }
172
+ selection={ selection }
173
+ data={ data }
174
+ onSelectionChange={ onSelectionChange }
175
+ getItemId={ getItemId }
176
+ item={ item }
177
+ actions={ actions }
178
+ mediaField={ mediaField }
179
+ primaryField={ primaryField }
180
+ visibleFields={ visibleFields }
181
+ />
182
+ );
183
+ } ) }
184
+ </Grid>
185
+ ) }
186
+ { ! hasData && (
187
+ <div
188
+ className={ classnames( {
189
+ 'dataviews-loading': isLoading,
190
+ 'dataviews-no-results': ! isLoading,
191
+ } ) }
192
+ >
193
+ <p>{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }</p>
194
+ </div>
195
+ ) }
196
+ </>
175
197
  );
176
198
  }