@wordpress/dataviews 9.0.1-next.6870dfe5b.0 → 9.0.1-next.a730c9c8c.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 (255) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +107 -12
  3. package/build/components/dataviews-filters/input-widget.js +48 -4
  4. package/build/components/dataviews-filters/input-widget.js.map +1 -1
  5. package/build/components/dataviews-layout/index.js +3 -1
  6. package/build/components/dataviews-layout/index.js.map +1 -1
  7. package/build/dataform-controls/array.js +9 -7
  8. package/build/dataform-controls/array.js.map +1 -1
  9. package/build/dataform-controls/checkbox.js +31 -20
  10. package/build/dataform-controls/checkbox.js.map +1 -1
  11. package/build/dataform-controls/color.js +29 -24
  12. package/build/dataform-controls/color.js.map +1 -1
  13. package/build/dataform-controls/date.js +32 -24
  14. package/build/dataform-controls/date.js.map +1 -1
  15. package/build/dataform-controls/datetime.js +133 -19
  16. package/build/dataform-controls/datetime.js.map +1 -1
  17. package/build/dataform-controls/email.js +9 -3
  18. package/build/dataform-controls/email.js.map +1 -1
  19. package/build/dataform-controls/index.js +27 -0
  20. package/build/dataform-controls/index.js.map +1 -1
  21. package/build/dataform-controls/integer.js +47 -34
  22. package/build/dataform-controls/integer.js.map +1 -1
  23. package/build/dataform-controls/password.js +47 -0
  24. package/build/dataform-controls/password.js.map +1 -0
  25. package/build/dataform-controls/radio.js +42 -9
  26. package/build/dataform-controls/radio.js.map +1 -1
  27. package/build/dataform-controls/relative-date-control.js +6 -10
  28. package/build/dataform-controls/relative-date-control.js.map +1 -1
  29. package/build/dataform-controls/select.js +41 -10
  30. package/build/dataform-controls/select.js.map +1 -1
  31. package/build/dataform-controls/telephone.js +9 -3
  32. package/build/dataform-controls/telephone.js.map +1 -1
  33. package/build/dataform-controls/text.js +16 -4
  34. package/build/dataform-controls/text.js.map +1 -1
  35. package/build/dataform-controls/textarea.js +81 -0
  36. package/build/dataform-controls/textarea.js.map +1 -0
  37. package/build/dataform-controls/toggle-group.js +36 -6
  38. package/build/dataform-controls/toggle-group.js.map +1 -1
  39. package/build/dataform-controls/toggle.js +33 -22
  40. package/build/dataform-controls/toggle.js.map +1 -1
  41. package/build/dataform-controls/url.js +9 -3
  42. package/build/dataform-controls/url.js.map +1 -1
  43. package/build/dataform-controls/utils/{validated-text.js → validated-input.js} +36 -29
  44. package/build/dataform-controls/utils/validated-input.js.map +1 -0
  45. package/build/dataforms-layouts/panel/dropdown.js +10 -14
  46. package/build/dataforms-layouts/panel/dropdown.js.map +1 -1
  47. package/build/dataforms-layouts/panel/index.js +24 -11
  48. package/build/dataforms-layouts/panel/index.js.map +1 -1
  49. package/build/dataforms-layouts/panel/modal.js +22 -27
  50. package/build/dataforms-layouts/panel/modal.js.map +1 -1
  51. package/build/dataforms-layouts/panel/summary-button.js +67 -0
  52. package/build/dataforms-layouts/panel/summary-button.js.map +1 -0
  53. package/build/dataforms-layouts/regular/index.js +7 -9
  54. package/build/dataforms-layouts/regular/index.js.map +1 -1
  55. package/build/dataviews-layouts/grid/index.js +5 -15
  56. package/build/dataviews-layouts/grid/index.js.map +1 -1
  57. package/build/dataviews-layouts/list/index.js +47 -2
  58. package/build/dataviews-layouts/list/index.js.map +1 -1
  59. package/build/dataviews-layouts/picker-grid/index.js +5 -15
  60. package/build/dataviews-layouts/picker-grid/index.js.map +1 -1
  61. package/build/dataviews-layouts/table/index.js +5 -17
  62. package/build/dataviews-layouts/table/index.js.map +1 -1
  63. package/build/dataviews-layouts/utils/get-data-by-group.js +23 -0
  64. package/build/dataviews-layouts/utils/get-data-by-group.js.map +1 -0
  65. package/build/field-types/index.js +4 -0
  66. package/build/field-types/index.js.map +1 -1
  67. package/build/field-types/password.js +51 -0
  68. package/build/field-types/password.js.map +1 -0
  69. package/build/normalize-fields.js +17 -0
  70. package/build/normalize-fields.js.map +1 -1
  71. package/build/types.js.map +1 -1
  72. package/build/validation.js +1 -1
  73. package/build/validation.js.map +1 -1
  74. package/build-module/components/dataviews-filters/input-widget.js +48 -4
  75. package/build-module/components/dataviews-filters/input-widget.js.map +1 -1
  76. package/build-module/components/dataviews-layout/index.js +3 -1
  77. package/build-module/components/dataviews-layout/index.js.map +1 -1
  78. package/build-module/dataform-controls/array.js +9 -7
  79. package/build-module/dataform-controls/array.js.map +1 -1
  80. package/build-module/dataform-controls/checkbox.js +31 -21
  81. package/build-module/dataform-controls/checkbox.js.map +1 -1
  82. package/build-module/dataform-controls/color.js +28 -24
  83. package/build-module/dataform-controls/color.js.map +1 -1
  84. package/build-module/dataform-controls/date.js +32 -24
  85. package/build-module/dataform-controls/date.js.map +1 -1
  86. package/build-module/dataform-controls/datetime.js +135 -21
  87. package/build-module/dataform-controls/datetime.js.map +1 -1
  88. package/build-module/dataform-controls/email.js +8 -2
  89. package/build-module/dataform-controls/email.js.map +1 -1
  90. package/build-module/dataform-controls/index.js +27 -0
  91. package/build-module/dataform-controls/index.js.map +1 -1
  92. package/build-module/dataform-controls/integer.js +46 -34
  93. package/build-module/dataform-controls/integer.js.map +1 -1
  94. package/build-module/dataform-controls/password.js +38 -0
  95. package/build-module/dataform-controls/password.js.map +1 -0
  96. package/build-module/dataform-controls/radio.js +44 -11
  97. package/build-module/dataform-controls/radio.js.map +1 -1
  98. package/build-module/dataform-controls/relative-date-control.js +6 -10
  99. package/build-module/dataform-controls/relative-date-control.js.map +1 -1
  100. package/build-module/dataform-controls/select.js +43 -12
  101. package/build-module/dataform-controls/select.js.map +1 -1
  102. package/build-module/dataform-controls/telephone.js +8 -2
  103. package/build-module/dataform-controls/telephone.js.map +1 -1
  104. package/build-module/dataform-controls/text.js +15 -3
  105. package/build-module/dataform-controls/text.js.map +1 -1
  106. package/build-module/dataform-controls/textarea.js +74 -0
  107. package/build-module/dataform-controls/textarea.js.map +1 -0
  108. package/build-module/dataform-controls/toggle-group.js +38 -8
  109. package/build-module/dataform-controls/toggle-group.js.map +1 -1
  110. package/build-module/dataform-controls/toggle.js +33 -23
  111. package/build-module/dataform-controls/toggle.js.map +1 -1
  112. package/build-module/dataform-controls/url.js +8 -2
  113. package/build-module/dataform-controls/url.js.map +1 -1
  114. package/build-module/dataform-controls/utils/validated-input.js +76 -0
  115. package/build-module/dataform-controls/utils/validated-input.js.map +1 -0
  116. package/build-module/dataforms-layouts/panel/dropdown.js +10 -15
  117. package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -1
  118. package/build-module/dataforms-layouts/panel/index.js +24 -11
  119. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  120. package/build-module/dataforms-layouts/panel/modal.js +22 -28
  121. package/build-module/dataforms-layouts/panel/modal.js.map +1 -1
  122. package/build-module/dataforms-layouts/panel/summary-button.js +60 -0
  123. package/build-module/dataforms-layouts/panel/summary-button.js.map +1 -0
  124. package/build-module/dataforms-layouts/regular/index.js +8 -10
  125. package/build-module/dataforms-layouts/regular/index.js.map +1 -1
  126. package/build-module/dataviews-layouts/grid/index.js +6 -16
  127. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  128. package/build-module/dataviews-layouts/list/index.js +48 -3
  129. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  130. package/build-module/dataviews-layouts/picker-grid/index.js +6 -16
  131. package/build-module/dataviews-layouts/picker-grid/index.js.map +1 -1
  132. package/build-module/dataviews-layouts/table/index.js +5 -17
  133. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  134. package/build-module/dataviews-layouts/utils/get-data-by-group.js +17 -0
  135. package/build-module/dataviews-layouts/utils/get-data-by-group.js.map +1 -0
  136. package/build-module/field-types/index.js +4 -0
  137. package/build-module/field-types/index.js.map +1 -1
  138. package/build-module/field-types/password.js +46 -0
  139. package/build-module/field-types/password.js.map +1 -0
  140. package/build-module/normalize-fields.js +15 -0
  141. package/build-module/normalize-fields.js.map +1 -1
  142. package/build-module/types.js.map +1 -1
  143. package/build-module/validation.js +1 -1
  144. package/build-module/validation.js.map +1 -1
  145. package/build-style/style-rtl.css +9 -6
  146. package/build-style/style.css +9 -6
  147. package/build-types/components/dataform/stories/index.story.d.ts +3 -14
  148. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  149. package/build-types/components/dataviews/stories/fixtures.d.ts +4 -2
  150. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  151. package/build-types/components/dataviews-filters/input-widget.d.ts.map +1 -1
  152. package/build-types/dataform-controls/array.d.ts.map +1 -1
  153. package/build-types/dataform-controls/checkbox.d.ts.map +1 -1
  154. package/build-types/dataform-controls/color.d.ts.map +1 -1
  155. package/build-types/dataform-controls/date.d.ts.map +1 -1
  156. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  157. package/build-types/dataform-controls/email.d.ts.map +1 -1
  158. package/build-types/dataform-controls/index.d.ts +1 -1
  159. package/build-types/dataform-controls/index.d.ts.map +1 -1
  160. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  161. package/build-types/dataform-controls/password.d.ts +3 -0
  162. package/build-types/dataform-controls/password.d.ts.map +1 -0
  163. package/build-types/dataform-controls/radio.d.ts.map +1 -1
  164. package/build-types/dataform-controls/relative-date-control.d.ts +6 -5
  165. package/build-types/dataform-controls/relative-date-control.d.ts.map +1 -1
  166. package/build-types/dataform-controls/select.d.ts.map +1 -1
  167. package/build-types/dataform-controls/telephone.d.ts.map +1 -1
  168. package/build-types/dataform-controls/text.d.ts +1 -1
  169. package/build-types/dataform-controls/text.d.ts.map +1 -1
  170. package/build-types/dataform-controls/textarea.d.ts +6 -0
  171. package/build-types/dataform-controls/textarea.d.ts.map +1 -0
  172. package/build-types/dataform-controls/toggle-group.d.ts.map +1 -1
  173. package/build-types/dataform-controls/toggle.d.ts.map +1 -1
  174. package/build-types/dataform-controls/url.d.ts.map +1 -1
  175. package/build-types/dataform-controls/utils/validated-input.d.ts +20 -0
  176. package/build-types/dataform-controls/utils/validated-input.d.ts.map +1 -0
  177. package/build-types/dataforms-layouts/panel/dropdown.d.ts +2 -1
  178. package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -1
  179. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  180. package/build-types/dataforms-layouts/panel/modal.d.ts +2 -1
  181. package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -1
  182. package/build-types/dataforms-layouts/panel/summary-button.d.ts +15 -0
  183. package/build-types/dataforms-layouts/panel/summary-button.d.ts.map +1 -0
  184. package/build-types/dataforms-layouts/regular/index.d.ts.map +1 -1
  185. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  186. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  187. package/build-types/dataviews-layouts/picker-grid/index.d.ts.map +1 -1
  188. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  189. package/build-types/dataviews-layouts/utils/get-data-by-group.d.ts +6 -0
  190. package/build-types/dataviews-layouts/utils/get-data-by-group.d.ts.map +1 -0
  191. package/build-types/field-types/index.d.ts.map +1 -1
  192. package/build-types/field-types/password.d.ts +17 -0
  193. package/build-types/field-types/password.d.ts.map +1 -0
  194. package/build-types/field-types/stories/index.story.d.ts +5 -1
  195. package/build-types/field-types/stories/index.story.d.ts.map +1 -1
  196. package/build-types/normalize-fields.d.ts +3 -0
  197. package/build-types/normalize-fields.d.ts.map +1 -1
  198. package/build-types/types.d.ts +67 -4
  199. package/build-types/types.d.ts.map +1 -1
  200. package/build-types/validation.d.ts.map +1 -1
  201. package/build-wp/index.js +1670 -1350
  202. package/package.json +16 -15
  203. package/src/components/dataform/stories/index.story.tsx +489 -17
  204. package/src/components/dataviews/stories/fixtures.tsx +99 -41
  205. package/src/components/dataviews/stories/index.story.tsx +2 -2
  206. package/src/components/dataviews-filters/input-widget.tsx +44 -5
  207. package/src/components/dataviews-layout/index.tsx +1 -1
  208. package/src/components/dataviews-picker/stories/index.story.tsx +1 -1
  209. package/src/dataform-controls/array.tsx +4 -6
  210. package/src/dataform-controls/checkbox.tsx +41 -24
  211. package/src/dataform-controls/color.tsx +33 -24
  212. package/src/dataform-controls/date.tsx +47 -21
  213. package/src/dataform-controls/datetime.tsx +171 -23
  214. package/src/dataform-controls/email.tsx +10 -2
  215. package/src/dataform-controls/index.tsx +30 -0
  216. package/src/dataform-controls/integer.tsx +82 -49
  217. package/src/dataform-controls/password.tsx +50 -0
  218. package/src/dataform-controls/radio.tsx +53 -11
  219. package/src/dataform-controls/relative-date-control.tsx +11 -10
  220. package/src/dataform-controls/select.tsx +53 -10
  221. package/src/dataform-controls/telephone.tsx +10 -2
  222. package/src/dataform-controls/text.tsx +19 -2
  223. package/src/dataform-controls/textarea.tsx +85 -0
  224. package/src/dataform-controls/toggle-group.tsx +50 -10
  225. package/src/dataform-controls/toggle.tsx +41 -24
  226. package/src/dataform-controls/url.tsx +10 -2
  227. package/src/dataform-controls/utils/validated-input.tsx +109 -0
  228. package/src/dataforms-layouts/panel/dropdown.tsx +12 -23
  229. package/src/dataforms-layouts/panel/index.tsx +39 -16
  230. package/src/dataforms-layouts/panel/modal.tsx +24 -30
  231. package/src/dataforms-layouts/panel/summary-button.tsx +92 -0
  232. package/src/dataforms-layouts/regular/index.tsx +9 -7
  233. package/src/dataforms-layouts/regular/style.scss +0 -6
  234. package/src/dataviews-layouts/grid/index.tsx +9 -14
  235. package/src/dataviews-layouts/grid/style.scss +1 -0
  236. package/src/dataviews-layouts/list/index.tsx +74 -2
  237. package/src/dataviews-layouts/list/style.scss +8 -0
  238. package/src/dataviews-layouts/picker-grid/index.tsx +9 -13
  239. package/src/dataviews-layouts/table/index.tsx +10 -14
  240. package/src/dataviews-layouts/utils/get-data-by-group.ts +18 -0
  241. package/src/field-types/index.tsx +5 -0
  242. package/src/field-types/password.tsx +46 -0
  243. package/src/field-types/stories/index.story.tsx +138 -1
  244. package/src/normalize-fields.ts +18 -0
  245. package/src/test/filter-and-sort-data-view.js +148 -138
  246. package/src/test/normalize-fields.ts +114 -0
  247. package/src/types.ts +74 -3
  248. package/src/validation.ts +2 -0
  249. package/tsconfig.tsbuildinfo +1 -1
  250. package/build/dataform-controls/utils/validated-text.js.map +0 -1
  251. package/build-module/dataform-controls/utils/validated-text.js +0 -70
  252. package/build-module/dataform-controls/utils/validated-text.js.map +0 -1
  253. package/build-types/dataform-controls/utils/validated-text.d.ts +0 -16
  254. package/build-types/dataform-controls/utils/validated-text.d.ts.map +0 -1
  255. package/src/dataform-controls/utils/validated-text.tsx +0 -96
