@wordpress/dataviews 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +42 -14
  3. package/build/components/dataviews/index.js +38 -6
  4. package/build/components/dataviews/index.js.map +1 -1
  5. package/build/components/dataviews-context/index.js +4 -1
  6. package/build/components/dataviews-context/index.js.map +1 -1
  7. package/build/components/dataviews-item-actions/index.js +1 -10
  8. package/build/components/dataviews-item-actions/index.js.map +1 -1
  9. package/build/components/dataviews-pagination/index.js +1 -1
  10. package/build/components/dataviews-pagination/index.js.map +1 -1
  11. package/build/components/dataviews-view-config/index.js +8 -5
  12. package/build/components/dataviews-view-config/index.js.map +1 -1
  13. package/build/components/dataviews-view-config/infinite-scroll-toggle.js +47 -0
  14. package/build/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
  15. package/build/dataform-controls/array.js +70 -0
  16. package/build/dataform-controls/array.js.map +1 -0
  17. package/build/dataform-controls/boolean.js +15 -7
  18. package/build/dataform-controls/boolean.js.map +1 -1
  19. package/build/dataform-controls/email.js +14 -7
  20. package/build/dataform-controls/email.js.map +1 -1
  21. package/build/dataform-controls/index.js +3 -1
  22. package/build/dataform-controls/index.js.map +1 -1
  23. package/build/dataform-controls/integer.js +14 -7
  24. package/build/dataform-controls/integer.js.map +1 -1
  25. package/build/dataform-controls/text.js +14 -7
  26. package/build/dataform-controls/text.js.map +1 -1
  27. package/build/dataforms-layouts/card/index.js +137 -0
  28. package/build/dataforms-layouts/card/index.js.map +1 -0
  29. package/build/dataforms-layouts/data-form-layout.js +2 -2
  30. package/build/dataforms-layouts/data-form-layout.js.map +1 -1
  31. package/build/dataforms-layouts/index.js +4 -0
  32. package/build/dataforms-layouts/index.js.map +1 -1
  33. package/build/dataforms-layouts/panel/dropdown.js +124 -0
  34. package/build/dataforms-layouts/panel/dropdown.js.map +1 -0
  35. package/build/dataforms-layouts/panel/index.js +34 -149
  36. package/build/dataforms-layouts/panel/index.js.map +1 -1
  37. package/build/dataforms-layouts/panel/modal.js +125 -0
  38. package/build/dataforms-layouts/panel/modal.js.map +1 -0
  39. package/build/dataforms-layouts/regular/index.js +10 -21
  40. package/build/dataforms-layouts/regular/index.js.map +1 -1
  41. package/build/dataviews-layouts/grid/index.js +24 -7
  42. package/build/dataviews-layouts/grid/index.js.map +1 -1
  43. package/build/dataviews-layouts/grid/preview-size-picker.js +11 -11
  44. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  45. package/build/dataviews-layouts/list/index.js +45 -27
  46. package/build/dataviews-layouts/list/index.js.map +1 -1
  47. package/build/dataviews-layouts/table/column-header-menu.js +3 -0
  48. package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
  49. package/build/dataviews-layouts/table/index.js +23 -8
  50. package/build/dataviews-layouts/table/index.js.map +1 -1
  51. package/build/field-types/array.js +2 -2
  52. package/build/field-types/array.js.map +1 -1
  53. package/build/normalize-form-fields.js +52 -13
  54. package/build/normalize-form-fields.js.map +1 -1
  55. package/build/types.js.map +1 -1
  56. package/build-module/components/dataviews/index.js +40 -8
  57. package/build-module/components/dataviews/index.js.map +1 -1
  58. package/build-module/components/dataviews-context/index.js +4 -1
  59. package/build-module/components/dataviews-context/index.js.map +1 -1
  60. package/build-module/components/dataviews-item-actions/index.js +1 -10
  61. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  62. package/build-module/components/dataviews-pagination/index.js +1 -1
  63. package/build-module/components/dataviews-pagination/index.js.map +1 -1
  64. package/build-module/components/dataviews-view-config/index.js +8 -5
  65. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  66. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js +39 -0
  67. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -0
  68. package/build-module/dataform-controls/array.js +63 -0
  69. package/build-module/dataform-controls/array.js.map +1 -0
  70. package/build-module/dataform-controls/boolean.js +15 -7
  71. package/build-module/dataform-controls/boolean.js.map +1 -1
  72. package/build-module/dataform-controls/email.js +15 -8
  73. package/build-module/dataform-controls/email.js.map +1 -1
  74. package/build-module/dataform-controls/index.js +3 -1
  75. package/build-module/dataform-controls/index.js.map +1 -1
  76. package/build-module/dataform-controls/integer.js +15 -8
  77. package/build-module/dataform-controls/integer.js.map +1 -1
  78. package/build-module/dataform-controls/text.js +15 -8
  79. package/build-module/dataform-controls/text.js.map +1 -1
  80. package/build-module/dataforms-layouts/card/index.js +128 -0
  81. package/build-module/dataforms-layouts/card/index.js.map +1 -0
  82. package/build-module/dataforms-layouts/data-form-layout.js +2 -2
  83. package/build-module/dataforms-layouts/data-form-layout.js.map +1 -1
  84. package/build-module/dataforms-layouts/index.js +4 -0
  85. package/build-module/dataforms-layouts/index.js.map +1 -1
  86. package/build-module/dataforms-layouts/panel/dropdown.js +118 -0
  87. package/build-module/dataforms-layouts/panel/dropdown.js.map +1 -0
  88. package/build-module/dataforms-layouts/panel/index.js +37 -152
  89. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  90. package/build-module/dataforms-layouts/panel/modal.js +119 -0
  91. package/build-module/dataforms-layouts/panel/modal.js.map +1 -0
  92. package/build-module/dataforms-layouts/regular/index.js +10 -21
  93. package/build-module/dataforms-layouts/regular/index.js.map +1 -1
  94. package/build-module/dataviews-layouts/grid/index.js +25 -8
  95. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  96. package/build-module/dataviews-layouts/grid/preview-size-picker.js +11 -11
  97. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  98. package/build-module/dataviews-layouts/list/index.js +47 -29
  99. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  100. package/build-module/dataviews-layouts/table/column-header-menu.js +3 -0
  101. package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
  102. package/build-module/dataviews-layouts/table/index.js +23 -8
  103. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  104. package/build-module/field-types/array.js +2 -2
  105. package/build-module/field-types/array.js.map +1 -1
  106. package/build-module/normalize-form-fields.js +50 -13
  107. package/build-module/normalize-form-fields.js.map +1 -1
  108. package/build-module/types.js.map +1 -1
  109. package/build-style/style-rtl.css +53 -16
  110. package/build-style/style.css +53 -16
  111. package/build-types/components/dataform/stories/index.story.d.ts +41 -17
  112. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  113. package/build-types/components/dataviews/index.d.ts +5 -2
  114. package/build-types/components/dataviews/index.d.ts.map +1 -1
  115. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  116. package/build-types/components/dataviews/stories/index.story.d.ts +2 -1
  117. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  118. package/build-types/components/dataviews-context/index.d.ts +4 -1
  119. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  120. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  121. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  122. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +2 -0
  123. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -0
  124. package/build-types/dataform-controls/array.d.ts +6 -0
  125. package/build-types/dataform-controls/array.d.ts.map +1 -0
  126. package/build-types/dataform-controls/boolean.d.ts.map +1 -1
  127. package/build-types/dataform-controls/email.d.ts.map +1 -1
  128. package/build-types/dataform-controls/index.d.ts.map +1 -1
  129. package/build-types/dataform-controls/integer.d.ts.map +1 -1
  130. package/build-types/dataform-controls/text.d.ts.map +1 -1
  131. package/build-types/dataforms-layouts/card/index.d.ts +13 -0
  132. package/build-types/dataforms-layouts/card/index.d.ts.map +1 -0
  133. package/build-types/dataforms-layouts/index.d.ts.map +1 -1
  134. package/build-types/dataforms-layouts/panel/dropdown.d.ts +14 -0
  135. package/build-types/dataforms-layouts/panel/dropdown.d.ts.map +1 -0
  136. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  137. package/build-types/dataforms-layouts/panel/modal.d.ts +13 -0
  138. package/build-types/dataforms-layouts/panel/modal.d.ts.map +1 -0
  139. package/build-types/dataforms-layouts/regular/index.d.ts.map +1 -1
  140. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  141. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts +1 -1
  142. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  143. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  144. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  145. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  146. package/build-types/field-types/boolean.d.ts +1 -1
  147. package/build-types/normalize-form-fields.d.ts +10 -3
  148. package/build-types/normalize-form-fields.d.ts.map +1 -1
  149. package/build-types/test/normalize-form-fields.d.ts +2 -0
  150. package/build-types/test/normalize-form-fields.d.ts.map +1 -0
  151. package/build-types/types.d.ts +54 -6
  152. package/build-types/types.d.ts.map +1 -1
  153. package/build-wp/index.js +3062 -1147
  154. package/package.json +15 -15
  155. package/src/components/dataform/stories/index.story.tsx +478 -91
  156. package/src/components/dataviews/index.tsx +50 -14
  157. package/src/components/dataviews/stories/fixtures.tsx +98 -7
  158. package/src/components/dataviews/stories/index.story.tsx +137 -4
  159. package/src/components/dataviews/style.scss +4 -0
  160. package/src/components/dataviews-context/index.ts +6 -2
  161. package/src/components/dataviews-item-actions/index.tsx +7 -16
  162. package/src/components/dataviews-pagination/index.tsx +1 -1
  163. package/src/components/dataviews-view-config/index.tsx +13 -5
  164. package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +39 -0
  165. package/src/dataform-controls/array.tsx +85 -0
  166. package/src/dataform-controls/boolean.tsx +24 -10
  167. package/src/dataform-controls/email.tsx +24 -11
  168. package/src/dataform-controls/index.tsx +3 -1
  169. package/src/dataform-controls/integer.tsx +27 -13
  170. package/src/dataform-controls/text.tsx +24 -11
  171. package/src/dataforms-layouts/card/index.tsx +154 -0
  172. package/src/dataforms-layouts/card/style.scss +3 -0
  173. package/src/dataforms-layouts/data-form-layout.tsx +2 -2
  174. package/src/dataforms-layouts/index.tsx +5 -0
  175. package/src/dataforms-layouts/panel/dropdown.tsx +160 -0
  176. package/src/dataforms-layouts/panel/index.tsx +49 -189
  177. package/src/dataforms-layouts/panel/modal.tsx +165 -0
  178. package/src/dataforms-layouts/panel/style.scss +4 -0
  179. package/src/dataforms-layouts/regular/index.tsx +20 -23
  180. package/src/dataviews-layouts/grid/index.tsx +32 -5
  181. package/src/dataviews-layouts/grid/preview-size-picker.tsx +15 -13
  182. package/src/dataviews-layouts/grid/style.scss +3 -1
  183. package/src/dataviews-layouts/list/index.tsx +65 -31
  184. package/src/dataviews-layouts/list/style.scss +7 -3
  185. package/src/dataviews-layouts/table/column-header-menu.tsx +4 -0
  186. package/src/dataviews-layouts/table/index.tsx +27 -1
  187. package/src/field-types/array.tsx +1 -1
  188. package/src/normalize-form-fields.ts +63 -17
  189. package/src/test/dataform.tsx +181 -3
  190. package/src/test/dataviews.tsx +38 -0
  191. package/src/test/filter-and-sort-data-view.js +123 -64
  192. package/src/test/normalize-form-fields.ts +247 -0
  193. package/src/types.ts +72 -6
  194. package/tsconfig.tsbuildinfo +1 -1
