@wordpress/dataviews 9.0.1-next.6f42e1382.0 → 9.1.1-next.f56bd8138.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 (201) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/README.md +107 -11
  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-view-config/index.js +22 -3
  6. package/build/components/dataviews-view-config/index.js.map +1 -1
  7. package/build/dataform-controls/array.js +117 -29
  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 +7 -1
  18. package/build/dataform-controls/email.js.map +1 -1
  19. package/build/dataform-controls/index.js +23 -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/radio.js +42 -9
  24. package/build/dataform-controls/radio.js.map +1 -1
  25. package/build/dataform-controls/relative-date-control.js +6 -10
  26. package/build/dataform-controls/relative-date-control.js.map +1 -1
  27. package/build/dataform-controls/select.js +41 -10
  28. package/build/dataform-controls/select.js.map +1 -1
  29. package/build/dataform-controls/telephone.js +7 -1
  30. package/build/dataform-controls/telephone.js.map +1 -1
  31. package/build/dataform-controls/text.js +14 -2
  32. package/build/dataform-controls/text.js.map +1 -1
  33. package/build/dataform-controls/textarea.js +33 -20
  34. package/build/dataform-controls/textarea.js.map +1 -1
  35. package/build/dataform-controls/toggle-group.js +36 -6
  36. package/build/dataform-controls/toggle-group.js.map +1 -1
  37. package/build/dataform-controls/toggle.js +33 -22
  38. package/build/dataform-controls/toggle.js.map +1 -1
  39. package/build/dataform-controls/url.js +7 -1
  40. package/build/dataform-controls/url.js.map +1 -1
  41. package/build/dataform-controls/utils/validated-input.js +34 -32
  42. package/build/dataform-controls/utils/validated-input.js.map +1 -1
  43. package/build/dataforms-layouts/panel/dropdown.js +10 -14
  44. package/build/dataforms-layouts/panel/dropdown.js.map +1 -1
  45. package/build/dataforms-layouts/panel/index.js +24 -11
  46. package/build/dataforms-layouts/panel/index.js.map +1 -1
  47. package/build/dataforms-layouts/panel/modal.js +22 -27
  48. package/build/dataforms-layouts/panel/modal.js.map +1 -1
  49. package/build/dataforms-layouts/panel/summary-button.js +67 -0
  50. package/build/dataforms-layouts/panel/summary-button.js.map +1 -0
  51. package/build/field-types/array.js +0 -6
  52. package/build/field-types/array.js.map +1 -1
  53. package/build/index.js +7 -0
  54. package/build/index.js.map +1 -1
  55. package/build/normalize-fields.js +17 -0
  56. package/build/normalize-fields.js.map +1 -1
  57. package/build/types.js.map +1 -1
  58. package/build/validation.js +18 -1
  59. package/build/validation.js.map +1 -1
  60. package/build-module/components/dataviews-filters/input-widget.js +48 -4
  61. package/build-module/components/dataviews-filters/input-widget.js.map +1 -1
  62. package/build-module/components/dataviews-view-config/index.js +22 -3
  63. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  64. package/build-module/dataform-controls/array.js +120 -32
  65. package/build-module/dataform-controls/array.js.map +1 -1
  66. package/build-module/dataform-controls/checkbox.js +31 -21
  67. package/build-module/dataform-controls/checkbox.js.map +1 -1
  68. package/build-module/dataform-controls/color.js +28 -24
  69. package/build-module/dataform-controls/color.js.map +1 -1
  70. package/build-module/dataform-controls/date.js +32 -24
  71. package/build-module/dataform-controls/date.js.map +1 -1
  72. package/build-module/dataform-controls/datetime.js +135 -21
  73. package/build-module/dataform-controls/datetime.js.map +1 -1
  74. package/build-module/dataform-controls/email.js +7 -1
  75. package/build-module/dataform-controls/email.js.map +1 -1
  76. package/build-module/dataform-controls/index.js +23 -0
  77. package/build-module/dataform-controls/index.js.map +1 -1
  78. package/build-module/dataform-controls/integer.js +46 -34
  79. package/build-module/dataform-controls/integer.js.map +1 -1
  80. package/build-module/dataform-controls/radio.js +44 -11
  81. package/build-module/dataform-controls/radio.js.map +1 -1
  82. package/build-module/dataform-controls/relative-date-control.js +6 -10
  83. package/build-module/dataform-controls/relative-date-control.js.map +1 -1
  84. package/build-module/dataform-controls/select.js +43 -12
  85. package/build-module/dataform-controls/select.js.map +1 -1
  86. package/build-module/dataform-controls/telephone.js +7 -1
  87. package/build-module/dataform-controls/telephone.js.map +1 -1
  88. package/build-module/dataform-controls/text.js +14 -2
  89. package/build-module/dataform-controls/text.js.map +1 -1
  90. package/build-module/dataform-controls/textarea.js +32 -20
  91. package/build-module/dataform-controls/textarea.js.map +1 -1
  92. package/build-module/dataform-controls/toggle-group.js +38 -8
  93. package/build-module/dataform-controls/toggle-group.js.map +1 -1
  94. package/build-module/dataform-controls/toggle.js +33 -23
  95. package/build-module/dataform-controls/toggle.js.map +1 -1
  96. package/build-module/dataform-controls/url.js +7 -1
  97. package/build-module/dataform-controls/url.js.map +1 -1
  98. package/build-module/dataform-controls/utils/validated-input.js +34 -33
  99. package/build-module/dataform-controls/utils/validated-input.js.map +1 -1
  100. package/build-module/dataforms-layouts/panel/dropdown.js +10 -15
  101. package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -1
  102. package/build-module/dataforms-layouts/panel/index.js +24 -11
  103. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  104. package/build-module/dataforms-layouts/panel/modal.js +22 -28
  105. package/build-module/dataforms-layouts/panel/modal.js.map +1 -1
  106. package/build-module/dataforms-layouts/panel/summary-button.js +60 -0
  107. package/build-module/dataforms-layouts/panel/summary-button.js.map +1 -0
  108. package/build-module/field-types/array.js +0 -6
  109. package/build-module/field-types/array.js.map +1 -1
  110. package/build-module/index.js +1 -0
  111. package/build-module/index.js.map +1 -1
  112. package/build-module/normalize-fields.js +15 -0
  113. package/build-module/normalize-fields.js.map +1 -1
  114. package/build-module/types.js.map +1 -1
  115. package/build-module/validation.js +18 -1
  116. package/build-module/validation.js.map +1 -1
  117. package/build-types/components/dataform/stories/index.story.d.ts +3 -0
  118. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  119. package/build-types/components/dataviews/stories/fixtures.d.ts +4 -2
  120. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  121. package/build-types/components/dataviews-filters/input-widget.d.ts.map +1 -1
  122. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  123. package/build-types/dataform-controls/array.d.ts.map +1 -1
  124. package/build-types/dataform-controls/checkbox.d.ts.map +1 -1
  125. package/build-types/dataform-controls/color.d.ts.map +1 -1
  126. package/build-types/dataform-controls/date.d.ts.map +1 -1
  127. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  128. package/build-types/dataform-controls/email.d.ts.map +1 -1
  129. package/build-types/dataform-controls/index.d.ts +1 -1
  130. package/build-types/dataform-controls/index.d.ts.map +1 -1
  131. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  132. package/build-types/dataform-controls/radio.d.ts.map +1 -1
  133. package/build-types/dataform-controls/relative-date-control.d.ts +6 -5
  134. package/build-types/dataform-controls/relative-date-control.d.ts.map +1 -1
  135. package/build-types/dataform-controls/select.d.ts.map +1 -1
  136. package/build-types/dataform-controls/telephone.d.ts.map +1 -1
  137. package/build-types/dataform-controls/text.d.ts +1 -1
  138. package/build-types/dataform-controls/text.d.ts.map +1 -1
  139. package/build-types/dataform-controls/textarea.d.ts +1 -1
  140. package/build-types/dataform-controls/textarea.d.ts.map +1 -1
  141. package/build-types/dataform-controls/toggle-group.d.ts.map +1 -1
  142. package/build-types/dataform-controls/toggle.d.ts.map +1 -1
  143. package/build-types/dataform-controls/url.d.ts.map +1 -1
  144. package/build-types/dataform-controls/utils/validated-input.d.ts +4 -4
  145. package/build-types/dataform-controls/utils/validated-input.d.ts.map +1 -1
  146. package/build-types/dataforms-layouts/panel/dropdown.d.ts +2 -1
  147. package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -1
  148. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  149. package/build-types/dataforms-layouts/panel/modal.d.ts +2 -1
  150. package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -1
  151. package/build-types/dataforms-layouts/panel/summary-button.d.ts +15 -0
  152. package/build-types/dataforms-layouts/panel/summary-button.d.ts.map +1 -0
  153. package/build-types/field-types/array.d.ts.map +1 -1
  154. package/build-types/field-types/stories/index.story.d.ts.map +1 -1
  155. package/build-types/index.d.ts +1 -0
  156. package/build-types/index.d.ts.map +1 -1
  157. package/build-types/normalize-fields.d.ts +3 -0
  158. package/build-types/normalize-fields.d.ts.map +1 -1
  159. package/build-types/types.d.ts +68 -4
  160. package/build-types/types.d.ts.map +1 -1
  161. package/build-types/validation.d.ts.map +1 -1
  162. package/build-wp/index.js +2009 -1489
  163. package/package.json +16 -15
  164. package/src/components/dataform/stories/index.story.tsx +509 -8
  165. package/src/components/dataviews/stories/fixtures.tsx +99 -41
  166. package/src/components/dataviews/stories/index.story.tsx +1 -1
  167. package/src/components/dataviews-filters/input-widget.tsx +44 -5
  168. package/src/components/dataviews-picker/stories/index.story.tsx +1 -1
  169. package/src/components/dataviews-view-config/index.tsx +18 -3
  170. package/src/dataform-controls/array.tsx +139 -44
  171. package/src/dataform-controls/checkbox.tsx +41 -24
  172. package/src/dataform-controls/color.tsx +33 -24
  173. package/src/dataform-controls/date.tsx +47 -21
  174. package/src/dataform-controls/datetime.tsx +171 -23
  175. package/src/dataform-controls/email.tsx +9 -1
  176. package/src/dataform-controls/index.tsx +26 -0
  177. package/src/dataform-controls/integer.tsx +82 -49
  178. package/src/dataform-controls/radio.tsx +53 -11
  179. package/src/dataform-controls/relative-date-control.tsx +11 -10
  180. package/src/dataform-controls/select.tsx +53 -10
  181. package/src/dataform-controls/telephone.tsx +9 -1
  182. package/src/dataform-controls/text.tsx +18 -1
  183. package/src/dataform-controls/textarea.tsx +38 -24
  184. package/src/dataform-controls/toggle-group.tsx +50 -10
  185. package/src/dataform-controls/toggle.tsx +41 -24
  186. package/src/dataform-controls/url.tsx +9 -1
  187. package/src/dataform-controls/utils/validated-input.tsx +50 -50
  188. package/src/dataforms-layouts/panel/dropdown.tsx +12 -23
  189. package/src/dataforms-layouts/panel/index.tsx +39 -16
  190. package/src/dataforms-layouts/panel/modal.tsx +24 -30
  191. package/src/dataforms-layouts/panel/summary-button.tsx +92 -0
  192. package/src/field-types/array.tsx +0 -8
  193. package/src/field-types/stories/index.story.tsx +89 -1
  194. package/src/index.ts +1 -0
  195. package/src/normalize-fields.ts +18 -0
  196. package/src/test/filter-and-sort-data-view.js +148 -138
  197. package/src/test/normalize-fields.ts +114 -0
  198. package/src/test/validation.ts +192 -0
  199. package/src/types.ts +75 -4
  200. package/src/validation.ts +30 -0
  201. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,13 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import deepMerge from 'deepmerge';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