@@ -0,0 +1,109 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import deepMerge from 'deepmerge';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { privateApis } from '@wordpress/components';
10
+ import { useCallback, useState } from '@wordpress/element';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import type { DataFormControlProps } from '../../types';
16
+ import { unlock } from '../../lock-unlock';
17
+
18
+ const { ValidatedInputControl } = unlock( privateApis );
19
+
20
+ export type DataFormValidatedTextControlProps< Item > =
21
+ DataFormControlProps< Item > & {
22
+ /**
23
+ * The input type of the control.
24
+ */
25
+ type?: 'text' | 'email' | 'tel' | 'url' | 'password';
26
+ /**
27
+ * Optional prefix element to display before the input.
28
+ */
29
+ prefix?: React.ReactElement;
30
+ /**
31
+ * Optional suffix element to display after the input.
32
+ */
33
+ suffix?: React.ReactElement;
34
+ };
35
+
36
+ export default function ValidatedText< Item >( {
37
+ data,
38
+ field,
39
+ onChange,
40
+ hideLabelFromVision,
41
+ type,
42
+ prefix,
43
+ suffix,
44
+ }: DataFormValidatedTextControlProps< Item > ) {
45
+ const { label, placeholder, description, getValue, setValue, isValid } =
46
+ field;
47
+ const value = getValue( { item: data } );
48
+ const [ customValidity, setCustomValidity ] =
49
+ useState<
50
+ React.ComponentProps<
51
+ typeof ValidatedInputControl
52
+ >[ 'customValidity' ]
53
+ >( undefined );
54
+
55
+ const onChangeControl = useCallback(
56
+ ( newValue: string ) =>
57
+ onChange(
58
+ setValue( {
59
+ item: data,
60
+ value: newValue,
61
+ } )
62
+ ),
63
+ [ data, setValue, onChange ]
64
+ );
65
+
66
+ const onValidateControl = useCallback(
67
+ ( newValue: any ) => {
68
+ const message = isValid?.custom?.(
69
+ deepMerge(
70
+ data,
71
+ setValue( {
72
+ item: data,
73
+ value: newValue,
74
+ } ) as Partial< Item >
75
+ ),
76
+ field
77
+ );
78
+
79
+ if ( message ) {
80
+ setCustomValidity( {
81
+ type: 'invalid',
82
+ message,
83
+ } );
84
+ return;
85
+ }
86
+
87
+ setCustomValidity( undefined );
88
+ },
89
+ [ data, field, isValid, setValue ]
90
+ );
91
+
92
+ return (
93
+ <ValidatedInputControl
94
+ required={ !! isValid?.required }
95
+ onValidate={ onValidateControl }
96
+ customValidity={ customValidity }
97
+ label={ label }
98
+ placeholder={ placeholder }
99
+ value={ value ?? '' }
100
+ help={ description }
101
+ onChange={ onChangeControl }
102
+ hideLabelFromVision={ hideLabelFromVision }
103
+ type={ type }
104
+ prefix={ prefix }
105
+ suffix={ suffix }
106
+ __next40pxDefaultSize
107
+ />
108
+ );
109
+ }
@@ -9,7 +9,7 @@ import {
9
9
  Dropdown,
10
10
  Button,
11
11
  } from '@wordpress/components';
