@wordpress/dataviews 5.0.0 → 6.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 (250) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +55 -27
  3. package/build/components/dataviews/index.js +13 -4
  4. package/build/components/dataviews/index.js.map +1 -1
  5. package/build/components/dataviews-context/index.js +3 -1
  6. package/build/components/dataviews-context/index.js.map +1 -1
  7. package/build/components/dataviews-filters/filter.js +15 -8
  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/input-widget.js +7 -1
  12. package/build/components/dataviews-filters/input-widget.js.map +1 -1
  13. package/build/components/dataviews-filters/reset-filters.js +2 -2
  14. package/build/components/dataviews-filters/reset-filters.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-view-config/index.js +4 -3
  18. package/build/components/dataviews-view-config/index.js.map +1 -1
  19. package/build/dataform-controls/boolean.js +15 -1
  20. package/build/dataform-controls/boolean.js.map +1 -1
  21. package/build/dataform-controls/date.js +385 -0
  22. package/build/dataform-controls/date.js.map +1 -0
  23. package/build/dataform-controls/datetime.js +5 -84
  24. package/build/dataform-controls/datetime.js.map +1 -1
  25. package/build/dataform-controls/email.js +15 -1
  26. package/build/dataform-controls/email.js.map +1 -1
  27. package/build/dataform-controls/index.js +2 -0
  28. package/build/dataform-controls/index.js.map +1 -1
  29. package/build/dataform-controls/integer.js +23 -4
  30. package/build/dataform-controls/integer.js.map +1 -1
  31. package/build/dataform-controls/relative-date-control.js +109 -0
  32. package/build/dataform-controls/relative-date-control.js.map +1 -0
  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 +15 -1
  36. package/build/dataform-controls/text.js.map +1 -1
  37. package/build/dataviews-layouts/grid/index.js +40 -23
  38. package/build/dataviews-layouts/grid/index.js.map +1 -1
  39. package/build/dataviews-layouts/grid/preview-size-picker.js +39 -85
  40. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  41. package/build/dataviews-layouts/list/index.js +7 -3
  42. package/build/dataviews-layouts/list/index.js.map +1 -1
  43. package/build/dataviews-layouts/table/column-primary.js +18 -3
  44. package/build/dataviews-layouts/table/column-primary.js.map +1 -1
  45. package/build/dataviews-layouts/table/index.js +57 -5
  46. package/build/dataviews-layouts/table/index.js.map +1 -1
  47. package/build/field-types/array.js +27 -18
  48. package/build/field-types/array.js.map +1 -1
  49. package/build/field-types/boolean.js +11 -7
  50. package/build/field-types/boolean.js.map +1 -1
  51. package/build/field-types/date.js +21 -12
  52. package/build/field-types/date.js.map +1 -1
  53. package/build/field-types/datetime.js +19 -10
  54. package/build/field-types/datetime.js.map +1 -1
  55. package/build/field-types/email.js +22 -18
  56. package/build/field-types/email.js.map +1 -1
  57. package/build/field-types/index.js +16 -6
  58. package/build/field-types/index.js.map +1 -1
  59. package/build/field-types/integer.js +22 -17
  60. package/build/field-types/integer.js.map +1 -1
  61. package/build/field-types/media.js +19 -10
  62. package/build/field-types/media.js.map +1 -1
  63. package/build/field-types/text.js +19 -10
  64. package/build/field-types/text.js.map +1 -1
  65. package/build/filter-and-sort-data-view.js +6 -4
  66. package/build/filter-and-sort-data-view.js.map +1 -1
  67. package/build/normalize-fields.js +4 -5
  68. package/build/normalize-fields.js.map +1 -1
  69. package/build/types.js.map +1 -1
  70. package/build/validation.js +15 -2
  71. package/build/validation.js.map +1 -1
  72. package/build-module/components/dataviews/index.js +15 -6
  73. package/build-module/components/dataviews/index.js.map +1 -1
  74. package/build-module/components/dataviews-context/index.js +3 -1
  75. package/build-module/components/dataviews-context/index.js.map +1 -1
  76. package/build-module/components/dataviews-filters/filter.js +15 -8
  77. package/build-module/components/dataviews-filters/filter.js.map +1 -1
  78. package/build-module/components/dataviews-filters/index.js +16 -5
  79. package/build-module/components/dataviews-filters/index.js.map +1 -1
  80. package/build-module/components/dataviews-filters/input-widget.js +7 -1
  81. package/build-module/components/dataviews-filters/input-widget.js.map +1 -1
  82. package/build-module/components/dataviews-filters/reset-filters.js +2 -2
  83. package/build-module/components/dataviews-filters/reset-filters.js.map +1 -1
  84. package/build-module/components/dataviews-layout/index.js +5 -2
  85. package/build-module/components/dataviews-layout/index.js.map +1 -1
  86. package/build-module/components/dataviews-view-config/index.js +4 -3
  87. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  88. package/build-module/dataform-controls/boolean.js +17 -2
  89. package/build-module/dataform-controls/boolean.js.map +1 -1
  90. package/build-module/dataform-controls/date.js +376 -0
  91. package/build-module/dataform-controls/date.js.map +1 -0
  92. package/build-module/dataform-controls/datetime.js +3 -84
  93. package/build-module/dataform-controls/datetime.js.map +1 -1
  94. package/build-module/dataform-controls/email.js +17 -2
  95. package/build-module/dataform-controls/email.js.map +1 -1
  96. package/build-module/dataform-controls/index.js +2 -0
  97. package/build-module/dataform-controls/index.js.map +1 -1
  98. package/build-module/dataform-controls/integer.js +24 -5
  99. package/build-module/dataform-controls/integer.js.map +1 -1
  100. package/build-module/dataform-controls/relative-date-control.js +100 -0
  101. package/build-module/dataform-controls/relative-date-control.js.map +1 -0
  102. package/build-module/dataform-controls/select.js +12 -5
  103. package/build-module/dataform-controls/select.js.map +1 -1
  104. package/build-module/dataform-controls/text.js +17 -2
  105. package/build-module/dataform-controls/text.js.map +1 -1
  106. package/build-module/dataviews-layouts/grid/index.js +41 -24
  107. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  108. package/build-module/dataviews-layouts/grid/preview-size-picker.js +40 -85
  109. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  110. package/build-module/dataviews-layouts/list/index.js +7 -3
  111. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  112. package/build-module/dataviews-layouts/table/column-primary.js +18 -3
  113. package/build-module/dataviews-layouts/table/column-primary.js.map +1 -1
  114. package/build-module/dataviews-layouts/table/index.js +58 -6
  115. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  116. package/build-module/field-types/array.js +27 -18
  117. package/build-module/field-types/array.js.map +1 -1
  118. package/build-module/field-types/boolean.js +11 -7
  119. package/build-module/field-types/boolean.js.map +1 -1
  120. package/build-module/field-types/date.js +21 -12
  121. package/build-module/field-types/date.js.map +1 -1
  122. package/build-module/field-types/datetime.js +19 -10
  123. package/build-module/field-types/datetime.js.map +1 -1
  124. package/build-module/field-types/email.js +22 -18
  125. package/build-module/field-types/email.js.map +1 -1
  126. package/build-module/field-types/index.js +16 -6
  127. package/build-module/field-types/index.js.map +1 -1
  128. package/build-module/field-types/integer.js +22 -17
  129. package/build-module/field-types/integer.js.map +1 -1
  130. package/build-module/field-types/media.js +19 -10
  131. package/build-module/field-types/media.js.map +1 -1
  132. package/build-module/field-types/text.js +19 -10
  133. package/build-module/field-types/text.js.map +1 -1
  134. package/build-module/filter-and-sort-data-view.js +6 -4
  135. package/build-module/filter-and-sort-data-view.js.map +1 -1
  136. package/build-module/normalize-fields.js +4 -5
  137. package/build-module/normalize-fields.js.map +1 -1
  138. package/build-module/types.js.map +1 -1
  139. package/build-module/validation.js +15 -2
  140. package/build-module/validation.js.map +1 -1
  141. package/build-style/style-rtl.css +78 -43
  142. package/build-style/style.css +78 -43
  143. package/build-types/components/dataform/stories/index.story.d.ts +21 -0
  144. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  145. package/build-types/components/dataviews/index.d.ts +3 -2
  146. package/build-types/components/dataviews/index.d.ts.map +1 -1
  147. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  148. package/build-types/components/dataviews/stories/index.story.d.ts +16 -3
  149. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  150. package/build-types/components/dataviews-context/index.d.ts +4 -2
  151. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  152. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  153. package/build-types/components/dataviews-filters/index.d.ts.map +1 -1
  154. package/build-types/components/dataviews-filters/input-widget.d.ts.map +1 -1
  155. package/build-types/components/dataviews-filters/reset-filters.d.ts.map +1 -1
  156. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  157. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  158. package/build-types/constants.d.ts +2 -2
  159. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  160. package/build-types/dataform-controls/date.d.ts +3 -0
  161. package/build-types/dataform-controls/date.d.ts.map +1 -0
  162. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  163. package/build-types/dataform-controls/email.d.ts.map +1 -1
  164. package/build-types/dataform-controls/index.d.ts.map +1 -1
  165. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  166. package/build-types/dataform-controls/relative-date-control.d.ts +46 -0
  167. package/build-types/dataform-controls/relative-date-control.d.ts.map +1 -0
  168. package/build-types/dataform-controls/select.d.ts.map +1 -1
  169. package/build-types/dataform-controls/text.d.ts.map +1 -1
  170. package/build-types/dataviews-layouts/grid/index.d.ts +1 -1
  171. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  172. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +0 -1
  173. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  174. package/build-types/dataviews-layouts/index.d.ts +3 -3
  175. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  176. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  177. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  178. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  179. package/build-types/field-types/array.d.ts.map +1 -1
  180. package/build-types/field-types/boolean.d.ts +5 -4
  181. package/build-types/field-types/boolean.d.ts.map +1 -1
  182. package/build-types/field-types/date.d.ts +9 -5
  183. package/build-types/field-types/date.d.ts.map +1 -1
  184. package/build-types/field-types/datetime.d.ts +4 -3
  185. package/build-types/field-types/datetime.d.ts.map +1 -1
  186. package/build-types/field-types/email.d.ts +4 -3
  187. package/build-types/field-types/email.d.ts.map +1 -1
  188. package/build-types/field-types/index.d.ts.map +1 -1
  189. package/build-types/field-types/integer.d.ts +4 -3
  190. package/build-types/field-types/integer.d.ts.map +1 -1
  191. package/build-types/field-types/media.d.ts +4 -3
  192. package/build-types/field-types/media.d.ts.map +1 -1
  193. package/build-types/field-types/text.d.ts +4 -3
  194. package/build-types/field-types/text.d.ts.map +1 -1
  195. package/build-types/filter-and-sort-data-view.d.ts.map +1 -1
  196. package/build-types/normalize-fields.d.ts.map +1 -1
  197. package/build-types/types.d.ts +20 -7
  198. package/build-types/types.d.ts.map +1 -1
  199. package/build-types/validation.d.ts.map +1 -1
  200. package/build-wp/index.js +1561 -670
  201. package/package.json +15 -14
  202. package/src/components/dataform/stories/index.story.tsx +229 -2
  203. package/src/components/dataviews/index.tsx +30 -10
  204. package/src/components/dataviews/stories/fixtures.tsx +3 -1
  205. package/src/components/dataviews/stories/index.story.tsx +49 -29
  206. package/src/components/dataviews/stories/style.css +6 -0
  207. package/src/components/dataviews-context/index.ts +8 -2
  208. package/src/components/dataviews-filters/filter.tsx +17 -7
  209. package/src/components/dataviews-filters/index.tsx +17 -2
  210. package/src/components/dataviews-filters/input-widget.tsx +7 -1
  211. package/src/components/dataviews-filters/reset-filters.tsx +4 -2
  212. package/src/components/dataviews-filters/style.scss +8 -2
  213. package/src/components/dataviews-layout/index.tsx +3 -0
  214. package/src/components/dataviews-view-config/index.tsx +5 -3
  215. package/src/dataform-controls/boolean.tsx +19 -2
  216. package/src/dataform-controls/date.tsx +499 -0
  217. package/src/dataform-controls/datetime.tsx +5 -91
  218. package/src/dataform-controls/email.tsx +19 -2
  219. package/src/dataform-controls/index.tsx +2 -0
  220. package/src/dataform-controls/integer.tsx +30 -4
  221. package/src/dataform-controls/relative-date-control.tsx +106 -0
  222. package/src/dataform-controls/select.tsx +23 -13
  223. package/src/dataform-controls/style.scss +19 -2
  224. package/src/dataform-controls/text.tsx +19 -2
  225. package/src/dataviews-layouts/grid/index.tsx +46 -24
  226. package/src/dataviews-layouts/grid/preview-size-picker.tsx +48 -73
  227. package/src/dataviews-layouts/grid/style.scss +15 -28
  228. package/src/dataviews-layouts/list/index.tsx +7 -4
  229. package/src/dataviews-layouts/list/style.scss +3 -3
  230. package/src/dataviews-layouts/table/column-primary.tsx +29 -5
  231. package/src/dataviews-layouts/table/index.tsx +134 -42
  232. package/src/dataviews-layouts/table/style.scss +45 -1
  233. package/src/field-types/array.tsx +33 -21
  234. package/src/field-types/boolean.tsx +15 -9
  235. package/src/field-types/date.ts +51 -15
  236. package/src/field-types/datetime.tsx +19 -13
  237. package/src/field-types/email.tsx +26 -21
  238. package/src/field-types/index.tsx +18 -8
  239. package/src/field-types/integer.tsx +26 -22
  240. package/src/field-types/media.tsx +19 -13
  241. package/src/field-types/text.tsx +19 -13
  242. package/src/filter-and-sort-data-view.ts +11 -4
  243. package/src/normalize-fields.ts +4 -8
  244. package/src/test/dataviews.tsx +129 -0
  245. package/src/test/filter-and-sort-data-view.js +52 -2
  246. package/src/test/validation.ts +4 -15
  247. package/src/types.ts +28 -8
  248. package/src/validation.ts +30 -1
  249. package/tsconfig.json +1 -0
  250. package/tsconfig.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/dataviews",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
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",
@@ -46,18 +46,19 @@
46
46
  "dependencies": {
47
47
  "@ariakit/react": "^0.4.15",
48
48
  "@babel/runtime": "7.25.7",
49
- "@wordpress/base-styles": "^6.3.0",
50
- "@wordpress/components": "^30.0.0",
51
- "@wordpress/compose": "^7.27.0",
52
- "@wordpress/data": "^10.27.0",
53
- "@wordpress/date": "^5.27.0",
54
- "@wordpress/element": "^6.27.0",
55
- "@wordpress/i18n": "^6.0.0",
56
- "@wordpress/icons": "^10.27.0",
57
- "@wordpress/primitives": "^4.27.0",
58
- "@wordpress/private-apis": "^1.27.0",
59
- "@wordpress/url": "^4.27.0",
60
- "@wordpress/warning": "^3.27.0",
49
+ "@wordpress/base-styles": "^6.4.0",
50
+ "@wordpress/components": "^30.1.0",
51
+ "@wordpress/compose": "^7.28.0",
52
+ "@wordpress/data": "^10.28.0",
53
+ "@wordpress/date": "^5.28.0",
54
+ "@wordpress/element": "^6.28.0",
55
+ "@wordpress/i18n": "^6.1.0",
56
+ "@wordpress/icons": "^10.28.0",
57
+ "@wordpress/keycodes": "^4.28.0",
58
+ "@wordpress/primitives": "^4.28.0",
59
+ "@wordpress/private-apis": "^1.28.0",
60
+ "@wordpress/url": "^4.28.0",
61
+ "@wordpress/warning": "^3.28.0",
61
62
  "clsx": "^2.1.1",
62
63
  "date-fns": "^4.1.0",
63
64
  "fast-deep-equal": "^3.1.3",
@@ -73,5 +74,5 @@
73
74
  "scripts": {
74
75
  "build:wp": "node build"
75
76
  },
76
- "gitHead": "abe06a6f2aef8d03c30ea9d5b3e133f041e523b1"
77
+ "gitHead": "28cc2098f5ee28f89096b638533796538f495f72"
77
78
  }