4
9
  import { privateApis } from '@wordpress/components';
5
- import { useState } from '@wordpress/element';
10
+ import { useCallback, useState } from '@wordpress/element';
6
11
 
7
12
  /**
8
13
  * Internal dependencies
@@ -18,7 +23,7 @@ export default function Toggle< Item >( {
18
23
  data,
19
24
  hideLabelFromVision,
20
25
  }: DataFormControlProps< Item > ) {
21
- const { id, getValue, label, description } = field;
26
+ const { label, description, getValue, setValue } = field;
22
27
  const [ customValidity, setCustomValidity ] =
23
28
  useState<
24
29
  React.ComponentProps<
@@ -26,37 +31,49 @@ export default function Toggle< Item >( {
26
31
  >[ 'customValidity' ]
27
32
  >( undefined );
28
33
 
34
+ const onChangeControl = useCallback( () => {
35
+ onChange(
36
+ setValue( { item: data, value: ! getValue( { item: data } ) } )
37
+ );
38
+ }, [ onChange, setValue, data, getValue ] );
39
+
40
+ const onValidateControl = useCallback(
41
+ ( newValue: any ) => {
42
+ const message = field.isValid?.custom?.(
43
+ deepMerge(
44
+ data,
45
+ setValue( {
46
+ item: data,
47
+ value: newValue,
48
+ } ) as Partial< Item >
49
+ ),
50
+ field
51
+ );
52
+
53
+ if ( message ) {
54
+ setCustomValidity( {
55
+ type: 'invalid',
56
+ message,
57
+ } );
58
+ return;
59
+ }
60
+
61
+ setCustomValidity( undefined );
62
+ },
63
+ [ data, field, setValue ]
64
+ );
65
+
29
66
  return (
30
67
  <ValidatedToggleControl
31
68
  required={ !! field.isValid.required }
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;
47
- }
48
-
49
- setCustomValidity( undefined );
50
- } }
69
+ onValidate={ onValidateControl }
51
70
  customValidity={ customValidity }
52
71
  hidden={ hideLabelFromVision }
53
72
  __nextHasNoMarginBottom
54
73
  label={ label }
55
74
  help={ description }
56
75
  checked={ getValue( { item: data } ) }
57
- onChange={ () =>
58
- onChange( { [ id ]: ! getValue( { item: data } ) } )
59
- }
76
+ onChange={ onChangeControl }
60
77
  />
61
78
  );
62
79
  }
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
+ import {
5
+ Icon,
6
+ __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
7
+ } from '@wordpress/components';
4
8
  import { link } from '@wordpress/icons';
5
9
 
6
10
  /**
@@ -23,7 +27,11 @@ export default function Url< Item >( {
23
27
  onChange,
24
28
  hideLabelFromVision,
25
29
  type: 'url',
26
- icon: link,
30
+ prefix: (
31
+ <InputControlPrefixWrapper variant="icon">
32
+ <Icon icon={ link } />
33
+ </InputControlPrefixWrapper>
34
+ ),
27
35
  } }
28
36
  />
29
37
  );
@@ -1,12 +1,12 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import deepMerge from 'deepmerge';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
4
- import {
5
- Icon,
6
- privateApis,
7
- __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper,
8
- __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper,
9
- } from '@wordpress/components';
9
+ import { privateApis } from '@wordpress/components';
10
10
  import { useCallback, useState } from '@wordpress/element';
11
11
 
12
12
  /**
@@ -24,11 +24,11 @@ export type DataFormValidatedTextControlProps< Item > =
24
24
  */