12
- import { sprintf, __, _x } from '@wordpress/i18n';
12
+ import { __ } from '@wordpress/i18n';
13
13
  import { useMemo } from '@wordpress/element';
14
14
  import { closeSmall } from '@wordpress/icons';
15
15
 
@@ -20,6 +20,7 @@ import type { Form, FormField, NormalizedField } from '../../types';
20
20
  import { DataFormLayout } from '../data-form-layout';
21
21
  import { isCombinedField } from '../is-combined-field';
22
22
  import { DEFAULT_LAYOUT } from '../../normalize-form-fields';
23
+ import SummaryButton from './summary-button';
23
24
 
24
25
  function DropdownHeader( {
25
26
  title,
@@ -55,6 +56,7 @@ function DropdownHeader( {
55
56
 
56
57
  function PanelDropdown< Item >( {
57
58
  fieldDefinition,
59
+ summaryFields,
58
60
  popoverAnchor,
59
61
  labelPosition = 'side',
60
62
  data,
@@ -62,6 +64,7 @@ function PanelDropdown< Item >( {
62
64
  field,
63
65
  }: {
64
66
  fieldDefinition: NormalizedField< Item >;
67
+ summaryFields: NormalizedField< Item >[];
65
68
  popoverAnchor: HTMLElement | null;
66
69
  labelPosition: 'side' | 'top' | 'none';
67
70
  data: Item;
@@ -107,29 +110,15 @@ function PanelDropdown< Item >( {
107
110
  tooltipPosition: 'middle left',
108
111
  } }
109
112
  renderToggle={ ( { isOpen, onToggle } ) => (
110
- <Button
111
- className="dataforms-layouts-panel__field-control"
112
- size="compact"
113
- variant={
114
- [ 'none', 'top' ].includes( labelPosition )
115
- ? 'link'
116
- : 'tertiary'
117
- }
118
- aria-expanded={ isOpen }
119
- aria-label={ sprintf(
120
- // translators: %s: Field name.
121
- _x( 'Edit %s', 'field' ),
122
- fieldLabel || ''
123
- ) }
124
- onClick={ onToggle }
113
+ <SummaryButton
114
+ summaryFields={ summaryFields }
115
+ data={ data }
116
+ labelPosition={ labelPosition }
117
+ fieldLabel={ fieldLabel }
125
118
  disabled={ fieldDefinition.readOnly === true }
126
- accessibleWhenDisabled
127
- >
128
- <fieldDefinition.render
129
- item={ data }
130
- field={ fieldDefinition }
131
- />
132
- </Button>
119
+ onClick={ onToggle }
120
+ aria-expanded={ isOpen }
121
+ />
133
122
  ) }
134
123
  renderContent={ ( { onClose } ) => (
135
124
  <>
@@ -32,28 +32,49 @@ export default function FormPanelField< Item >( {
32
32
  onChange,
33
33
  }: FieldLayoutProps< Item > ) {
34
34
  const { fields } = useContext( DataFormContext );
35
- const fieldDefinition = fields.find( ( _field ) => {
36
- // Default to the first simple child if it is a combined field.
37
- if ( isCombinedField( field ) ) {
38
- const simpleChildren = field.children.filter(
39
- ( child ): child is string | SimpleFormField =>
40
- typeof child === 'string' || ! isCombinedField( child )
35
+ const getSummaryFields = () => {
36
+ if ( ! isCombinedField( field ) ) {
37
+ const fieldDef = fields.find(
38
+ ( _field ) => _field.id === field.id
41
39
  );
40
+ return fieldDef ? [ fieldDef ] : [];
41
+ }
42
42
 
43
- if ( simpleChildren.length === 0 ) {
44
- return false;
45
- }
43
+ // Use summary field(s) if specified for combined fields
44
+ if ( field.summary ) {
45
+ const summaryIds = Array.isArray( field.summary )
46
+ ? field.summary
47
+ : [ field.summary ];
48
+ return summaryIds
49
+ .map( ( summaryId ) =>
50
+ fields.find( ( _field ) => _field.id === summaryId )
51
+ )
52
+ .filter( ( _field ) => _field !== undefined );
53
+ }
46
54
 
47
- const firstChildFieldId =
48
- typeof simpleChildren[ 0 ] === 'string'
49
- ? simpleChildren[ 0 ]
50
- : simpleChildren[ 0 ].id;
55
+ // Default to the first simple child
56
+ const simpleChildren = field.children.filter(
57
+ ( child ): child is string | SimpleFormField =>
58
+ typeof child === 'string' || ! isCombinedField( child )
59
+ );
51
60
 
52
- return _field.id === firstChildFieldId;
61
+ if ( simpleChildren.length === 0 ) {
62
+ return [];
53
63
  }
54
64
 
55
- return _field.id === field.id;
56
- } );
65
+ const firstChildFieldId =
66
+ typeof simpleChildren[ 0 ] === 'string'
67
+ ? simpleChildren[ 0 ]
68
+ : simpleChildren[ 0 ].id;
69
+
70
+ const fieldDef = fields.find(
71
+ ( _field ) => _field.id === firstChildFieldId
72
+ );
73
+ return fieldDef ? [ fieldDef ] : [];
74
+ };
75
+
76
+ const summaryFields = getSummaryFields();
77
+ const fieldDefinition = summaryFields[ 0 ]; // For backward compatibility
57
78
 
58
79
  // Use internal state instead of a ref to make sure that the component
59
80
  // re-renders when the popover's anchor updates.
@@ -84,6 +105,7 @@ export default function FormPanelField< Item >( {
84
105
  <PanelModal
85
106
  field={ field }
86
107
  fieldDefinition={ fieldDefinition }
108
+ summaryFields={ summaryFields }
87
109
  data={ data }
88
110
  onChange={ onChange }
89
111
  labelPosition={ labelPosition }
@@ -93,6 +115,7 @@ export default function FormPanelField< Item >( {
93
115
  field={ field }
94
116
  popoverAnchor={ popoverAnchor }
95
117
  fieldDefinition={ fieldDefinition }
118
+ summaryFields={ summaryFields }
96
119
  data={ data }
97
120
  onChange={ onChange }
98
121
  labelPosition={ labelPosition }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import deepMerge from 'deepmerge';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
@@ -7,7 +12,7 @@ import {
7
12
  Button,
8
13
  Modal,
9
14
  } from '@wordpress/components';
10
- import { __, sprintf, _x } from '@wordpress/i18n';
15
+ import { __ } from '@wordpress/i18n';
11
16
  import { useState, useMemo } from '@wordpress/element';
12
17
 
13
18
  /**
@@ -17,6 +22,7 @@ import type { Form, FormField, NormalizedField } from '../../types';
17
22
  import { DataFormLayout } from '../data-form-layout';
18
23
  import { isCombinedField } from '../is-combined-field';
19
24
  import { DEFAULT_LAYOUT } from '../../normalize-form-fields';
25
+ import SummaryButton from './summary-button';
20
26
 
21
27
  function ModalContent< Item >( {
22
28
  data,
@@ -32,19 +38,19 @@ function ModalContent< Item >( {
32
38
  onClose: () => void;
33
39
  } ) {
34
40
  const [ changes, setChanges ] = useState< Partial< Item > >( {} );
41
+ const modalData = useMemo( () => {
42
+ return deepMerge( data, changes );
43
+ }, [ data, changes ] );
35
44
 
36
45
  const onApply = () => {
37
46
  onChange( changes );
38
47
  onClose();
39
48
  };
40
49
 
41
- const handleOnChange = ( value: Partial< Item > ) => {
42
- setChanges( ( prev ) => ( { ...prev, ...value } ) );
50
+ const handleOnChange = ( newValue: Partial< Item > ) => {
51
+ setChanges( ( prev ) => deepMerge( prev, newValue ) );
43
52
  };
44
53
 
45
- // Merge original data with local changes for display
46
- const displayData = { ...data, ...changes };
47
-
48
54
  return (
49
55
  <Modal
50
56
  className="dataforms-layouts-panel__modal"
@@ -54,14 +60,14 @@ function ModalContent< Item >( {
54
60
  size="medium"
55
61
  >
56
62
  <DataFormLayout
57
- data={ displayData }
63
+ data={ modalData }
58
64
  form={ form }
59
65
  onChange={ handleOnChange }
60
66
  >
61
67
  { ( FieldLayout, nestedField ) => (
62
68
  <FieldLayout
63
69
  key={ nestedField.id }
64
- data={ displayData }
70
+ data={ modalData }
65
71
  field={ nestedField }
66
72
  onChange={ handleOnChange }
67
73
  hideLabelFromVision={
@@ -96,12 +102,14 @@ function ModalContent< Item >( {
96
102
 
97
103
  function PanelModal< Item >( {
98
104
  fieldDefinition,
105
+ summaryFields,
99
106
  labelPosition,
100
107
  data,
101
108
  onChange,
102
109
  field,
103
110
  }: {
104
111
  fieldDefinition: NormalizedField< Item >;
112
+ summaryFields: NormalizedField< Item >[];
105
113
  labelPosition: 'side' | 'top' | 'none';
106
114
  data: Item;
107
115
  onChange: ( value: any ) => void;
@@ -126,29 +134,15 @@ function PanelModal< Item >( {
126
134
 
127
135
  return (
128
136
  <>
129
- <Button
130
- className="dataforms-layouts-modal__field-control"
131
- size="compact"
132
- variant={
133
- [ 'none', 'top' ].includes( labelPosition )
134
- ? 'link'
135
- : 'tertiary'
136
- }
137
- aria-expanded={ isOpen }
138
- aria-label={ sprintf(
139
- // translators: %s: Field name.
140
- _x( 'Edit %s', 'field' ),
141
- fieldLabel || ''
142
- ) }
143
- onClick={ () => setIsOpen( true ) }
137
+ <SummaryButton
138
+ summaryFields={ summaryFields }
139
+ data={ data }
140
+ labelPosition={ labelPosition }
141
+ fieldLabel={ fieldLabel }
144
142
  disabled={ fieldDefinition.readOnly === true }
145
- accessibleWhenDisabled
146
- >
147
- <fieldDefinition.render
148
- item={ data }
149
- field={ fieldDefinition }
150
- />
151
- </Button>
143
+ onClick={ () => setIsOpen( true ) }
144
+ aria-expanded={ isOpen }
145
+ />
152
146
  { isOpen && (
153
147
  <ModalContent
154
148
  data={ data }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Button } from '@wordpress/components';
5
+ import { sprintf, _x } from '@wordpress/i18n';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import type { NormalizedField } from '../../types';
11
+
12
+ function SummaryButton< Item >( {
13
+ summaryFields,
14
+ data,
15
+ labelPosition,
16
+ fieldLabel,
17
+ disabled,
18
+ onClick,
19
+ 'aria-expanded': ariaExpanded,
20
+ }: {
21
+ summaryFields: NormalizedField< Item >[];
22
+ data: Item;
23
+ labelPosition: 'side' | 'top' | 'none';
24
+ fieldLabel?: string;
25
+ disabled?: boolean;
26
+ onClick: () => void;
27
+ 'aria-expanded'?: boolean;
28
+ } ) {
29
+ return (
30
+ <Button
31
+ className="dataforms-layouts-panel__summary-button"
32
+ size="compact"
33
+ variant={
34
+ [ 'none', 'top' ].includes( labelPosition )
35
+ ? 'link'
36
+ : 'tertiary'
37
+ }
38
+ aria-expanded={ ariaExpanded }
39
+ aria-label={ sprintf(
40
+ // translators: %s: Field name.
41
+ _x( 'Edit %s', 'field' ),
42
+ fieldLabel || ''
43
+ ) }
44
+ onClick={ onClick }
45
+ disabled={ disabled }
46
+ accessibleWhenDisabled
47
+ style={
48
+ summaryFields.length > 1
49
+ ? {
50
+ minHeight: 'auto',
51
+ height: 'auto',
52
+ alignItems: 'flex-start',
53
+ }
54
+ : undefined
55
+ }
56
+ >
57
+ { summaryFields.length > 1 ? (
58
+ <div
59
+ style={ {
60
+ display: 'flex',
61
+ flexDirection: 'column',
62
+ alignItems: 'flex-start',
63
+ width: '100%',
64
+ gap: '2px',
65
+ } }
66
+ >
67
+ { summaryFields.map( ( summaryField ) => (
68
+ <div
69
+ key={ summaryField.id }
70
+ style={ { width: '100%' } }
71
+ >
72
+ <summaryField.render
73
+ item={ data }
74
+ field={ summaryField }
75
+ />
76
+ </div>
77
+ ) ) }
78
+ </div>
79
+ ) : (
80
+ summaryFields.map( ( summaryField ) => (
81
+ <summaryField.render
82
+ key={ summaryField.id }
83
+ item={ data }
84
+ field={ summaryField }
85
+ />
86
+ ) )
87
+ ) }
88
+ </Button>
89
+ );
90
+ }
91
+
92
+ export default SummaryButton;
@@ -12,6 +12,7 @@ import {
12
12
  __experimentalVStack as VStack,
13
13
  __experimentalHeading as Heading,
14
14
  __experimentalSpacer as Spacer,
15
+ BaseControl,
15
16
  } from '@wordpress/components';
16
17
 
17
18
  /**
@@ -84,6 +85,7 @@ export default function FormRegularField< Item >( {
84
85
  if ( ! fieldDefinition || ! fieldDefinition.Edit ) {
85
86
  return null;
86
87
  }
88
+
87
89
  if ( labelPosition === 'side' ) {
88
90
  return (
89
91
  <HStack className="dataforms-layouts-regular__field">
@@ -119,17 +121,17 @@ export default function FormRegularField< Item >( {
119
121
  <div className="dataforms-layouts-regular__field">
120
122
  { fieldDefinition.readOnly === true ? (
121
123
  <>
122
- { ! hideLabelFromVision && labelPosition !== 'none' && (
123
- <div className="dataforms-layouts-regular__field-label">
124
- { fieldDefinition.label }
125
- </div>
126
- ) }
127
- <div className="dataforms-layouts-regular__field-control">
124
+ <>
125
+ { ! hideLabelFromVision && labelPosition !== 'none' && (
126
+ <BaseControl.VisualLabel>
127
+ { fieldDefinition.label }
128
+ </BaseControl.VisualLabel>
129
+ ) }
128
130
  <fieldDefinition.render
129
131
  item={ data }
130
132
  field={ fieldDefinition }
131
133
  />
132
- </div>
134
+ </>
133
135
  </>
134
136
  ) : (
135
137
  <fieldDefinition.Edit
@@ -5,12 +5,6 @@
5
5
  align-items: flex-start !important;
6
6
  }
7
7
 
8
- .dataforms-layouts-regular__field .components-base-control__label {
9
- font-size: inherit;
10
- font-weight: normal;
11
- text-transform: none;
12
- }
13
-
14
8
  .dataforms-layouts-regular__field-label {
15
9
  width: 38%;
16
10
  flex-shrink: 0;
@@ -42,6 +42,7 @@ import type { SetSelection } from '../../private-types';
42
42
  import { ItemClickWrapper } from '../utils/item-click-wrapper';
43
43
  import { GridItems } from '../utils/grid-items';
44
44
  const { Badge } = unlock( componentsPrivateApis );
45
+ import getDataByGroup from '../utils/get-data-by-group';
45
46
 
46
47
  interface GridItemProps< Item > {
47
48
  view: ViewGridType;
@@ -338,19 +339,7 @@ function ViewGrid< Item >( {
338
339
  const groupField = view.groupByField
339
340
  ? fields.find( ( f ) => f.id === view.groupByField )
340
341
  : null;
341
-
342
- // Group data by groupByField if specified
343
- const dataByGroup = groupField
344
- ? data.reduce( ( groups: Map< string, typeof data >, item ) => {
345
- const groupName = groupField.getValue( { item } );
346
- if ( ! groups.has( groupName ) ) {
347
- groups.set( groupName, [] );
348
- }
349
- groups.get( groupName )?.push( item );
350
- return groups;
351
- }, new Map< string, typeof data >() )
352
- : null;
353
-
342
+ const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
354
343
  const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
355
344
 
356
345
  return (
@@ -474,7 +463,13 @@ function ViewGrid< Item >( {
474
463
  'dataviews-no-results': ! isLoading,
475
464
  } ) }
476
465
  >
477
- <p>{ isLoading ? <Spinner /> : empty }</p>
466
+ { isLoading ? (
467
+ <p>
468
+ <Spinner />
469
+ </p>
470
+ ) : (
471
+ empty
472
+ ) }
478
473
  </div>
479
474
  )
480
475
  }
@@ -43,6 +43,7 @@
43
43
  aspect-ratio: 1/1;
44
44
  background-color: $white;
45
45
  border-radius: $radius-medium;
46
+ overflow: hidden;
46
47
  position: relative;
47
48
 
48
49
  img {
@@ -24,7 +24,7 @@ import {
24
24
  useState,
25
25
  useContext,
26
26
  } from '@wordpress/element';
27
- import { __ } from '@wordpress/i18n';
27
+ import { __, sprintf } from '@wordpress/i18n';
28
28
  import { moreVertical } from '@wordpress/icons';
29
29
  import { useRegistry } from '@wordpress/data';
30
30
 
@@ -44,6 +44,7 @@ import type {
44
44
  ViewListProps,
45
45
  ActionModal as ActionModalType,
46
46
  } from '../../types';
47
+ import getDataByGroup from '../utils/get-data-by-group';
47
48
 
48
49
  interface ListViewItemProps< Item > {
49
50
  view: ViewListType;
@@ -512,11 +513,82 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
512
513
  'dataviews-no-results': ! hasData && ! isLoading,
513
514
  } ) }
514
515
  >
515
- { ! hasData && <p>{ isLoading ? <Spinner /> : empty }</p> }
516
+ { ! hasData &&
517
+ ( isLoading ? (
518
+ <p>
519
+ <Spinner />
520
+ </p>
521
+ ) : (
522
+ empty
523
+ ) ) }
516
524
  </div>
517
525
  );
518
526
  }
519
527
 
528
+ const groupField = view.groupByField
529
+ ? fields.find( ( field ) => field.id === view.groupByField )
530
+ : null;
531
+ const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
532
+
533
+ // Render data grouped by field
534
+ if ( hasData && groupField && dataByGroup ) {
535
+ return (
536
+ <Composite
537
+ id={ `${ baseId }` }
538
+ render={ <div /> }
539
+ className="dataviews-view-list__group"
540
+ role="grid"
541
+ activeId={ activeCompositeId }
542
+ setActiveId={ setActiveCompositeId }
543
+ >
544
+ <VStack
545
+ spacing={ 4 }
546
+ className={ clsx( 'dataviews-view-list', className ) }
547
+ >
548
+ { Array.from( dataByGroup.entries() ).map(
549
+ ( [ groupName, groupItems ] ) => (
550
+ <VStack key={ groupName } spacing={ 2 }>
551
+ <h3 className="dataviews-view-list__group-header">
552
+ { sprintf(
553
+ // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022".
554
+ __( '%1$s: %2$s' ),
555
+ groupField.label,
556
+ groupName
557
+ ) }
558
+ </h3>
559
+ { groupItems.map( ( item ) => {
560
+ const id =
561
+ generateCompositeItemIdPrefix( item );
562
+ return (
563
+ <ListItem
564
+ key={ id }
565
+ view={ view }
566
+ idPrefix={ id }
567
+ actions={ actions }
568
+ item={ item }
569
+ isSelected={ item === selectedItem }
570
+ onSelect={ onSelect }
571
+ mediaField={ mediaField }
572
+ titleField={ titleField }
573
+ descriptionField={
574
+ descriptionField
575
+ }
576
+ otherFields={ otherFields }
577
+ onDropdownTriggerKeyDown={
578
+ onDropdownTriggerKeyDown
579
+ }
580
+ />
581
+ );
582
+ } ) }
583
+ </VStack>
584
+ )
585
+ ) }
586
+ </VStack>
587
+ </Composite>
588
+ );
589
+ }
590
+
591
+ // Render ungrouped data
520
592
  return (
521
593
  <>
522
594
  <Composite
@@ -196,3 +196,11 @@ div.dataviews-view-list {
196
196
  justify-content: space-between;
197
197
  }
198
198
  }
199
+
200
+ .dataviews-view-list__group-header {
201
+ font-size: $font-size-large;
202
+ font-weight: $font-weight-medium;
203
+ color: $gray-900;
204
+ margin: 0 0 $grid-unit-10 0;
205
+ padding: 0 $grid-unit-30;
206
+ }