@wordpress/dataviews 0.8.0 → 1.0.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 (92) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +3 -13
  3. package/build/add-filter.js.map +1 -1
  4. package/build/bulk-actions.js.map +1 -1
  5. package/build/constants.js +1 -4
  6. package/build/constants.js.map +1 -1
  7. package/build/dataviews.js +3 -17
  8. package/build/dataviews.js.map +1 -1
  9. package/build/dropdown-menu-helper.js.map +1 -1
  10. package/build/filter-and-sort-data-view.js +147 -0
  11. package/build/filter-and-sort-data-view.js.map +1 -0
  12. package/build/filter-summary.js +4 -2
  13. package/build/filter-summary.js.map +1 -1
  14. package/build/filters.js +11 -17
  15. package/build/filters.js.map +1 -1
  16. package/build/index.js +3 -9
  17. package/build/index.js.map +1 -1
  18. package/build/item-actions.js.map +1 -1
  19. package/build/lock-unlock.js.map +1 -1
  20. package/build/normalize-fields.js +25 -0
  21. package/build/normalize-fields.js.map +1 -0
  22. package/build/pagination.js.map +1 -1
  23. package/build/reset-filters.js.map +1 -1
  24. package/build/search-widget.js +5 -4
  25. package/build/search-widget.js.map +1 -1
  26. package/build/search.js.map +1 -1
  27. package/build/single-selection-checkbox.js +1 -1
  28. package/build/single-selection-checkbox.js.map +1 -1
  29. package/build/utils.js +1 -65
  30. package/build/utils.js.map +1 -1
  31. package/build/view-actions.js.map +1 -1
  32. package/build/view-grid.js +57 -19
  33. package/build/view-grid.js.map +1 -1
  34. package/build/view-list.js +112 -66
  35. package/build/view-list.js.map +1 -1
  36. package/build/view-table.js +32 -24
  37. package/build/view-table.js.map +1 -1
  38. package/build-module/add-filter.js.map +1 -1
  39. package/build-module/bulk-actions.js.map +1 -1
  40. package/build-module/constants.js +0 -3
  41. package/build-module/constants.js.map +1 -1
  42. package/build-module/dataviews.js +3 -17
  43. package/build-module/dataviews.js.map +1 -1
  44. package/build-module/dropdown-menu-helper.js.map +1 -1
  45. package/build-module/filter-and-sort-data-view.js +139 -0
  46. package/build-module/filter-and-sort-data-view.js.map +1 -0
  47. package/build-module/filter-summary.js +3 -2
  48. package/build-module/filter-summary.js.map +1 -1
  49. package/build-module/filters.js +12 -18
  50. package/build-module/filters.js.map +1 -1
  51. package/build-module/index.js +1 -1
  52. package/build-module/index.js.map +1 -1
  53. package/build-module/item-actions.js.map +1 -1
  54. package/build-module/lock-unlock.js.map +1 -1
  55. package/build-module/normalize-fields.js +19 -0
  56. package/build-module/normalize-fields.js.map +1 -0
  57. package/build-module/pagination.js.map +1 -1
  58. package/build-module/reset-filters.js.map +1 -1
  59. package/build-module/search-widget.js +4 -3
  60. package/build-module/search-widget.js.map +1 -1
  61. package/build-module/search.js.map +1 -1
  62. package/build-module/single-selection-checkbox.js +1 -1
  63. package/build-module/single-selection-checkbox.js.map +1 -1
  64. package/build-module/utils.js +0 -63
  65. package/build-module/utils.js.map +1 -1
  66. package/build-module/view-actions.js.map +1 -1
  67. package/build-module/view-grid.js +58 -20
  68. package/build-module/view-grid.js.map +1 -1
  69. package/build-module/view-list.js +114 -68
  70. package/build-module/view-list.js.map +1 -1
  71. package/build-module/view-table.js +33 -25
  72. package/build-module/view-table.js.map +1 -1
  73. package/build-style/style-rtl.css +75 -39
  74. package/build-style/style.css +75 -39
  75. package/package.json +11 -11
  76. package/src/constants.js +0 -3
  77. package/src/dataviews.js +2 -16
  78. package/src/filter-and-sort-data-view.js +154 -0
  79. package/src/filter-summary.js +4 -4
  80. package/src/filters.js +20 -32
  81. package/src/index.js +1 -1
  82. package/src/normalize-fields.js +17 -0
  83. package/src/search-widget.js +4 -3
  84. package/src/single-selection-checkbox.js +1 -1
  85. package/src/stories/fixtures.js +75 -1
  86. package/src/stories/index.story.js +5 -113
  87. package/src/style.scss +89 -49
  88. package/src/test/filter-and-sort-data-view.js +276 -0
  89. package/src/utils.js +0 -52
  90. package/src/view-grid.js +97 -36
  91. package/src/view-list.js +147 -77
  92. package/src/view-table.js +36 -24