@@ -9,173 +9,22 @@ import clsx from 'clsx';
9
9
  import {
10
10
  __experimentalVStack as VStack,
11
11
  __experimentalHStack as HStack,
12
- __experimentalHeading as Heading,
13
- __experimentalSpacer as Spacer,
14
- Dropdown,
15
- Button,
16
12
  } from '@wordpress/components';
17
- import { sprintf, __, _x } from '@wordpress/i18n';
18
- import { useState, useMemo, useContext } from '@wordpress/element';
19
- import { closeSmall } from '@wordpress/icons';
13
+ import { useState, useContext } from '@wordpress/element';
20
14
 
21
15
  /**
22
16
  * Internal dependencies
23
17
  */
24
18
  import type {
25
- Form,
26
- FormField,
27
19
  FieldLayoutProps,
28
- NormalizedField,
20
+ NormalizedPanelLayout,
29
21
  SimpleFormField,
30
22
  } from '../../types';
31
23
  import DataFormContext from '../../components/dataform-context';
32
- import { DataFormLayout } from '../data-form-layout';
33
24
  import { isCombinedField } from '../is-combined-field';
34
-
35
- function DropdownHeader( {
36
- title,
37
- onClose,
38
- }: {
39
- title?: string;
40
- onClose: () => void;
41
- } ) {
42
- return (
43
- <VStack
44
- className="dataforms-layouts-panel__dropdown-header"
45
- spacing={ 4 }
46
- >
47
- <HStack alignment="center">
48
- { title && (
49
- <Heading level={ 2 } size={ 13 }>
50
- { title }
51
- </Heading>
52
- ) }
53
- <Spacer />
54
- { onClose && (
55
- <Button
56
- label={ __( 'Close' ) }
57
- icon={ closeSmall }
58
- onClick={ onClose }
59
- size="small"
60
- />
61
- ) }
62
- </HStack>
63
- </VStack>
64
- );
65
- }
66
-
67
- function PanelDropdown< Item >( {
68
- fieldDefinition,
69
- popoverAnchor,
70
- labelPosition = 'side',
71
- data,
72
- onChange,
73
- field,
74
- }: {
75
- fieldDefinition: NormalizedField< Item >;
76
- popoverAnchor: HTMLElement | null;
77
- labelPosition: 'side' | 'top' | 'none';
78
- data: Item;
79
- onChange: ( value: any ) => void;
80
- field: FormField;
81
- } ) {
82
- const fieldLabel = isCombinedField( field )
83
- ? field.label
84
- : fieldDefinition?.label;
85
- const form = useMemo( () => {
86
- if ( isCombinedField( field ) ) {
87
- return {
88
- type: 'regular' as const,
89
- fields: field.children.map( ( child ) => {
90
- if ( typeof child === 'string' ) {
91
- return {
92
- id: child,
93
- };
94
- }
95
- return child;
96
- } ),
97
- };
98
- }
99
- // If not explicit children return the field id itself.
100
- return {
101
- type: 'regular' as const,
102
- fields: [ { id: field.id } ],
103
- };
104
- }, [ field ] );
105
-
106
- // Memoize popoverProps to avoid returning a new object every time.
107
- const popoverProps = useMemo(
108
- () => ( {
109
- // Anchor the popover to the middle of the entire row so that it doesn't
110
- // move around when the label changes.
111
- anchor: popoverAnchor,
112
- placement: 'left-start',
113
- offset: 36,
114
- shift: true,
115
- } ),
116
- [ popoverAnchor ]
117
- );
118
-
119
- return (
120
- <Dropdown
121
- contentClassName="dataforms-layouts-panel__field-dropdown"
122
- popoverProps={ popoverProps }
123
- focusOnMount
124
- toggleProps={ {
125
- size: 'compact',
126
- variant: 'tertiary',
127
- tooltipPosition: 'middle left',
128
- } }
129
- renderToggle={ ( { isOpen, onToggle } ) => (
130
- <Button
131
- className="dataforms-layouts-panel__field-control"
132
- size="compact"
133
- variant={
134
- [ 'none', 'top' ].includes( labelPosition )
135
- ? 'link'
136
- : 'tertiary'
137
- }
138
- aria-expanded={ isOpen }
139
- aria-label={ sprintf(
140
- // translators: %s: Field name.
141
- _x( 'Edit %s', 'field' ),
142
- fieldLabel || ''
143
- ) }
144
- onClick={ onToggle }
145
- disabled={ fieldDefinition.readOnly === true }
146
- accessibleWhenDisabled
147
- >
148
- <fieldDefinition.render
149
- item={ data }
150
- field={ fieldDefinition }
151
- />
152
- </Button>
153
- ) }
154
- renderContent={ ( { onClose } ) => (
155
- <>
156
- <DropdownHeader title={ fieldLabel } onClose={ onClose } />
157
- <DataFormLayout
158
- data={ data }
159
- form={ form as Form }
160
- onChange={ onChange }
161
- >
162
- { ( FieldLayout, nestedField ) => (
163
- <FieldLayout
164
- key={ nestedField.id }
165
- data={ data }
166
- field={ nestedField }
167
- onChange={ onChange }
168
- hideLabelFromVision={
169
- ( form?.fields ?? [] ).length < 2
170
- }
171
- />
172
- ) }
173
- </DataFormLayout>
174
- </>
175
- ) }
176
- />
177
- );
178
- }
25
+ import { normalizeLayout } from '../../normalize-form-fields';
26
+ import PanelDropdown from './dropdown';
27
+ import PanelModal from './modal';
179
28
 