25
25
  type?: 'text' | 'email' | 'tel' | 'url' | 'password';
26
26
  /**
27
- * Optional icon to display as prefix.
27
+ * Optional prefix element to display before the input.
28
28
  */
29
- icon?: React.ComponentType | React.ReactElement;
29
+ prefix?: React.ReactElement;
30
30
  /**
31
- * Optional icon to display as suffix.
31
+ * Optional suffix element to display after the input.
32
32
  */
33
33
  suffix?: React.ReactElement;
34
34
  };
@@ -39,11 +39,12 @@ export default function ValidatedText< Item >( {
39
39
  onChange,
40
40
  hideLabelFromVision,
41
41
  type,
42
- icon,
42
+ prefix,
43
43
  suffix,
44
44
  }: DataFormValidatedTextControlProps< Item > ) {
45
- const { id, label, placeholder, description } = field;
46
- const value = field.getValue( { item: data } );
45
+ const { label, placeholder, description, getValue, setValue, isValid } =
46
+ field;
47
+ const value = getValue( { item: data } );
47
48
  const [ customValidity, setCustomValidity ] =
48
49
  useState<
49
50
  React.ComponentProps<
@@ -53,34 +54,45 @@ export default function ValidatedText< Item >( {
53
54
 
54
55
  const onChangeControl = useCallback(
55
56
  ( newValue: string ) =>
56
- onChange( {
57
- [ id ]: newValue,
58
- } ),
59
- [ id, onChange ]
57
+ onChange(
58
+ setValue( {
59
+ item: data,
60
+ value: newValue,
61
+ } )
62
+ ),
63
+ [ data, setValue, onChange ]
60
64
  );
61
65
 
62
- return (
63
- <ValidatedInputControl
64
- required={ !! field.isValid?.required }
65
- onValidate={ ( newValue: any ) => {
66
- const message = field.isValid?.custom?.(
67
- {
68
- ...data,
69
- [ id ]: newValue,
70
- },
71
- field
72
- );
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
+ }
73
86
 
74
- if ( message ) {
75
- setCustomValidity( {
76
- type: 'invalid',
77
- message,
78
- } );
79
- return;
80
- }
87
+ setCustomValidity( undefined );
88
+ },
89
+ [ data, field, isValid, setValue ]
90
+ );
81
91
 
82
- setCustomValidity( undefined );
83
- } }
92
+ return (
93
+ <ValidatedInputControl
94
+ required={ !! isValid?.required }
95
+ onValidate={ onValidateControl }
84
96
  customValidity={ customValidity }
85
97
  label={ label }
86
98
  placeholder={ placeholder }
@@ -89,20 +101,8 @@ export default function ValidatedText< Item >( {
89
101
  onChange={ onChangeControl }
90
102
  hideLabelFromVision={ hideLabelFromVision }
91
103
  type={ type }
92
- prefix={
93
- icon ? (
94
- <InputControlPrefixWrapper variant="icon">
95
- <Icon icon={ icon } />
96
- </InputControlPrefixWrapper>
97
- ) : undefined
98
- }
99
- suffix={
100
- suffix ? (
101
- <InputControlSuffixWrapper variant="control">
102
- { suffix }
103
- </InputControlSuffixWrapper>
104
- ) : undefined
105
- }
104
+ prefix={ prefix }
105
+ suffix={ suffix }
106
106
  __next40pxDefaultSize
107
107
  />
108
108
  );
@@ -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;
@@ -59,14 +59,6 @@ const arrayFieldType: FieldTypeDefinition< any > = {
59
59
  return __( 'Every value must be a string.' );
60
60
  }
61
61
 
62
- if ( field?.elements ) {
63
- const validValues = field.elements.map( ( f ) => f.value );
64
- if (
65
- ! value.every( ( v: any ) => validValues.includes( v ) )
66
- ) {
67
- return __( 'Value must be one of the elements.' );
68
- }
69
- }
70
62
  return null;
71
63
  },
72
64
  },