@wordpress/dataviews 6.0.1-next.46f643fa0.0 → 7.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 (235) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +50 -16
  3. package/build/components/dataviews/index.js +46 -6
  4. package/build/components/dataviews/index.js.map +1 -1
  5. package/build/components/dataviews-context/index.js +5 -1
  6. package/build/components/dataviews-context/index.js.map +1 -1
  7. package/build/components/dataviews-filters/filter.js +13 -6
  8. package/build/components/dataviews-filters/filter.js.map +1 -1
  9. package/build/components/dataviews-filters/index.js +16 -5
  10. package/build/components/dataviews-filters/index.js.map +1 -1
  11. package/build/components/dataviews-filters/reset-filters.js +2 -2
  12. package/build/components/dataviews-filters/reset-filters.js.map +1 -1
  13. package/build/components/dataviews-item-actions/index.js +1 -10
  14. package/build/components/dataviews-item-actions/index.js.map +1 -1
  15. package/build/components/dataviews-layout/index.js +5 -2
  16. package/build/components/dataviews-layout/index.js.map +1 -1
  17. package/build/components/dataviews-pagination/index.js +1 -1
  18. package/build/components/dataviews-pagination/index.js.map +1 -1
  19. package/build/components/dataviews-view-config/index.js +10 -6
  20. package/build/components/dataviews-view-config/index.js.map +1 -1
  21. package/build/components/dataviews-view-config/infinite-scroll-toggle.js +47 -0
  22. package/build/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
  23. package/build/dataform-controls/array.js +70 -0
  24. package/build/dataform-controls/array.js.map +1 -0
  25. package/build/dataform-controls/boolean.js +15 -7
  26. package/build/dataform-controls/boolean.js.map +1 -1
  27. package/build/dataform-controls/email.js +14 -7
  28. package/build/dataform-controls/email.js.map +1 -1
  29. package/build/dataform-controls/index.js +3 -1
  30. package/build/dataform-controls/index.js.map +1 -1
  31. package/build/dataform-controls/integer.js +14 -7
  32. package/build/dataform-controls/integer.js.map +1 -1
  33. package/build/dataform-controls/select.js +12 -5
  34. package/build/dataform-controls/select.js.map +1 -1
  35. package/build/dataform-controls/text.js +14 -7
  36. package/build/dataform-controls/text.js.map +1 -1
  37. package/build/dataforms-layouts/card/index.js +137 -0
  38. package/build/dataforms-layouts/card/index.js.map +1 -0
  39. package/build/dataforms-layouts/data-form-layout.js +2 -2
  40. package/build/dataforms-layouts/data-form-layout.js.map +1 -1
  41. package/build/dataforms-layouts/index.js +4 -0
  42. package/build/dataforms-layouts/index.js.map +1 -1
  43. package/build/dataforms-layouts/panel/dropdown.js +124 -0
  44. package/build/dataforms-layouts/panel/dropdown.js.map +1 -0
  45. package/build/dataforms-layouts/panel/index.js +34 -149
  46. package/build/dataforms-layouts/panel/index.js.map +1 -1
  47. package/build/dataforms-layouts/panel/modal.js +125 -0
  48. package/build/dataforms-layouts/panel/modal.js.map +1 -0
  49. package/build/dataforms-layouts/regular/index.js +10 -21
  50. package/build/dataforms-layouts/regular/index.js.map +1 -1
  51. package/build/dataviews-layouts/grid/index.js +27 -9
  52. package/build/dataviews-layouts/grid/index.js.map +1 -1
  53. package/build/dataviews-layouts/grid/preview-size-picker.js +11 -11
  54. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  55. package/build/dataviews-layouts/list/index.js +48 -29
  56. package/build/dataviews-layouts/list/index.js.map +1 -1
  57. package/build/dataviews-layouts/table/column-header-menu.js +3 -0
  58. package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
  59. package/build/dataviews-layouts/table/column-primary.js +14 -2
  60. package/build/dataviews-layouts/table/column-primary.js.map +1 -1
  61. package/build/dataviews-layouts/table/index.js +71 -11
  62. package/build/dataviews-layouts/table/index.js.map +1 -1
  63. package/build/field-types/array.js +2 -2
  64. package/build/field-types/array.js.map +1 -1
  65. package/build/normalize-form-fields.js +52 -13
  66. package/build/normalize-form-fields.js.map +1 -1
  67. package/build/types.js.map +1 -1
  68. package/build-module/components/dataviews/index.js +48 -8
  69. package/build-module/components/dataviews/index.js.map +1 -1
  70. package/build-module/components/dataviews-context/index.js +5 -1
  71. package/build-module/components/dataviews-context/index.js.map +1 -1
  72. package/build-module/components/dataviews-filters/filter.js +13 -6
  73. package/build-module/components/dataviews-filters/filter.js.map +1 -1
  74. package/build-module/components/dataviews-filters/index.js +16 -5
  75. package/build-module/components/dataviews-filters/index.js.map +1 -1
  76. package/build-module/components/dataviews-filters/reset-filters.js +2 -2
  77. package/build-module/components/dataviews-filters/reset-filters.js.map +1 -1
  78. package/build-module/components/dataviews-item-actions/index.js +1 -10
  79. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  80. package/build-module/components/dataviews-layout/index.js +5 -2
  81. package/build-module/components/dataviews-layout/index.js.map +1 -1
  82. package/build-module/components/dataviews-pagination/index.js +1 -1
  83. package/build-module/components/dataviews-pagination/index.js.map +1 -1
  84. package/build-module/components/dataviews-view-config/index.js +10 -6
  85. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  86. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js +39 -0
  87. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
  88. package/build-module/dataform-controls/array.js +63 -0
  89. package/build-module/dataform-controls/array.js.map +1 -0
  90. package/build-module/dataform-controls/boolean.js +15 -7
  91. package/build-module/dataform-controls/boolean.js.map +1 -1
  92. package/build-module/dataform-controls/email.js +15 -8
  93. package/build-module/dataform-controls/email.js.map +1 -1
  94. package/build-module/dataform-controls/index.js +3 -1
  95. package/build-module/dataform-controls/index.js.map +1 -1
  96. package/build-module/dataform-controls/integer.js +15 -8
  97. package/build-module/dataform-controls/integer.js.map +1 -1
  98. package/build-module/dataform-controls/select.js +12 -5
  99. package/build-module/dataform-controls/select.js.map +1 -1
  100. package/build-module/dataform-controls/text.js +15 -8
  101. package/build-module/dataform-controls/text.js.map +1 -1
  102. package/build-module/dataforms-layouts/card/index.js +128 -0
  103. package/build-module/dataforms-layouts/card/index.js.map +1 -0
  104. package/build-module/dataforms-layouts/data-form-layout.js +2 -2
  105. package/build-module/dataforms-layouts/data-form-layout.js.map +1 -1
  106. package/build-module/dataforms-layouts/index.js +4 -0
  107. package/build-module/dataforms-layouts/index.js.map +1 -1
  108. package/build-module/dataforms-layouts/panel/dropdown.js +118 -0
  109. package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -0
  110. package/build-module/dataforms-layouts/panel/index.js +37 -152
  111. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  112. package/build-module/dataforms-layouts/panel/modal.js +119 -0
  113. package/build-module/dataforms-layouts/panel/modal.js.map +1 -0
  114. package/build-module/dataforms-layouts/regular/index.js +10 -21
  115. package/build-module/dataforms-layouts/regular/index.js.map +1 -1
  116. package/build-module/dataviews-layouts/grid/index.js +28 -10
  117. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  118. package/build-module/dataviews-layouts/grid/preview-size-picker.js +11 -11
  119. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  120. package/build-module/dataviews-layouts/list/index.js +50 -31
  121. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  122. package/build-module/dataviews-layouts/table/column-header-menu.js +3 -0
  123. package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
  124. package/build-module/dataviews-layouts/table/column-primary.js +14 -2
  125. package/build-module/dataviews-layouts/table/column-primary.js.map +1 -1
  126. package/build-module/dataviews-layouts/table/index.js +72 -12
  127. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  128. package/build-module/field-types/array.js +2 -2
  129. package/build-module/field-types/array.js.map +1 -1
  130. package/build-module/normalize-form-fields.js +50 -13
  131. package/build-module/normalize-form-fields.js.map +1 -1
  132. package/build-module/types.js.map +1 -1
  133. package/build-style/style-rtl.css +78 -20
  134. package/build-style/style.css +78 -20
  135. package/build-types/components/dataform/stories/index.story.d.ts +41 -17
  136. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  137. package/build-types/components/dataviews/index.d.ts +6 -2
  138. package/build-types/components/dataviews/index.d.ts.map +1 -1
  139. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  140. package/build-types/components/dataviews/stories/index.story.d.ts +18 -4
  141. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  142. package/build-types/components/dataviews-context/index.d.ts +6 -2
  143. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  144. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  145. package/build-types/components/dataviews-filters/index.d.ts.map +1 -1
  146. package/build-types/components/dataviews-filters/reset-filters.d.ts.map +1 -1
  147. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  148. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  149. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  150. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +2 -0
  151. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -0
  152. package/build-types/dataform-controls/array.d.ts +6 -0
  153. package/build-types/dataform-controls/array.d.ts.map +1 -0
  154. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  155. package/build-types/dataform-controls/email.d.ts.map +1 -1
  156. package/build-types/dataform-controls/index.d.ts.map +1 -1
  157. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  158. package/build-types/dataform-controls/select.d.ts.map +1 -1
  159. package/build-types/dataform-controls/text.d.ts.map +1 -1
  160. package/build-types/dataforms-layouts/card/index.d.ts +13 -0
  161. package/build-types/dataforms-layouts/card/index.d.ts.map +1 -0
  162. package/build-types/dataforms-layouts/index.d.ts.map +1 -1
  163. package/build-types/dataforms-layouts/panel/dropdown.d.ts +14 -0
  164. package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -0
  165. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  166. package/build-types/dataforms-layouts/panel/modal.d.ts +13 -0
  167. package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -0
  168. package/build-types/dataforms-layouts/regular/index.d.ts.map +1 -1
  169. package/build-types/dataviews-layouts/grid/index.d.ts +1 -1
  170. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  171. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +1 -1
  172. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  173. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  174. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  175. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  176. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  177. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  178. package/build-types/field-types/boolean.d.ts +1 -1
  179. package/build-types/normalize-form-fields.d.ts +10 -3
  180. package/build-types/normalize-form-fields.d.ts.map +1 -1
  181. package/build-types/test/normalize-form-fields.d.ts +2 -0
  182. package/build-types/test/normalize-form-fields.d.ts.map +1 -0
  183. package/build-types/types.d.ts +64 -7
  184. package/build-types/types.d.ts.map +1 -1
  185. package/build-wp/index.js +3219 -1186
  186. package/package.json +15 -15
  187. package/src/components/dataform/stories/index.story.tsx +479 -91
  188. package/src/components/dataviews/index.tsx +74 -16
  189. package/src/components/dataviews/stories/fixtures.tsx +100 -8
  190. package/src/components/dataviews/stories/index.story.tsx +185 -32
  191. package/src/components/dataviews/stories/style.css +6 -0
  192. package/src/components/dataviews/style.scss +4 -0
  193. package/src/components/dataviews-context/index.ts +8 -2
  194. package/src/components/dataviews-filters/filter.tsx +15 -5
  195. package/src/components/dataviews-filters/index.tsx +17 -2
  196. package/src/components/dataviews-filters/reset-filters.tsx +4 -2
  197. package/src/components/dataviews-filters/style.scss +5 -1
  198. package/src/components/dataviews-item-actions/index.tsx +7 -16
  199. package/src/components/dataviews-layout/index.tsx +3 -0
  200. package/src/components/dataviews-pagination/index.tsx +1 -1
  201. package/src/components/dataviews-view-config/index.tsx +16 -6
  202. package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +39 -0
  203. package/src/dataform-controls/array.tsx +85 -0
  204. package/src/dataform-controls/boolean.tsx +24 -10
  205. package/src/dataform-controls/email.tsx +24 -11
  206. package/src/dataform-controls/index.tsx +3 -1
  207. package/src/dataform-controls/integer.tsx +27 -13
  208. package/src/dataform-controls/select.tsx +23 -13
  209. package/src/dataform-controls/text.tsx +24 -11
  210. package/src/dataforms-layouts/card/index.tsx +154 -0
  211. package/src/dataforms-layouts/card/style.scss +3 -0
  212. package/src/dataforms-layouts/data-form-layout.tsx +2 -2
  213. package/src/dataforms-layouts/index.tsx +5 -0
  214. package/src/dataforms-layouts/panel/dropdown.tsx +160 -0
  215. package/src/dataforms-layouts/panel/index.tsx +49 -189
  216. package/src/dataforms-layouts/panel/modal.tsx +165 -0
  217. package/src/dataforms-layouts/panel/style.scss +4 -0
  218. package/src/dataforms-layouts/regular/index.tsx +20 -23
  219. package/src/dataviews-layouts/grid/index.tsx +34 -6
  220. package/src/dataviews-layouts/grid/preview-size-picker.tsx +15 -13
  221. package/src/dataviews-layouts/grid/style.scss +5 -3
  222. package/src/dataviews-layouts/list/index.tsx +67 -34
  223. package/src/dataviews-layouts/list/style.scss +7 -3
  224. package/src/dataviews-layouts/table/column-header-menu.tsx +4 -0
  225. package/src/dataviews-layouts/table/column-primary.tsx +24 -4
  226. package/src/dataviews-layouts/table/index.tsx +138 -36
  227. package/src/dataviews-layouts/table/style.scss +23 -1
  228. package/src/field-types/array.tsx +1 -1
  229. package/src/normalize-form-fields.ts +63 -17
  230. package/src/test/dataform.tsx +181 -3
  231. package/src/test/dataviews.tsx +38 -0
  232. package/src/test/filter-and-sort-data-view.js +126 -65
  233. package/src/test/normalize-form-fields.ts +247 -0
  234. package/src/types.ts +89 -7
  235. package/tsconfig.tsbuildinfo +1 -1