180
29
  export default function FormPanelField< Item >( {
181
30
  data,
@@ -183,20 +32,27 @@ export default function FormPanelField< Item >( {
183
32
  onChange,
184
33
  }: FieldLayoutProps< Item > ) {
185
34
  const { fields } = useContext( DataFormContext );
186
- const fieldDefinition = fields.find( ( fieldDef ) => {
187
- // Default to the first child if it is a combined field.
35
+ const fieldDefinition = fields.find( ( _field ) => {
36
+ // Default to the first simple child if it is a combined field.
188
37
  if ( isCombinedField( field ) ) {
189
- const children = field.children.filter(
38
+ const simpleChildren = field.children.filter(
190
39
  ( child ): child is string | SimpleFormField =>
191
40
  typeof child === 'string' || ! isCombinedField( child )
192
41
  );
42
+
43
+ if ( simpleChildren.length === 0 ) {
44
+ return false;
45
+ }
46
+
193
47
  const firstChildFieldId =
194
- typeof children[ 0 ] === 'string'
195
- ? children[ 0 ]
196
- : children[ 0 ].id;
197
- return fieldDef.id === firstChildFieldId;
48
+ typeof simpleChildren[ 0 ] === 'string'
49
+ ? simpleChildren[ 0 ]
50
+ : simpleChildren[ 0 ].id;
51
+
52
+ return _field.id === firstChildFieldId;
198
53
  }
199
- return fieldDef.id === field.id;
54
+
55
+ return _field.id === field.id;
200
56
  } );
201
57
 
202
58
  // Use internal state instead of a ref to make sure that the component
@@ -209,7 +65,12 @@ export default function FormPanelField< Item >( {
209
65
  return null;
210
66
  }
211
67
 
212
- const labelPosition = field.labelPosition ?? 'side';
68
+ const layout: NormalizedPanelLayout = normalizeLayout( {
69
+ ...field.layout,
70
+ type: 'panel',
71
+ } ) as NormalizedPanelLayout;
72
+
73
+ const labelPosition = layout.labelPosition;
213
74
  const labelClassName = clsx(
214
75
  'dataforms-layouts-panel__field-label',
215
76
  `dataforms-layouts-panel__field-label--label-position-${ labelPosition }`
@@ -218,6 +79,26 @@ export default function FormPanelField< Item >( {
218
79
  ? field.label
219
80
  : fieldDefinition?.label;
220
81
 
82
+ const renderedControl =
83
+ layout.openAs === 'modal' ? (
84
+ <PanelModal
85
+ field={ field }
86
+ fieldDefinition={ fieldDefinition }
87
+ data={ data }
88
+ onChange={ onChange }
89
+ labelPosition={ labelPosition }
90
+ />
91
+ ) : (
92
+ <PanelDropdown
93
+ field={ field }
94
+ popoverAnchor={ popoverAnchor }
95
+ fieldDefinition={ fieldDefinition }
96
+ data={ data }
97
+ onChange={ onChange }
98
+ labelPosition={ labelPosition }
99
+ />
100
+ );
101
+
221
102
  if ( labelPosition === 'top' ) {
222
103
  return (
223
104
  <VStack className="dataforms-layouts-panel__field" spacing={ 0 }>
@@ -228,14 +109,7 @@ export default function FormPanelField< Item >( {
228
109
  { fieldLabel }
229
110
  </div>
230
111
  <div className="dataforms-layouts-panel__field-control">
231
- <PanelDropdown
232
- field={ field }
233
- popoverAnchor={ popoverAnchor }
234
- fieldDefinition={ fieldDefinition }
235
- data={ data }
236
- onChange={ onChange }
237
- labelPosition={ labelPosition }
238
- />
112
+ { renderedControl }
239
113
  </div>
240
114
  </VStack>
241
115
  );
@@ -244,14 +118,7 @@ export default function FormPanelField< Item >( {
244
118
  if ( labelPosition === 'none' ) {
245
119
  return (
246
120
  <div className="dataforms-layouts-panel__field">
247
- <PanelDropdown
248
- field={ field }
249
- popoverAnchor={ popoverAnchor }
250
- fieldDefinition={ fieldDefinition }
251
- data={ data }
252
- onChange={ onChange }
253
- labelPosition={ labelPosition }
254
- />
121
+ { renderedControl }
255
122
  </div>
256
123
  );
257
124
  }
@@ -264,14 +131,7 @@ export default function FormPanelField< Item >( {
264
131
  >
265
132
  <div className={ labelClassName }>{ fieldLabel }</div>
266
133
  <div className="dataforms-layouts-panel__field-control">
267
- <PanelDropdown
268
- field={ field }
269
- popoverAnchor={ popoverAnchor }
270
- fieldDefinition={ fieldDefinition }
271
- data={ data }
272
- onChange={ onChange }
273
- labelPosition={ labelPosition }
274
- />
134
+ { renderedControl }
275
135
  </div>
276
136
  </HStack>
277
137
  );
@@ -0,0 +1,165 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ __experimentalHStack as HStack,
6
+ __experimentalSpacer as Spacer,
7
+ Button,
8
+ Modal,
9
+ } from '@wordpress/components';
10
+ import { __, sprintf, _x } from '@wordpress/i18n';
11
+ import { useState, useMemo } from '@wordpress/element';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import type { Form, FormField, NormalizedField } from '../../types';
17
+ import { DataFormLayout } from '../data-form-layout';
18
+ import { isCombinedField } from '../is-combined-field';
19
+ import { DEFAULT_LAYOUT } from '../../normalize-form-fields';
20
+
21
+ function ModalContent< Item >( {
22
+ data,
23
+ form,
24
+ fieldLabel,
25
+ onChange,
26
+ onClose,
27
+ }: {
28
+ data: Item;
29
+ form: Form;
30
+ fieldLabel: string;
31
+ onChange: ( data: Partial< Item > ) => void;
32
+ onClose: () => void;
33
+ } ) {
34
+ const [ changes, setChanges ] = useState< Partial< Item > >( {} );
35
+
36
+ const onApply = () => {
37
+ onChange( changes );
38
+ onClose();
39
+ };
40
+
41
+ const handleOnChange = ( value: Partial< Item > ) => {
42
+ setChanges( ( prev ) => ( { ...prev, ...value } ) );
43
+ };
44
+
45
+ // Merge original data with local changes for display
46
+ const displayData = { ...data, ...changes };
47
+
48
+ return (
49
+ <Modal
50
+ className="dataforms-layouts-panel__modal"
51
+ onRequestClose={ onClose }
52
+ isFullScreen={ false }
53
+ title={ fieldLabel }
54
+ size="medium"
55
+ >
56
+ <DataFormLayout
57
+ data={ displayData }
58
+ form={ form }
59
+ onChange={ handleOnChange }
60
+ >
61
+ { ( FieldLayout, nestedField ) => (
62
+ <FieldLayout
63
+ key={ nestedField.id }
64
+ data={ displayData }
65
+ field={ nestedField }
66
+ onChange={ handleOnChange }
67
+ hideLabelFromVision={
68
+ ( form?.fields ?? [] ).length < 2
69
+ }
70
+ />
71
+ ) }
72
+ </DataFormLayout>
73
+ <HStack
74
+ className="dataforms-layouts-panel__modal-footer"
75
+ spacing={ 3 }
76
+ >
77
+ <Spacer />
78
+ <Button
79
+ variant="tertiary"
80
+ onClick={ onClose }
81
+ __next40pxDefaultSize
82
+ >
83
+ { __( 'Cancel' ) }
84
+ </Button>
85
+ <Button
86
+ variant="primary"
87
+ onClick={ onApply }
88
+ __next40pxDefaultSize
89
+ >
90
+ { __( 'Apply' ) }
91
+ </Button>
92
+ </HStack>
93
+ </Modal>
94
+ );
95
+ }
96
+
97
+ function PanelModal< Item >( {
98
+ fieldDefinition,
99
+ labelPosition,
100
+ data,
101
+ onChange,
102
+ field,
103
+ }: {
104
+ fieldDefinition: NormalizedField< Item >;
105
+ labelPosition: 'side' | 'top' | 'none';
106
+ data: Item;
107
+ onChange: ( value: any ) => void;
108
+ field: FormField;
109
+ } ) {
110
+ const [ isOpen, setIsOpen ] = useState( false );
111
+
112
+ const fieldLabel = isCombinedField( field )
113
+ ? field.label
114
+ : fieldDefinition?.label;
115
+
116
+ const form: Form = useMemo(
117
+ (): Form => ( {
118
+ layout: DEFAULT_LAYOUT,
119
+ fields: isCombinedField( field )
120
+ ? field.children
121
+ : // If not explicit children return the field id itself.
122
+ [ { id: field.id } ],
123
+ } ),
124
+ [ field ]
125
+ );
126
+
127
+ return (
128
+ <>
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 ) }
144
+ disabled={ fieldDefinition.readOnly === true }
145
+ accessibleWhenDisabled
146
+ >
147
+ <fieldDefinition.render
148
+ item={ data }
149
+ field={ fieldDefinition }
150
+ />
151
+ </Button>
152
+ { isOpen && (
153
+ <ModalContent
154
+ data={ data }
155
+ form={ form as Form }
156
+ fieldLabel={ fieldLabel ?? '' }
157
+ onChange={ onChange }
158
+ onClose={ () => setIsOpen( false ) }
159
+ />
160
+ ) }
161
+ </>
162
+ );
163
+ }
164
+
165
+ export default PanelModal;
@@ -52,6 +52,10 @@
52
52
  margin-bottom: $grid-unit-20;
53
53
  }
54
54
 
55
+ .dataforms-layouts-panel__modal-footer {
56
+ margin-top: $grid-unit-20;
57
+ }
58
+
55
59
  .components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown {
56
60
  z-index: z-index(".components-popover.components-dropdown__content.dataforms-layouts-panel__field-dropdown");
57
61
  }
@@ -17,10 +17,15 @@ import {
17
17
  /**
18
18
  * Internal dependencies
19
19
  */
20
- import type { Form, FieldLayoutProps } from '../../types';
20
+ import type {
21
+ Form,
22
+ FieldLayoutProps,
23
+ NormalizedRegularLayout,
24
+ } from '../../types';
21
25
  import DataFormContext from '../../components/dataform-context';
22
26
  import { DataFormLayout } from '../data-form-layout';
23
27
  import { isCombinedField } from '../is-combined-field';
28
+ import { DEFAULT_LAYOUT, normalizeLayout } from '../../normalize-form-fields';
24
29
 
25
30
  function Header( { title }: { title: string } ) {
26
31
  return (
@@ -43,26 +48,13 @@ export default function FormRegularField< Item >( {
43
48
  }: FieldLayoutProps< Item > ) {
44
49
  const { fields } = useContext( DataFormContext );
45
50
 
46
- const form = useMemo( () => {
47
- if ( isCombinedField( field ) ) {
48
- return {
49
- fields: field.children.map( ( child ) => {
50
- if ( typeof child === 'string' ) {
51
- return {
52
- id: child,
53
- };
54
- }
55
- return child;
56
- } ),
57
- type: 'regular' as const,
58
- };
59
- }
60
-
61
- return {
62
- type: 'regular' as const,
63
- fields: [],
64
- };
65
- }, [ field ] );
51
+ const form: Form = useMemo(
52
+ (): Form => ( {
53
+ layout: DEFAULT_LAYOUT,
54
+ fields: isCombinedField( field ) ? field.children : [],
55
+ } ),
56
+ [ field ]
57
+ );
66
58
 
67
59
  if ( isCombinedField( field ) ) {
68
60
  return (
@@ -72,14 +64,19 @@ export default function FormRegularField< Item >( {
72
64
  ) }
73
65
  <DataFormLayout
74
66
  data={ data }
75
- form={ form as Form }
67
+ form={ form }
76
68
  onChange={ onChange }
77
69
  />
78
70
  </>
79
71
  );
80
72
  }
81
73
 
82
- const labelPosition = field.labelPosition ?? 'top';
74
+ const layout: NormalizedRegularLayout = normalizeLayout( {
75
+ ...field.layout,
76
+ type: 'regular',
77
+ } ) as NormalizedRegularLayout;
78
+
79
+ const labelPosition = layout.labelPosition;
83
80
  const fieldDefinition = fields.find(
84
81
  ( fieldDef ) => fieldDef.id === field.id
85
82
  );
@@ -13,6 +13,7 @@ import {
13
13
  Spinner,
14
14
  Flex,
15
15
  FlexItem,
16
+ Tooltip,
16
17
  privateApis as componentsPrivateApis,
17
18
  } from '@wordpress/components';
18
19
  import { __, sprintf } from '@wordpress/i18n';
@@ -64,6 +65,7 @@ interface GridItemProps< Item > {
64
65
  config: {
65
66
  sizes: string;
66
67
  };
68
+ posinset?: number;
67
69
  }
68
70
 
69
71
  function GridItem< Item >( {
@@ -83,8 +85,14 @@ function GridItem< Item >( {
83
85
  badgeFields,
84
86
  hasBulkActions,
85
87
  config,
88
+ posinset,
86
89
  }: GridItemProps< Item > ) {
87
- const { showTitle = true, showMedia = true, showDescription = true } = view;
90
+ const {
91
+ showTitle = true,
92
+ showMedia = true,
93
+ showDescription = true,
94
+ infiniteScrollEnabled,
95
+ } = view;
88
96
  const hasBulkAction = useHasAPossibleBulkAction( actions, item );
89
97
  const id = getItemId( item );
90
98
  const instanceId = useInstanceId( GridItem );
@@ -117,6 +125,7 @@ function GridItem< Item >( {
117
125
  };
118
126
  }
119
127
  }
128
+ const { paginationInfo } = useContext( DataViewsContext );
120
129
 
121
130
  return (
122
131
  <VStack
@@ -139,6 +148,11 @@ function GridItem< Item >( {
139
148
  );
140
149
  }
141
150
  } }
151
+ role={ infiniteScrollEnabled ? 'article' : undefined }
152
+ aria-setsize={
153
+ infiniteScrollEnabled ? paginationInfo.totalItems : undefined
154
+ }
155
+ aria-posinset={ posinset }
142
156
  >
143
157
  { showMedia && renderedMediaField && (
144
158
  <ItemClickWrapper
@@ -227,9 +241,11 @@ function GridItem< Item >( {
227
241
  direction="row"
228
242
  >
229
243
  <>
230
- <FlexItem className="dataviews-view-grid__field-name">
231
- { field.header }
232
- </FlexItem>
244
+ <Tooltip text={ field.label }>
245
+ <FlexItem className="dataviews-view-grid__field-name">
246
+ { field.header }
247
+ </FlexItem>
248
+ </Tooltip>
233
249
  <FlexItem
234
250
  className="dataviews-view-grid__field-value"
235
251
  style={ { maxHeight: 'none' } }
@@ -322,6 +338,8 @@ function ViewGrid< Item >( {
322
338
  }, new Map< string, typeof data >() )
323
339
  : null;
324
340
 
341
+ const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
342
+
325
343
  return (
326
344
  <>
327
345
  {
@@ -409,8 +427,9 @@ function ViewGrid< Item >( {
409
427
  } }
410
428
  aria-busy={ isLoading }
411
429
  ref={ resizeObserverRef }
430
+ role={ isInfiniteScroll ? 'feed' : undefined }
412
431
  >
413
- { data.map( ( item ) => {
432
+ { data.map( ( item, index ) => {
414
433
  return (
415
434
  <GridItem
416
435
  key={ getItemId( item ) }
@@ -432,6 +451,9 @@ function ViewGrid< Item >( {
432
451
  config={ {
433
452
  sizes: size,
434
453
  } }
454
+ posinset={
455
+ isInfiniteScroll ? index + 1 : undefined
456
+ }
435
457
  />
436
458
  );
437
459
  } ) }
@@ -451,6 +473,11 @@ function ViewGrid< Item >( {
451
473
  </div>
452
474
  )
453
475
  }
476
+ { hasData && isLoading && (
477
+ <p className="dataviews-loading-more">
478
+ <Spinner />
479
+ </p>
480
+ ) }
454
481
  </>
455
482
  );
456
483
  }
@@ -12,6 +12,14 @@ import DataViewsContext from '../../components/dataviews-context';
12
12
  import type { ViewGrid } from '../../types';
13
13
 
14
14
  const imageSizes = [
15
+ {
16
+ value: 120,
17
+ breakpoint: 1,
18
+ },
19
+ {
20
+ value: 170,
21
+ breakpoint: 1,
22
+ },
15
23
  {
16
24
  value: 230,
17
25
  breakpoint: 1,
@@ -34,24 +42,18 @@ export default function PreviewSizePicker() {
34
42
  const context = useContext( DataViewsContext );
35
43
  const view = context.view as ViewGrid;
36
44
 
37
- if ( context.containerWidth < 588 ) {
38
- return null;
39
- }
40
-
41
45
  const breakValues = imageSizes.filter( ( size ) => {
42
46
  return context.containerWidth >= size.breakpoint;
43
47
  } );
44
48
 
49
+ const layoutPreviewSize = view.layout?.previewSize ?? 230; // Default to the third smallest size if no preview size is set.
45
50
  // If the container has resized and the set preview size is no longer available,
46
- // we reset it to the next smallest size.
47
- const previewSizeToUse = view.layout?.previewSize
48
- ? breakValues
49
- .map( ( size, index ) => ( { ...size, index } ) )
50
- .filter(
51
- ( size ) => size.value <= ( view.layout?.previewSize ?? 0 ) // We know the view.layout?.previewSize exists at this point but the linter doesn't seem to.
52
- )
53
- .sort( ( a, b ) => b.value - a.value )[ 0 ].index
54
- : 0;
51
+ // we reset it to the next smallest size, or the smallest available size.
52
+ const previewSizeToUse =
53
+ breakValues
54
+ .map( ( size, index ) => ( { ...size, index } ) )
55
+ .filter( ( size ) => size.value <= layoutPreviewSize )
56
+ .sort( ( a, b ) => b.value - a.value )[ 0 ]?.index ?? 0;
55
57
 
56
58
  const marks = breakValues.map( ( size, index ) => {
57
59
  return {