package/src/style.scss CHANGED
@@ -92,10 +92,6 @@
92
92
  &.dataviews-view-table__checkbox-column {
93
93
  padding-right: 0;
94
94
  }
95
-
96
- .components-checkbox-control__input-container {
97
- margin: $grid-unit-05;
98
- }
99
95
  }
100
96
  tr {
101
97
  border-bottom: 1px solid $gray-100;
@@ -150,6 +146,14 @@
150
146
  }
151
147
  }
152
148
 
149
+ @media (hover: none) {
150
+ // Show checkboxes and quick-actions on devices that do not support hover.
151
+ .components-checkbox-control__input.components-checkbox-control__input,
152
+ .dataviews-item-actions .components-button:not(.dataviews-all-actions-button) {
153
+ opacity: 1;
154
+ }
155
+ }
156
+
153
157
  &.is-selected {
154
158
  background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04);
155
159
  color: $gray-700;
@@ -190,6 +194,12 @@
190
194
  > * {
191
195
  flex-grow: 1;
192
196
  }
197
+
198
+ &.dataviews-view-table__primary-field {
199
+ a {
200
+ flex-grow: 0;
201
+ }
202
+ }
193
203
  }
194
204
  }
195
205
  .dataviews-view-table-header-button {
@@ -231,7 +241,7 @@
231
241
  .dataviews-view-table__primary-field {
232
242
  font-size: $default-font-size;
233
243
  font-weight: 500;
234
- color: $gray-900;
244
+ color: $gray-700;
235
245
  text-overflow: ellipsis;
236
246
  white-space: nowrap;
237
247
  display: block;
@@ -239,28 +249,31 @@
239
249
 
240
250
  a {
241
251
  text-decoration: none;
242
- color: inherit;
243
252
  text-overflow: ellipsis;
244
253
  white-space: nowrap;
245
254
  overflow: hidden;
246
255
  display: block;
247
- width: 100%;
256
+ flex-grow: 0;
257
+ color: $gray-900;
248
258
 
249
259
  &:hover {
250
- color: $gray-900;
260
+ color: var(--wp-admin-theme-color);
251
261
  }
252
262
  @include link-reset();
253
263
  }
254
264
 
255
265
  button.components-button.is-link {
256
266
  text-decoration: none;
257
- color: inherit;
258
267
  font-weight: inherit;
259
268
  text-overflow: ellipsis;
260
269
  white-space: nowrap;
261
270
  overflow: hidden;
262
271
  display: block;
263
272
  width: 100%;
273
+ color: $gray-900;
274
+ &:hover {
275
+ color: var(--wp-admin-theme-color);
276
+ }
264
277
  }
265
278
  }
266
279
 
@@ -322,16 +335,52 @@
322
335
  line-height: 16px;
323
336
 
324
337
  &:not(:empty) {
325
- padding: $grid-unit-15;
338
+ padding: $grid-unit-15 0;
326
339
  padding-top: 0;
340
+ margin: 0 $grid-unit-15;
327
341
  }
328
342
 
329
343
  .dataviews-view-grid__field {
330
- .dataviews-view-grid__field-value {
344
+ align-items: flex-start;
345
+
346
+ &:not(.is-column) {
347
+ align-items: center;
348
+
349
+ .dataviews-view-grid__field-name {
350
+ width: 35%;
351
+ }
352
+
353
+ .dataviews-view-grid__field-value {
354
+ width: 65%;
355
+ overflow: hidden;
356
+ text-overflow: ellipsis;
357
+ white-space: nowrap;
358
+ }
359
+ }
360
+
361
+ .dataviews-view-grid__field-name {
331
362
  color: $gray-700;
332
363
  }
333
364
  }
334
365
  }
366
+
367
+ .dataviews-view-grid__badge-fields {
368
+ &:not(:empty) {
369
+ padding: $grid-unit-15;
370
+ padding-top: 0;
371
+ }
372
+
373
+ .dataviews-view-grid__field-value {
374
+ width: fit-content;
375
+ background: $gray-100;
376
+ padding: 0 $grid-unit-10;
377
+ min-height: $grid-unit-30;
378
+ border-radius: $radius-block-ui;
379
+ display: flex;
380
+ align-items: center;
381
+ font-size: 12px;
382
+ }
383
+ }
335
384
  }