@@ -1,13 +1,22 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useMemo, useState } from '@wordpress/element';
4
+ import { useCallback, useMemo, useState } from '@wordpress/element';
5
+ import {
6
+ Button,
7
+ __experimentalVStack as VStack,
8
+ privateApis,
9
+ } from '@wordpress/components';
5
10
 
6
11
  /**
7
12
  * Internal dependencies
8
13
  */
9
14
  import DataForm from '../index';
10
- import type { Field, Form } from '../../../types';
15
+ import { isItemValid } from '../../../validation';
16
+ import type { Field, Form, DataFormControlProps } from '../../../types';
17
+ import { unlock } from '../../../lock-unlock';
18
+
19
+ const { ValidatedTextControl } = unlock( privateApis );
11
20
 
12
21
  type SamplePost = {
13
22
  title: string;
@@ -62,6 +71,7 @@ const fields = [
62
71
  label: 'Date as options',
63
72
  type: 'datetime' as const,
64
73
  elements: [
74
+ { value: '', label: 'Select a date' },
65
75
  { value: '1970-02-23T12:00:00', label: "Jane's birth date" },
66
76
  { value: '1950-02-23T12:00:00', label: "John's birth date" },
67
77
  ],
@@ -259,3 +269,220 @@ export const CombinedFields = {
259
269
  type: 'panel',
260
270
  },
261
271
  };
272
+
273
+ function CustomEditControl< Item >( {
274
+ data,
275
+ field,
276
+ onChange,
277
+ hideLabelFromVision,
278
+ }: DataFormControlProps< Item > ) {
279
+ const { id, label, placeholder, description } = field;
280
+ const value = field.getValue( { item: data } );
281
+
282
+ const onChangeControl = useCallback(
283
+ ( newValue: string ) =>
284
+ onChange( {
285
+ [ id ]: newValue,
286
+ } ),
287
+ [ id, onChange ]
288
+ );
289
+
290
+ return (
291
+ <ValidatedTextControl
292
+ required={ !! field.isValid?.required }
293
+ label={ label }
294
+ placeholder={ placeholder }
295
+ value={ value ?? '' }
296
+ help={ description }
297
+ onChange={ onChangeControl }
298
+ __next40pxDefaultSize
299
+ __nextHasNoMarginBottom
300
+ hideLabelFromVision={ hideLabelFromVision }
301
+ />
302
+ );
303
+ }
304
+
305
+ const DataFormValidationComponent = ( { required }: { required: boolean } ) => {
306
+ type ValidatedItem = {
307
+ text: string;
308
+ email: string;
309
+ integer: number;
310
+ boolean: boolean;
311
+ customEdit: string;
312
+ customValidation: string;
313
+ };
314
+
315
+ const [ post, setPost ] = useState< ValidatedItem >( {
316
+ text: 'Hello, World!',
317
+ email: 'hi@example.com',
318
+ integer: 2,
319
+ boolean: true,
320
+ customEdit: 'custom control',
321
+ customValidation: 'potato',
322
+ } );
323
+
324
+ const _fields: Field< ValidatedItem >[] = [
325
+ {
326
+ id: 'text',
327
+ type: 'text' as const,
328
+ label: 'Text',
329
+ isValid: {
330
+ required,
331
+ },
332
+ },
333
+ {
334
+ id: 'email',
335
+ type: 'email' as const,
336
+ label: 'e-mail',
337
+ isValid: {
338
+ required,
339
+ },
340
+ },
341
+ {
342
+ id: 'integer',
343
+ type: 'integer' as const,
344
+ label: 'Integer',
345
+ isValid: {
346
+ required,
347
+ },
348
+ },
349
+ {
350
+ id: 'boolean',
351
+ type: 'boolean' as const,
352
+ label: 'Boolean',
353
+ isValid: {
354
+ required,
355
+ },
356
+ },
357
+ {
358
+ id: 'customEdit',
359
+ label: 'Custom Control',
360
+ Edit: CustomEditControl,
361
+ isValid: {
362
+ required,
363
+ },
364
+ },
365
+ {
366
+ id: 'customValidation',
367
+ type: 'text',
368
+ label: 'Custom validation',
369
+ isValid: {
370
+ required,
371
+ custom: ( value: ValidatedItem ) => {
372
+ if (
373
+ ! [ 'tomato', 'potato' ].includes(
374
+ value.customValidation
375
+ )
376
+ ) {
377
+ return 'Value must be one of "tomato", "potato"';
378
+ }
379
+
380
+ return null;
381
+ },
382
+ },
383
+ },
384
+ ];
385
+
386
+ const form = {
387
+ fields: [
388
+ 'text',
389
+ 'email',
390
+ 'integer',
391
+ 'boolean',
392
+ 'customEdit',
393
+ 'customValidation',
394
+ ],
395
+ };
396
+
397
+ const canSave = isItemValid( post, _fields, form );
398
+
399
+ return (
400
+ <form>
401
+ <VStack alignment="left">
402
+ <DataForm< ValidatedItem >
403
+ data={ post }
404
+ fields={ _fields }
405
+ form={ form }
406
+ onChange={ ( edits ) =>
407
+ setPost( ( prev ) => ( {
408
+ ...prev,
409
+ ...edits,
410
+ } ) )
411
+ }
412
+ />
413
+ <Button
414
+ __next40pxDefaultSize
415
+ accessibleWhenDisabled
416
+ disabled={ ! canSave }
417
+ variant="primary"
418
+ >
419
+ Submit
420
+ </Button>
421
+ </VStack>
422
+ </form>
423
+ );
424
+ };
425
+
426
+ export const Validation = {
427
+ title: 'DataForm/Validation',
428
+ render: DataFormValidationComponent,
429
+ argTypes: {
430
+ required: {
431
+ control: { type: 'boolean' },
432
+ description: 'Whether or not the fields are required.',
433
+ },
434
+ },
435
+ args: {
436
+ required: true,
437
+ },
438
+ };
439
+
440
+ const DataFormVisibilityComponent = () => {
441
+ type Post = {
442
+ name: string;
443
+ email: string;
444
+ isActive: boolean;
445
+ };
446
+ const [ data, setData ] = useState( {
447
+ name: '',
448
+ email: '',
449
+ isActive: true,
450
+ } );
451
+
452
+ const _fields = [
453
+ { id: 'isActive', label: 'Is module active?', type: 'boolean' },
454
+ {
455
+ id: 'name',
456
+ label: 'Name',
457
+ type: 'text',
458
+ isVisible: ( post ) => post.isActive === true,
459
+ },
460
+ {
461
+ id: 'email',
462
+ label: 'Email',
463
+ type: 'email',
464
+ isVisible: ( post ) => post.isActive === true,
465
+ },
466
+ ] satisfies Field< Post >[];
467
+ const form = {
468
+ fields: [ 'isActive', 'name', 'email' ],
469
+ };
470
+ return (
471
+ <DataForm< Post >
472
+ data={ data }
473
+ fields={ _fields }
474
+ form={ form }
475
+ onChange={ ( edits ) =>
476
+ setData( ( prev ) => ( {
477
+ ...prev,
478
+ ...edits,
479
+ } ) )
480
+ }
481
+ />
482
+ );
483
+ };
484
+
485
+ export const Visibility = {
486
+ title: 'DataForm/Visibility',
487
+ render: DataFormVisibilityComponent,
488
+ };
@@ -7,8 +7,14 @@ import type { ReactNode, ComponentProps, ReactElement } from 'react';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { __experimentalHStack as HStack } from '@wordpress/components';
10
- import { useContext, useMemo, useRef, useState } from '@wordpress/element';
11
- import { useMergeRefs, useResizeObserver } from '@wordpress/compose';
10
+ import {
11
+ useContext,
12
+ useMemo,
13
+ useRef,
14
+ useState,
15
+ useEffect,
16
+ } from '@wordpress/element';
17
+ import { useResizeObserver } from '@wordpress/compose';
12
18
 
13
19
  /**
14
20
  * Internal dependencies
@@ -59,7 +65,8 @@ type DataViewsProps< Item > = {
59
65
  header?: ReactNode;
60
66
  getItemLevel?: ( item: Item ) => number;
61
67
  children?: ReactNode;
62
- perPageSizes?: [ number, number, number, number ];
68
+ perPageSizes?: number[];
69
+ empty?: ReactNode;
63
70
  } & ( Item extends ItemWithId
64
71
  ? { getItemId?: ( item: Item ) => string }
65
72
  : { getItemId: ( item: Item ) => string } );
@@ -133,7 +140,8 @@ function DataViews< Item >( {
133
140
  isItemClickable = defaultIsItemClickable,
134
141
  header,
135
142
  children,
136
- perPageSizes,
143
+ perPageSizes = [ 10, 20, 50, 100 ],
144
+ empty,
137
145
  }: DataViewsProps< Item > ) {
138
146
  const containerRef = useRef< HTMLDivElement | null >( null );
139
147
  const [ containerWidth, setContainerWidth ] = useState( 0 );
@@ -168,10 +176,23 @@ function DataViews< Item >( {
168
176
  }, [ selection, data, getItemId ] );
169
177
 
170
178
  const filters = useFilters( _fields, view );
171
- const [ isShowingFilter, setIsShowingFilter ] = useState< boolean >( () =>
172
- ( filters || [] ).some( ( filter ) => filter.isPrimary )
179
+ const hasPrimaryOrLockedFilters = useMemo(
180
+ () =>
181
+ ( filters || [] ).some(
182
+ ( filter ) => filter.isPrimary || filter.isLocked
183
+ ),
184
+ [ filters ]
185
+ );
186
+ const [ isShowingFilter, setIsShowingFilter ] = useState< boolean >(
187
+ hasPrimaryOrLockedFilters
173
188
  );
174
189
 
190
+ useEffect( () => {
191
+ if ( hasPrimaryOrLockedFilters && ! isShowingFilter ) {
192
+ setIsShowingFilter( true );
193
+ }
194
+ }, [ hasPrimaryOrLockedFilters, isShowingFilter ] );
195
+
175
196
  return (
176
197
  <DataViewsContext.Provider
177
198
  value={ {
@@ -193,17 +214,16 @@ function DataViews< Item >( {
193
214
  renderItemLink,
194
215
  containerWidth,
195
216
  containerRef,
217
+ resizeObserverRef,
196
218
  defaultLayouts,
197
219
  filters,
198
220
  isShowingFilter,
199
221
  setIsShowingFilter,
200
222
  perPageSizes,
223
+ empty,
201
224
  } }
202
225
  >
203
- <div
204
- className="dataviews-wrapper"
205
- ref={ useMergeRefs( [ containerRef, resizeObserverRef ] ) }
206
- >
226
+ <div className="dataviews-wrapper" ref={ containerRef }>
207
227
  { children ?? (
208
228
  <DefaultUI
209
229
  header={ header }
@@ -41,7 +41,8 @@ export const data: SpaceObject[] = [
41
41
  {
42
42
  id: 1,
43
43
  title: 'Moon',
44
- description: "Earth's satellite",
44
+ description:
45
+ 'The Moon is Earth’s only natural satellite, orbiting at an average distance of 384,400 kilometers with a synchronous rotation that leads to fixed lunar phases as seen from Earth. Its cratered surface and subtle glow define night skies, inspiring exploration missions and influencing tides and biological rhythms worldwide.',
45
46
  image: 'https://live.staticflickr.com/7398/9458193857_e1256123e3_z.jpg',
46
47
  type: 'Satellite',
47
48
  isPlanet: false,
@@ -759,5 +760,6 @@ export const fields: Field< SpaceObject >[] = [
759
760
  { value: 'Gas giant', label: 'Gas giant' },
760
761
  ],
761
762
  type: 'array',
763
+ enableGlobalSearch: true,
762
764
  },
763
765
  ];
@@ -20,6 +20,7 @@ import {
20
20
  __experimentalText as Text,
21
21
  __experimentalHStack as HStack,
22
22
  __experimentalVStack as VStack,
23
+ Button,
23
24
  } from '@wordpress/components';
24
25
  import { __, _n } from '@wordpress/i18n';
25
26
 
@@ -53,7 +54,7 @@ const defaultLayouts = {
53
54
  [ LAYOUT_LIST ]: {},
54
55
  };
55
56
 
56
- export const Default = () => {
57
+ export const Default = ( { perPageSizes = [ 10, 25, 50, 100 ] } ) => {
57
58
  const [ view, setView ] = useState< View >( {
58
59
  ...DEFAULT_VIEW,
59
60
  fields: [ 'categories' ],
@@ -86,10 +87,22 @@ export const Default = () => {
86
87
  ) }
87
88
  isItemClickable={ () => true }
88
89
  defaultLayouts={ defaultLayouts }
90
+ perPageSizes={ perPageSizes }
89
91
  />
90
92
  );
91
93
  };
92
94
 
95
+ Default.args = {
96
+ perPageSizes: [ 10, 25, 50, 100 ],
97
+ };
98
+
99
+ Default.argTypes = {
100
+ perPageSizes: {
101
+ control: 'object',
102
+ description: 'Array of available page sizes',
103
+ },
104
+ };
105
+
93
106
  export const Empty = () => {
94
107
  const [ view, setView ] = useState< View >( {
95
108
  ...DEFAULT_VIEW,
@@ -110,6 +123,27 @@ export const Empty = () => {
110
123
  );
111
124
  };
112
125
 
126
+ export const CustomEmpty = () => {
127
+ const [ view, setView ] = useState< View >( {
128
+ ...DEFAULT_VIEW,
129
+ fields: [ 'title', 'description', 'categories' ],
130
+ } );
131
+
132
+ return (
133
+ <DataViews
134
+ getItemId={ ( item ) => item.id.toString() }
135
+ paginationInfo={ { totalItems: 0, totalPages: 0 } }
136
+ data={ [] }
137
+ view={ view }
138
+ fields={ fields }
139
+ onChangeView={ setView }
140
+ actions={ actions }
141
+ defaultLayouts={ defaultLayouts }
142
+ empty={ view.search ? 'No sites found' : 'No sites' }
143
+ />
144
+ );
145
+ };
146
+
113
147
  export const FieldsNoSortableNoHidable = () => {
114
148
  const [ view, setView ] = useState< View >( {
115
149
  ...DEFAULT_VIEW,
@@ -261,6 +295,19 @@ export const FreeComposition = () => {
261
295
  table: {},
262
296
  grid: {},
263
297
  } }
298
+ empty={
299
+ <VStack
300
+ justify="space-around"
301
+ alignment="center"
302
+ className="free-composition-dataviews-empty"
303
+ >
304
+ <Text size={ 18 } as="p">
305
+ No planets
306
+ </Text>
307
+ <Text variant="muted">{ `Try a different search because “${ view.search }” returned no results.` }</Text>
308
+ <Button variant="secondary">Create new planet</Button>
309
+ </VStack>
310
+ }
264
311
  >
265
312
  <PlanetOverview planets={ planets } />
266
313
  </DataViews>
@@ -300,34 +347,7 @@ export const WithCard = () => {
300
347
  );
301
348
  };
302
349
 
303
- export const CustomPerPageSizes = () => {
304
- const [ view, setView ] = useState< View >( {
305
- ...DEFAULT_VIEW,
306
- fields: [ 'categories' ],
307
- titleField: 'title',
308
- descriptionField: 'description',
309
- mediaField: 'image',
310
- perPage: 3,
311
- } );
312
- const { data: shownData, paginationInfo } = useMemo( () => {
313
- return filterSortAndPaginate( data, view, fields );
314
- }, [ view ] );
315
- return (
316
- <DataViews
317
- getItemId={ ( item ) => item.id.toString() }
318
- paginationInfo={ paginationInfo }
319
- data={ shownData }
320
- view={ view }
321
- fields={ fields }
322
- onChangeView={ setView }
323
- actions={ actions.filter( ( action ) => ! action.supportsBulk ) }
324
- defaultLayouts={ defaultLayouts }
325
- perPageSizes={ [ 3, 6, 12, 24 ] }
326
- />
327
- );
328
- };
329
-
330
- export const GroupedGridLayout = () => {
350
+ export const GroupByLayout = () => {
331
351
  const [ view, setView ] = useState< View >( {
332
352
  type: LAYOUT_GRID,
333
353
  search: '',
@@ -23,3 +23,9 @@
23
23
  .free-composition-dataviews-layout thead {
24
24
  inset-block-start: 67px;
25
25
  }
26
+
27
+ .free-composition-dataviews-empty {
28
+ border: 1px solid #000;
29
+ border-radius: 8px;
30
+ padding: 24px;
31
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import type { ComponentProps, ReactElement } from 'react';
4
+ import type { ComponentProps, ReactElement, ReactNode } from 'react';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -47,11 +47,15 @@ type DataViewsContextType< Item > = {
47
47
  isItemClickable: ( item: Item ) => boolean;
48
48
  containerWidth: number;
49
49
  containerRef: React.MutableRefObject< HTMLDivElement | null >;
50
+ resizeObserverRef:
51
+ | ( ( element?: HTMLDivElement | null ) => void )
52
+ | React.RefObject< HTMLDivElement >;
50
53
  defaultLayouts: SupportedLayouts;
51
54
  filters: NormalizedFilter[];
52
55
  isShowingFilter: boolean;
53
56
  setIsShowingFilter: ( value: boolean ) => void;
54
- perPageSizes?: [ number, number, number, number ];
57
+ perPageSizes: number[];
58
+ empty?: ReactNode;
55
59
  };
56
60
 
57
61
  const DataViewsContext = createContext< DataViewsContextType< any > >( {
@@ -72,10 +76,12 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
72
76
  renderItemLink: undefined,
73
77
  containerWidth: 0,
74
78
  containerRef: createRef(),
79
+ resizeObserverRef: () => {},
75
80
  defaultLayouts: { list: {}, grid: {}, table: {} },
76
81
  filters: [],
77
82
  isShowingFilter: false,
78
83
  setIsShowingFilter: () => {},
84
+ perPageSizes: [],
79
85
  } );
80
86
 
81
87
  export default DataViewsContext;
@@ -309,9 +309,9 @@ const FilterText = ( {
309
309
 
310
310
  return createInterpolateElement(
311
311
  sprintf(
312
- /* translators: 1: Filter name. 2: Min value. 3: Max value. e.g.: "Item count between (inc): 10-180". */
312
+ /* translators: 1: Filter name. 2: Min value. 3: Max value. e.g.: "Item count between (inc): 10 and 180". */
313
313
  __(
314
- '<Name>%1$s between (inc): </Name><Value>%2$s-%3$s</Value>'
314
+ '<Name>%1$s between (inc): </Name><Value>%2$s and %3$s</Value>'
315
315
  ),
316
316
  filter.name,
317
317
  label[ 0 ],
@@ -497,8 +497,9 @@ export default function Filter( {
497
497
  }
498
498
 
499
499
  const isPrimary = filter.isPrimary;
500
- const hasValues = filterInView?.value !== undefined;
501
- const canResetOrRemove = ! isPrimary || hasValues;
500
+ const isLocked = filterInView?.isLocked;
501
+ const hasValues = ! isLocked && filterInView?.value !== undefined;
502
+ const canResetOrRemove = ! isLocked && ( ! isPrimary || hasValues );
502
503
  return (
503
504
  <Dropdown
504
505
  defaultOpen={ openedFilter === filter.field }
@@ -523,17 +524,26 @@ export default function Filter( {
523
524
  {
524
525
  'has-reset': canResetOrRemove,
525
526
  'has-values': hasValues,
527
+ 'is-not-clickable': isLocked,
526
528
  }
527
529
  ) }
528
530
  role="button"
529
- tabIndex={ 0 }
530
- onClick={ onToggle }
531
+ tabIndex={ isLocked ? -1 : 0 }
532
+ onClick={ () => {
533
+ if ( ! isLocked ) {
534
+ onToggle();
535
+ }
536
+ } }
531
537
  onKeyDown={ ( event ) => {
532
- if ( [ ENTER, SPACE ].includes( event.key ) ) {
538
+ if (
539
+ ! isLocked &&
540
+ [ ENTER, SPACE ].includes( event.key )
541
+ ) {
533
542
  onToggle();
534
543
  event.preventDefault();
535
544
  }
536
545
  } }
546
+ aria-disabled={ isLocked }
537
547
  aria-pressed={ isOpen }
538
548
  aria-expanded={ isOpen }
539
549
  ref={ toggleRef }
@@ -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
  }
@@ -62,7 +62,13 @@ export default function InputWidget( {
62
62
  ..._filter,
63
63
  operator:
64
64
  currentFilter.operator || filter.operators[ 0 ],
65
- value: nextValue,
65
+ // Consider empty strings as undefined:
66
+ //
67
+ // - undefined as value means the filter is unset: the filter widget displays no value and the search returns all records
68
+ // - empty string as value means "search empty string": returns only the records that have an empty string as value
69
+ //
70
+ // In practice, this means the filter will not be able to find an empty string as the value.
71
+ value: nextValue === '' ? undefined : nextValue,
66
72
  }
67
73
  : _filter
68
74
  ),