@@ -36,6 +36,10 @@ export function useFilters( fields: NormalizedField< any >[], view: View ) {
36
36
 
37
37
  const operators = field.filterBy.operators;
38
38
  const isPrimary = !! field.filterBy?.isPrimary;
39
+ const isLocked =
40
+ view.filters?.some(
41
+ ( f ) => f.field === field.id && !! f.isLocked
42
+ ) ?? false;
39
43
  filters.push( {
40
44
  field: field.id,
41
45
  name: field.label,
@@ -45,6 +49,7 @@ export function useFilters( fields: NormalizedField< any >[], view: View ) {
45
49
  ),
46
50
  operators,
47
51
  isVisible:
52
+ isLocked ||
48
53
  isPrimary ||
49
54
  !! view.filters?.some(
50
55
  ( f ) =>
@@ -52,11 +57,21 @@ export function useFilters( fields: NormalizedField< any >[], view: View ) {
52
57
  ALL_OPERATORS.includes( f.operator )
53
58
  ),
54
59
  isPrimary,
60
+ isLocked,
55
61
  } );
56
62
  } );
57
- // Sort filters by primary property. We need the primary filters to be first.
58
- // Then we sort by name.
63
+
64
+ // Sort filters by:
65
+ // - locked filters go first
66
+ // - primary filters go next
67
+ // - then, sort by name
59
68
  filters.sort( ( a, b ) => {
69
+ if ( a.isLocked && ! b.isLocked ) {
70
+ return -1;
71
+ }
72
+ if ( ! a.isLocked && b.isLocked ) {
73
+ return 1;
74
+ }
60
75
  if ( a.isPrimary && ! b.isPrimary ) {
61
76
  return -1;
62
77
  }
@@ -28,7 +28,8 @@ export default function ResetFilter( {
28
28
  ! view.search &&
29
29
  ! view.filters?.some(
30
30
  ( _filter ) =>
31
- _filter.value !== undefined || ! isPrimary( _filter.field )
31
+ ! _filter.isLocked &&
32
+ ( _filter.value !== undefined || ! isPrimary( _filter.field ) )
32
33
  );
33
34
  return (
34
35
  <Button
@@ -42,7 +43,8 @@ export default function ResetFilter( {
42
43
  ...view,
43
44
  page: 1,
44
45
  search: '',
45
- filters: [],
46
+ filters:
47
+ view.filters?.filter( ( f ) => !! f.isLocked ) || [],
46
48
  } );
47
49
  } }
48
50
  >
@@ -80,11 +80,15 @@
80
80
  align-items: center;
81
81
  box-sizing: border-box;
82
82
 
83
+ &.is-not-clickable {
84
+ cursor: default;
85
+ }
86
+
83
87
  &.has-reset {
84
88
  padding-inline-end: $button-size-small + $grid-unit-05;
85
89
  }
86
90
 
87
- &:hover,
91
+ &:hover:not(&.is-not-clickable),
88
92
  &:focus-visible,
89
93
  &[aria-expanded="true"] {
90
94
  background: $gray-200;
@@ -179,17 +179,6 @@ export default function ItemActions< Item >( {
179
179
  );
180
180
  }
181
181
 
182
- // If all actions are primary, there is no need to render the dropdown.
183
- if ( primaryActions.length === eligibleActions.length ) {
184
- return (
185
- <PrimaryActions
186
- item={ item }
187
- actions={ primaryActions }
188
- registry={ registry }
189
- />
190
- );
191
- }
192
-
193
182
  return (
194
183
  <HStack
195
184
  spacing={ 1 }
@@ -205,11 +194,13 @@ export default function ItemActions< Item >( {
205
194
  actions={ primaryActions }
206
195
  registry={ registry }
207
196
  />
208
- <CompactItemActions
209
- item={ item }
210
- actions={ eligibleActions }
211
- registry={ registry }
212
- />
197
+ { primaryActions.length < eligibleActions.length && (
198
+ <CompactItemActions
199
+ item={ item }
200
+ actions={ eligibleActions }
201
+ registry={ registry }
202
+ />
203
+ ) }
213
204
  </HStack>
214
205
  );
215
206
  }
@@ -7,6 +7,7 @@ import type { ComponentType } from 'react';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { useContext } from '@wordpress/element';
10
+ import { __ } from '@wordpress/i18n';
10
11
 
11
12
  /**
12
13
  * Internal dependencies
@@ -35,6 +36,7 @@ export default function DataViewsLayout( { className }: DataViewsLayoutProps ) {
35
36
  onClickItem,
36
37
  isItemClickable,
37
38
  renderItemLink,
39
+ empty = __( 'No results' ),
38
40
  } = useContext( DataViewsContext );
39
41
 
40
42
  const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
@@ -57,6 +59,7 @@ export default function DataViewsLayout( { className }: DataViewsLayoutProps ) {
57
59
  renderItemLink={ renderItemLink }
58
60
  isItemClickable={ isItemClickable }
59
61
  view={ view }
62
+ empty={ empty }
60
63
  />
61
64
  );
62
65
  }
@@ -22,7 +22,7 @@ export function DataViewsPagination() {
22
22
  paginationInfo: { totalItems = 0, totalPages },
23
23
  } = useContext( DataViewsContext );
24
24
 
25
- if ( ! totalItems || ! totalPages ) {
25
+ if ( ! totalItems || ! totalPages || view.infiniteScrollEnabled ) {
26
26
  return null;
27
27
  }
28
28
 
@@ -47,6 +47,7 @@ import { SORTING_DIRECTIONS, sortIcons, sortLabels } from '../../constants';
47
47
  import { VIEW_LAYOUTS } from '../../dataviews-layouts';
48
48
  import type { NormalizedField, View } from '../../types';
49
49
  import DataViewsContext from '../dataviews-context';
50
+ import InfiniteScrollToggle from './infinite-scroll-toggle';
50
51
  import { unlock } from '../../lock-unlock';
51
52
 
52
53
  const { Menu } = unlock( componentsPrivateApis );
@@ -102,12 +103,11 @@ export function ViewTypeMenu() {
102
103
  if ( 'layout' in viewWithoutLayout ) {
103
104
  delete viewWithoutLayout.layout;
104
105
  }
105
- // @ts-expect-error
106
106
  return onChangeView( {
107
107
  ...viewWithoutLayout,
108
108
  type: e.target.value,
109
109
  ...defaultLayouts[ e.target.value ],
110
- } );
110
+ } as View );
111
111
  }
112
112
  warning( 'Invalid dataview' );
113
113
  } }
@@ -213,10 +213,19 @@ function SortDirectionControl() {
213
213
  );
214
214
  }
215
215
 
216
- const PAGE_SIZE_VALUES = [ 10, 20, 50, 100 ];
217
216
  function ItemsPerPageControl() {
218
- const { view, perPageSizes, onChangeView } = useContext( DataViewsContext );
219
- const pageSizeValues = perPageSizes ?? PAGE_SIZE_VALUES;
217
+ const { view, config, onChangeView } = useContext( DataViewsContext );
218
+ const { infiniteScrollEnabled } = view;
219
+ if (
220
+ ! config ||
221
+ ! config.perPageSizes ||
222
+ config.perPageSizes.length < 2 ||
223
+ config.perPageSizes.length > 6 ||
224
+ infiniteScrollEnabled
225
+ ) {
226
+ return null;
227
+ }
228
+
220
229
  return (
221
230
  <ToggleGroupControl
222
231
  __nextHasNoMarginBottom
@@ -238,7 +247,7 @@ function ItemsPerPageControl() {
238
247
  } );
239
248
  } }
240
249
  >
241
- { pageSizeValues.map( ( value ) => {
250
+ { config.perPageSizes.map( ( value ) => {
242
251
  return (
243
252
  <ToggleGroupControlOption
244
253
  key={ value }
@@ -799,6 +808,7 @@ export function DataviewsViewConfigDropdown() {
799
808
  { !! activeLayout?.viewConfigOptions && (
800
809
  <activeLayout.viewConfigOptions />
801
810
  ) }
811
+ <InfiniteScrollToggle />
802
812
  <ItemsPerPageControl />
803
813
  </SettingsSection>
804
814
  <SettingsSection title={ __( 'Properties' ) }>
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { ToggleControl } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { useContext } from '@wordpress/element';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import DataViewsContext from '../dataviews-context';
12
+
13
+ export default function InfiniteScrollToggle() {
14
+ const context = useContext( DataViewsContext );
15
+ const { view, onChangeView } = context;
16
+ const infiniteScrollEnabled = view.infiniteScrollEnabled ?? false;
17
+
18
+ // Only render the toggle if an infinite scroll handler is available
19
+ if ( ! context.hasInfiniteScrollHandler ) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <ToggleControl
25
+ __nextHasNoMarginBottom
26
+ label={ __( 'Enable infinite scroll' ) }
27
+ help={ __(
28
+ 'Automatically load more content as you scroll, instead of showing pagination links.'
29
+ ) }
30
+ checked={ infiniteScrollEnabled }
31
+ onChange={ ( newValue ) => {
32
+ onChangeView( {
33
+ ...view,
34
+ infiniteScrollEnabled: newValue,
35
+ } );
36
+ } }
37
+ />
38
+ );
39
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { FormTokenField } from '@wordpress/components';
5
+ import { useCallback, useMemo } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { DataFormControlProps } from '../types';
11
+
12
+ export default function ArrayControl< Item >( {
13
+ data,
14
+ field,
15
+ onChange,
16
+ hideLabelFromVision,
17
+ }: DataFormControlProps< Item > ) {
18
+ const { id, label, placeholder, elements } = field;
19
+ const value = field.getValue( { item: data } );
20
+
21
+ const findElementByValue = useCallback(
22
+ ( suggestionValue: string ) => {
23
+ return elements?.find(
24
+ ( suggestion ) => suggestion.value === suggestionValue
25
+ );
26
+ },
27
+ [ elements ]
28
+ );
29
+
30
+ const findElementByLabel = useCallback(
31
+ ( suggestionLabel: string ) => {
32
+ return elements?.find(
33
+ ( suggestion ) => suggestion.label === suggestionLabel
34
+ );
35
+ },
36
+ [ elements ]
37
+ );
38
+
39
+ // Ensure value is an array
40
+ const arrayValue = useMemo(
41
+ () =>
42
+ Array.isArray( value )
43
+ ? value.map( ( token ) => {
44
+ const tokenLabel = findElementByValue( token )?.label;
45
+ return tokenLabel || token;
46
+ } )
47
+ : [],
48
+ [ value, findElementByValue ]
49
+ );
50
+
51
+ const onChangeControl = useCallback(
52
+ ( tokens: ( string | { value: string } )[] ) => {
53
+ // Convert TokenItem objects to strings
54
+ const stringTokens = tokens.map( ( token ) => {
55
+ if ( typeof token !== 'string' ) {
56
+ return token.value;
57
+ }
58
+
59
+ const tokenByLabel = findElementByLabel( token );
60
+
61
+ return tokenByLabel?.value || token;
62
+ } );
63
+
64
+ onChange( {
65
+ [ id ]: stringTokens,
66
+ } );
67
+ },
68
+ [ id, onChange, findElementByLabel ]
69
+ );
70
+
71
+ return (
72
+ <FormTokenField
73
+ label={ hideLabelFromVision ? undefined : label }
74
+ value={ arrayValue }
75
+ onChange={ onChangeControl }
76
+ placeholder={ placeholder }
77
+ suggestions={
78
+ elements?.map( ( suggestion ) => suggestion.label ) ?? []
79
+ }
80
+ __experimentalExpandOnFocus={ elements && elements.length > 0 }
81
+ __next40pxDefaultSize
82
+ __nextHasNoMarginBottom
83
+ />
84
+ );
85
+ }
@@ -2,6 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { privateApis } from '@wordpress/components';
5
+ import { useState } from '@wordpress/element';
5
6
 
6
7
  /**
7
8
  * Internal dependencies
@@ -18,23 +19,36 @@ export default function Boolean< Item >( {
18
19
  hideLabelFromVision,
19
20
  }: DataFormControlProps< Item > ) {
20
21
  const { id, getValue, label } = field;
22
+ const [ customValidity, setCustomValidity ] =
23
+ useState<
24
+ React.ComponentProps<
25
+ typeof ValidatedToggleControl
26
+ >[ 'customValidity' ]
27
+ >( undefined );
21
28
 
22
29
  return (
23
30
  <ValidatedToggleControl
24
31
  required={ !! field.isValid.required }
25
- customValidator={ ( newValue: any ) => {
26
- if ( field.isValid?.custom ) {
27
- return field.isValid.custom(
28
- {
29
- ...data,
30
- [ id ]: newValue,
31
- },
32
- field
33
- );
32
+ onValidate={ ( newValue: any ) => {
33
+ const message = field.isValid?.custom?.(
34
+ {
35
+ ...data,
36
+ [ id ]: newValue,
37
+ },
38
+ field
39
+ );
40
+
41
+ if ( message ) {
42
+ setCustomValidity( {
43
+ type: 'invalid',
44
+ message,
45
+ } );
46
+ return;
34
47
  }
35
48
 
36
- return null;
49
+ setCustomValidity( undefined );
37
50
  } }
51
+ customValidity={ customValidity }
38
52
  hidden={ hideLabelFromVision }
39
53
  __nextHasNoMarginBottom
40
54
  label={ label }
@@ -2,7 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { privateApis } from '@wordpress/components';
5
- import { useCallback } from '@wordpress/element';
5
+ import { useCallback, useState } from '@wordpress/element';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
@@ -20,6 +20,12 @@ export default function Email< Item >( {
20
20
  }: DataFormControlProps< Item > ) {
21
21
  const { id, label, placeholder, description } = field;
22
22
  const value = field.getValue( { item: data } );
23
+ const [ customValidity, setCustomValidity ] =
24
+ useState<
25
+ React.ComponentProps<
26
+ typeof ValidatedTextControl
27
+ >[ 'customValidity' ]
28
+ >( undefined );
23
29
 
24
30
  const onChangeControl = useCallback(
25
31
  ( newValue: string ) =>
@@ -32,19 +38,26 @@ export default function Email< Item >( {
32
38
  return (
33
39
  <ValidatedTextControl
34
40
  required={ !! field.isValid?.required }
35
- customValidator={ ( newValue: any ) => {
36
- if ( field.isValid?.custom ) {
37
- return field.isValid.custom(
38
- {
39
- ...data,
40
- [ id ]: newValue,
41
- },
42
- field
43
- );
41
+ onValidate={ ( newValue: any ) => {
42
+ const message = field.isValid?.custom?.(
43
+ {
44
+ ...data,
45
+ [ id ]: newValue,
46
+ },
47
+ field
48
+ );
49
+
50
+ if ( message ) {
51
+ setCustomValidity( {
52
+ type: 'invalid',
53
+ message,
54
+ } );
55
+ return;
44
56
  }
45
57
 
46
- return null;
58
+ setCustomValidity( undefined );
47
59
  } }
60
+ customValidity={ customValidity }
48
61
  type="email"
49
62
  label={ label }
50
63
  placeholder={ placeholder }
@@ -21,12 +21,14 @@ import select from './select';
21
21
  import text from './text';
22
22
  import toggleGroup from './toggle-group';
23
23
  import boolean from './boolean';
24
+ import array from './array';
24
25
 
25
26
  interface FormControls {
26
27
  [ key: string ]: ComponentType< DataFormControlProps< any > >;
27
28
  }
28
29
 
29
30
  const FORM_CONTROLS: FormControls = {
31
+ array,
30
32
  boolean,
31
33
  checkbox,
32
34
  datetime,
@@ -51,7 +53,7 @@ export function getControl< Item >(
51
53
  return getControlByType( field.Edit );
52
54
  }
53
55
 
54
- if ( field.elements ) {
56
+ if ( field.elements && field.type !== 'array' ) {
55
57
  return getControlByType( 'select' );
56
58
  }
57
59
 
@@ -7,7 +7,7 @@ import {
7
7
  __experimentalNumberControl as NumberControl,
8
8
  privateApis,
9
9
  } from '@wordpress/components';
10
- import { useCallback } from '@wordpress/element';
10
+ import { useCallback, useState } from '@wordpress/element';
11
11
  import { __ } from '@wordpress/i18n';
12
12
 
13
13
  /**
@@ -84,6 +84,13 @@ export default function Integer< Item >( {
84
84
  }: DataFormControlProps< Item > ) {
85
85
  const { id, label, description } = field;
86
86
  const value = field.getValue( { item: data } ) ?? '';
87
+ const [ customValidity, setCustomValidity ] =
88
+ useState<
89
+ React.ComponentProps<
90
+ typeof ValidatedNumberControl
91
+ >[ 'customValidity' ]
92
+ >( undefined );
93
+
87
94
  const onChangeControl = useCallback(
88
95
  ( newValue: string | undefined ) => {
89
96
  onChange( {
@@ -112,21 +119,28 @@ export default function Integer< Item >( {
112
119
  return (
113
120
  <ValidatedNumberControl
114
121
  required={ !! field.isValid?.required }
115
- customValidator={ ( newValue: any ) => {
116
- if ( field.isValid?.custom ) {
117
- return field.isValid.custom(
118
- {
119
- ...data,
120
- [ id ]: [ undefined, '', null ].includes( newValue )
121
- ? undefined
122
- : Number( newValue ),
123
- },
124
- field
125
- );
122
+ onValidate={ ( newValue: any ) => {
123
+ const message = field.isValid?.custom?.(
124
+ {
125
+ ...data,
126
+ [ id ]: [ undefined, '', null ].includes( newValue )
127
+ ? undefined
128
+ : Number( newValue ),
129
+ },
130
+ field
131
+ );
132
+
133
+ if ( message ) {
134
+ setCustomValidity( {
135
+ type: 'invalid',
136
+ message,
137
+ } );
138
+ return;
126
139
  }
127
140
 
128
- return null;
141
+ setCustomValidity( undefined );
129
142
  } }
143
+ customValidity={ customValidity }
130
144
  label={ label }
131
145
  help={ description }
132
146
  value={ value }
@@ -16,8 +16,9 @@ export default function Select< Item >( {
16
16
  onChange,
17
17
  hideLabelFromVision,
18
18
  }: DataFormControlProps< Item > ) {
19
- const { id, label } = field;
20
- const value = field.getValue( { item: data } ) ?? '';
19
+ const { id, label, type } = field;
20
+ const isMultiple = type === 'array';
21
+ const value = field.getValue( { item: data } ) ?? ( isMultiple ? [] : '' );
21
22
  const onChangeControl = useCallback(
22
23
  ( newValue: any ) =>
23
24
  onChange( {
@@ -26,17 +27,25 @@ export default function Select< Item >( {
26
27
  [ id, onChange ]
27
28
  );
28
29
 
29
- const elements = [
30
- /*
31
- * Value can be undefined when:
32
- *
33
- * - the field is not required
34
- * - in bulk editing
35
- *
36
- */
37
- { label: __( 'Select item' ), value: '' },
38
- ...( field?.elements ?? [] ),
39
- ];
30
+ const fieldElements = field?.elements ?? [];
31
+ const hasEmptyValue = fieldElements.some(
32
+ ( { value: elementValue } ) => elementValue === ''
33
+ );
34
+
35
+ const elements =
36
+ hasEmptyValue || isMultiple
37
+ ? fieldElements
38
+ : [
39
+ /*
40
+ * Value can be undefined when:
41
+ *
42
+ * - the field is not required
43
+ * - in bulk editing
44
+ *
45
+ */
46
+ { label: __( 'Select item' ), value: '' },
47
+ ...fieldElements,
48
+ ];
40
49
 
41
50
  return (
42
51
  <SelectControl
@@ -48,6 +57,7 @@ export default function Select< Item >( {
48
57
  __next40pxDefaultSize
49
58
  __nextHasNoMarginBottom
50
59
  hideLabelFromVision={ hideLabelFromVision }
60
+ multiple={ isMultiple }
51
61
  />
52
62
  );
53
63
  }
@@ -2,7 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { privateApis } from '@wordpress/components';
5
- import { useCallback } from '@wordpress/element';
5
+ import { useCallback, useState } from '@wordpress/element';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
@@ -20,6 +20,12 @@ export default function Text< Item >( {
20
20
  }: DataFormControlProps< Item > ) {
21
21
  const { id, label, placeholder, description } = field;
22
22
  const value = field.getValue( { item: data } );
23
+ const [ customValidity, setCustomValidity ] =
24
+ useState<
25
+ React.ComponentProps<
26
+ typeof ValidatedTextControl
27
+ >[ 'customValidity' ]
28
+ >( undefined );
23
29
 
24
30
  const onChangeControl = useCallback(
25
31
  ( newValue: string ) =>
@@ -32,19 +38,26 @@ export default function Text< Item >( {
32
38
  return (
33
39
  <ValidatedTextControl
34
40
  required={ !! field.isValid?.required }
35
- customValidator={ ( newValue: any ) => {
36
- if ( field.isValid?.custom ) {
37
- return field.isValid.custom(
38
- {
39
- ...data,
40
- [ id ]: newValue,
41
- },
42
- field
43
- );
41
+ onValidate={ ( newValue: any ) => {
42
+ const message = field.isValid?.custom?.(
43
+ {
44
+ ...data,
45
+ [ id ]: newValue,
46
+ },
47
+ field
48
+ );
49
+
50
+ if ( message ) {
51
+ setCustomValidity( {
52
+ type: 'invalid',
53
+ message,
54
+ } );
55
+ return;
44
56
  }
45
57
 
46
- return null;
58
+ setCustomValidity( undefined );
47
59
  } }
60
+ customValidity={ customValidity }
48
61
  label={ label }
49
62
  placeholder={ placeholder }
50
63
  value={ value ?? '' }