336
385
 
337
386
  .dataviews-view-list {
@@ -340,6 +389,7 @@
340
389
 
341
390
  li {
342
391
  margin: 0;
392
+ cursor: pointer;
343
393
 
344
394
  .dataviews-view-list__item-wrapper {
345
395
  position: relative;
@@ -355,14 +405,24 @@
355
405
  background: $gray-100;
356
406
  height: 1px;
357
407
  }
358
- }
359
408
 
360
- &:not(.is-selected):hover {
361
- color: var(--wp-admin-theme-color);
409
+ > * {
410
+ width: 100%;
411
+ }
412
+ }
362
413
 
363
- .dataviews-view-list__primary-field,
364
- .dataviews-view-list__fields {
414
+ &:not(.is-selected) {
415
+ .dataviews-view-list__primary-field {
416
+ color: $gray-900;
417
+ }
418
+ &:hover,
419
+ &:focus-within {
365
420
  color: var(--wp-admin-theme-color);
421
+
422
+ .dataviews-view-list__primary-field,
423
+ .dataviews-view-list__fields {
424
+ color: var(--wp-admin-theme-color);
425
+ }
366
426
  }
367
427
  }
368
428
  }
@@ -388,8 +448,9 @@
388
448
  .dataviews-view-list__item {
389
449
  padding: $grid-unit-15 0 $grid-unit-15 $grid-unit-30;
390
450
  width: 100%;
391
- cursor: pointer;
392
- &:focus {
451
+ scroll-margin: $grid-unit-10 0;
452
+
453
+ &:focus-visible {
393
454
  &::before {
394
455
  position: absolute;
395
456
  content: "";
@@ -404,6 +465,7 @@
404
465
  }
405
466
  .dataviews-view-list__primary-field {
406
467
  min-height: $grid-unit-05 * 5;
468
+ overflow: hidden;
407
469
  }
408
470
  }
409
471
 
@@ -449,7 +511,7 @@
449
511
  line-height: $grid-unit-20;
450
512
 
451
513
  .dataviews-view-list__field {
452
- &:empty {
514
+ &:has(.dataviews-view-list__field-value:empty) {
453
515
  display: none;
454
516
  }
455
517
  }
@@ -459,26 +521,6 @@
459
521
  justify-content: space-between;
460
522
  }
461
523
 
462
- .dataviews-view-list__details-button {
463
- align-self: center;
464
- opacity: 0;
465
- }
466
-
467
- li.is-selected,
468
- li:hover,
469
- li:focus-within {
470
- .dataviews-view-list__details-button {
471
- opacity: 1;
472
- }
473
- }
474
-
475
- li.is-selected {
476
- .dataviews-view-list__details-button {
477
- &:focus {
478
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) currentColor;
479
- }
480
- }
481
- }
482
524
  }
483
525
 
484
526
  .dataviews-action-modal {
@@ -494,16 +536,14 @@
494
536
  justify-content: center;
495
537
  }
496
538
 
497
- .dataviews-view-table-selection-checkbox label {
498
- position: absolute;
499
- width: 1px;
500
- height: 1px;
501
- padding: 0;
502
- margin: -1px;
503
- overflow: hidden;
504
- clip: rect(0, 0, 0, 0);
505
- white-space: nowrap;
506
- border: 0;
539
+ .dataviews-view-table-selection-checkbox {
540
+ // Experimental override for CheckboxControl size (fragile)
541
+ --checkbox-input-size: 24px;
542
+ @include break-small() {
543
+ --checkbox-input-size: 16px;
544
+ }
545
+
546
+ line-height: 0;
507
547
  }
508
548
 
509
549
  .dataviews-filters__custom-menu-radio-item-prefix {
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { filterSortAndPaginate } from '../filter-and-sort-data-view';
5
+ import { data, fields } from '../stories/fixtures';
6
+
7
+ describe( 'filters', () => {
8
+ it( 'should return empty if the data is empty', () => {
9
+ expect( filterSortAndPaginate( null, {}, [] ) ).toStrictEqual( {
10
+ data: [],
11
+ paginationInfo: { totalItems: 0, totalPages: 0 },
12
+ } );
13
+ } );
14
+
15
+ it( 'should return the same data if no filters are applied', () => {
16
+ expect(
17
+ filterSortAndPaginate(
18
+ data,
19
+ {
20
+ filters: [],
21
+ },
22
+ []
23
+ )
24
+ ).toStrictEqual( {
25
+ data,
26
+ paginationInfo: { totalItems: data.length, totalPages: 1 },
27
+ } );
28
+ } );
29
+
30
+ it( 'should search using searchable fields (title)', () => {
31
+ const { data: result } = filterSortAndPaginate(
32
+ data,
33
+ {
34
+ search: 'Neptu',
35
+ filters: [],
36
+ },
37
+ fields
38
+ );
39
+ expect( result ).toHaveLength( 1 );
40
+ expect( result[ 0 ].title ).toBe( 'Neptune' );
41
+ } );
42
+
43
+ it( 'should search using searchable fields (description)', () => {
44
+ const { data: result } = filterSortAndPaginate(
45
+ data,
46
+ {
47
+ search: 'photo',
48
+ filters: [],
49
+ },
50
+ fields
51
+ );
52
+ expect( result ).toHaveLength( 1 );
53
+ expect( result[ 0 ].description ).toBe( 'NASA photo' );
54
+ } );
55
+
56
+ it( 'should perform case-insensitive and accent-insensitive search', () => {
57
+ const { data: result } = filterSortAndPaginate(
58
+ data,
59
+ {
60
+ search: 'nete ven',
61
+ filters: [],
62
+ },
63
+ fields
64
+ );
65
+ expect( result ).toHaveLength( 1 );
66
+ expect( result[ 0 ].description ).toBe( 'La planète Vénus' );
67
+ } );
68
+
69
+ it( 'should search using IS filter', () => {
70
+ const { data: result } = filterSortAndPaginate(
71
+ data,
72
+ {
73
+ filters: [
74
+ {
75
+ field: 'type',
76
+ operator: 'is',
77
+ value: 'Ice giant',
78
+ },
79
+ ],
80
+ },
81
+ fields
82
+ );
83
+ expect( result ).toHaveLength( 2 );
84
+ expect( result[ 0 ].title ).toBe( 'Neptune' );
85
+ expect( result[ 1 ].title ).toBe( 'Uranus' );
86
+ } );
87
+
88
+ it( 'should search using IS NOT filter', () => {
89
+ const { data: result } = filterSortAndPaginate(
90
+ data,
91
+ {
92
+ filters: [
93
+ {
94
+ field: 'type',
95
+ operator: 'isNot',
96
+ value: 'Ice giant',
97
+ },
98
+ ],
99
+ },
100
+ fields
101
+ );
102
+ expect( result ).toHaveLength( 9 );
103
+ expect( result[ 0 ].title ).toBe( 'Apollo' );
104
+ expect( result[ 1 ].title ).toBe( 'Space' );
105
+ expect( result[ 2 ].title ).toBe( 'NASA' );
106
+ expect( result[ 3 ].title ).toBe( 'Mercury' );
107
+ expect( result[ 4 ].title ).toBe( 'Venus' );
108
+ expect( result[ 5 ].title ).toBe( 'Earth' );
109
+ expect( result[ 6 ].title ).toBe( 'Mars' );
110
+ expect( result[ 7 ].title ).toBe( 'Jupiter' );
111
+ expect( result[ 8 ].title ).toBe( 'Saturn' );
112
+ } );
113
+
114
+ it( 'should search using IS ANY filter for STRING values', () => {
115
+ const { data: result } = filterSortAndPaginate(
116
+ data,
117
+ {
118
+ filters: [
119
+ {
120
+ field: 'type',
121
+ operator: 'isAny',
122
+ value: [ 'Ice giant' ],
123
+ },
124
+ ],
125
+ },
126
+ fields
127
+ );
128
+ expect( result ).toHaveLength( 2 );
129
+ expect( result[ 0 ].title ).toBe( 'Neptune' );
130
+ expect( result[ 1 ].title ).toBe( 'Uranus' );
131
+ } );
132
+
133
+ it( 'should search using IS NONE filter for STRING values', () => {
134
+ const { data: result } = filterSortAndPaginate(
135
+ data,
136
+ {
137
+ filters: [
138
+ {
139
+ field: 'type',
140
+ operator: 'isNone',
141
+ value: [ 'Ice giant', 'Gas giant', 'Terrestrial' ],
142
+ },
143
+ ],
144
+ },
145
+ fields
146
+ );
147
+ expect( result ).toHaveLength( 3 );
148
+ expect( result[ 0 ].title ).toBe( 'Apollo' );
149
+ expect( result[ 1 ].title ).toBe( 'Space' );
150
+ expect( result[ 2 ].title ).toBe( 'NASA' );
151
+ } );
152
+
153
+ it( 'should search using IS ANY filter for ARRAY values', () => {
154
+ const { data: result } = filterSortAndPaginate(
155
+ data,
156
+ {
157
+ filters: [
158
+ {
159
+ field: 'categories',
160
+ operator: 'isAny',
161
+ value: [ 'NASA' ],
162
+ },
163
+ ],
164
+ },
165
+ fields
166
+ );
167
+ expect( result ).toHaveLength( 2 );
168
+ expect( result[ 0 ].title ).toBe( 'Apollo' );
169
+ expect( result[ 1 ].title ).toBe( 'NASA' );
170
+ } );
171
+
172
+ it( 'should search using IS NONE filter for ARRAY values', () => {
173
+ const { data: result } = filterSortAndPaginate(
174
+ data,
175
+ {
176
+ filters: [
177
+ {
178
+ field: 'categories',
179
+ operator: 'isNone',
180
+ value: [ 'Space' ],
181
+ },
182
+ ],
183
+ },
184
+ fields
185
+ );
186
+ expect( result ).toHaveLength( 1 );
187
+ expect( result[ 0 ].title ).toBe( 'NASA' );
188
+ } );
189
+
190
+ it( 'should search using IS ALL filter', () => {
191
+ const { data: result } = filterSortAndPaginate(
192
+ data,
193
+ {
194
+ filters: [
195
+ {
196
+ field: 'categories',
197
+ operator: 'isAll',
198
+ value: [ 'Planet', 'Solar system' ],
199
+ },
200
+ ],
201
+ },
202
+ fields
203
+ );
204
+ expect( result ).toHaveLength( 7 );
205
+ expect( result[ 0 ].title ).toBe( 'Neptune' );
206
+ expect( result[ 1 ].title ).toBe( 'Mercury' );
207
+ expect( result[ 2 ].title ).toBe( 'Venus' );
208
+ expect( result[ 3 ].title ).toBe( 'Earth' );
209
+ expect( result[ 4 ].title ).toBe( 'Mars' );
210
+ expect( result[ 5 ].title ).toBe( 'Jupiter' );
211
+ expect( result[ 6 ].title ).toBe( 'Saturn' );
212
+ } );
213
+
214
+ it( 'should search using IS NOT ALL filter', () => {
215
+ const { data: result } = filterSortAndPaginate(
216
+ data,
217
+ {
218
+ filters: [
219
+ {
220
+ field: 'categories',
221
+ operator: 'isNotAll',
222
+ value: [ 'Planet', 'Solar system' ],
223
+ },
224
+ ],
225
+ },
226
+ fields
227
+ );
228
+ expect( result ).toHaveLength( 3 );
229
+ expect( result[ 0 ].title ).toBe( 'Apollo' );
230
+ expect( result[ 1 ].title ).toBe( 'Space' );
231
+ expect( result[ 2 ].title ).toBe( 'NASA' );
232
+ } );
233
+ } );
234
+
235
+ describe( 'sorting', () => {
236
+ it( 'should sort', () => {
237
+ const { data: result } = filterSortAndPaginate(
238
+ data,
239
+ {
240
+ sort: { field: 'title', direction: 'desc' },
241
+ filters: [
242
+ {
243
+ field: 'type',
244
+ operator: 'isAny',
245
+ value: [ 'Ice giant' ],
246
+ },
247
+ ],
248
+ },
249
+ fields
250
+ );
251
+ expect( result ).toHaveLength( 2 );
252
+ expect( result[ 0 ].title ).toBe( 'Uranus' );
253
+ expect( result[ 1 ].title ).toBe( 'Neptune' );
254
+ } );
255
+ } );
256
+
257
+ describe( 'pagination', () => {
258
+ it( 'should paginate', () => {
259
+ const { data: result, paginationInfo } = filterSortAndPaginate(
260
+ data,
261
+ {
262
+ perPage: 2,
263
+ page: 2,
264
+ filters: [],
265
+ },
266
+ fields
267
+ );
268
+ expect( result ).toHaveLength( 2 );
269
+ expect( result[ 0 ].title ).toBe( 'NASA' );
270
+ expect( result[ 1 ].title ).toBe( 'Neptune' );
271
+ expect( paginationInfo ).toStrictEqual( {
272
+ totalItems: data.length,
273
+ totalPages: 6,
274
+ } );
275
+ } );
276
+ } );
package/src/utils.js CHANGED
@@ -9,58 +9,6 @@ import {
9
9
  OPERATOR_IS_NONE,
10
10
  } from './constants';
11
11
 
12
- /**
13
- * Helper util to sort data by text fields, when sorting is done client side.
14
- *
15
- * @param {Object} params Function params.
16
- * @param {Object[]} params.data Data to sort.
17
- * @param {Object} params.view Current view object.
18
- * @param {Object[]} params.fields Array of available fields.
19
- * @param {string[]} params.textFields Array of the field ids to sort.
20
- *
21
- * @return {Object[]} Sorted data.
22
- */
23
- export const sortByTextFields = ( { data, view, fields, textFields } ) => {
24
- const sortedData = [ ...data ];
25
- const fieldId = view.sort.field;
26
- if ( textFields.includes( fieldId ) ) {
27
- const fieldToSort = fields.find( ( field ) => {
28
- return field.id === fieldId;
29
- } );
30
- sortedData.sort( ( a, b ) => {
31
- const valueA = fieldToSort.getValue( { item: a } ) ?? '';
32
- const valueB = fieldToSort.getValue( { item: b } ) ?? '';
33
- return view.sort.direction === 'asc'
34
- ? valueA.localeCompare( valueB )
35
- : valueB.localeCompare( valueA );
36
- } );
37
- }
38
- return sortedData;
39
- };
40
-
41
- /**
42
- * Helper util to get the paginated data and the paginateInfo needed,
43
- * when pagination is done client side.
44
- *
45
- * @param {Object} params Function params.
46
- * @param {Object[]} params.data Available data.
47
- * @param {Object} params.view Current view object.
48
- *
49
- * @return {Object} Paginated data and paginationInfo.
50
- */
51
- export function getPaginationResults( { data, view } ) {
52
- const start = ( view.page - 1 ) * view.perPage;
53
- const totalItems = data?.length || 0;
54
- data = data?.slice( start, start + view.perPage );
55
- return {
56
- data,
57
- paginationInfo: {
58
- totalItems,
59
- totalPages: Math.ceil( totalItems / view.perPage ),
60
- },
61
- };
62
- }
63
-
64
12
  export const sanitizeOperators = ( field ) => {
65
13
  let operators = field.filterBy?.operators;
66
14