@wordpress/dataviews 4.10.0 → 4.11.1

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 (119) hide show
  1. package/CHANGELOG.md +19 -9
  2. package/LICENSE.md +1 -1
  3. package/README.md +309 -175
  4. package/build/components/dataviews/index.js +12 -1
  5. package/build/components/dataviews/index.js.map +1 -1
  6. package/build/components/dataviews-context/index.js +2 -1
  7. package/build/components/dataviews-context/index.js.map +1 -1
  8. package/build/components/dataviews-filters/add-filter.js +35 -30
  9. package/build/components/dataviews-filters/add-filter.js.map +1 -1
  10. package/build/components/dataviews-filters/index.js +3 -1
  11. package/build/components/dataviews-filters/index.js.map +1 -1
  12. package/build/components/dataviews-item-actions/index.js +24 -19
  13. package/build/components/dataviews-item-actions/index.js.map +1 -1
  14. package/build/components/dataviews-layout/index.js +2 -0
  15. package/build/components/dataviews-layout/index.js.map +1 -1
  16. package/build/components/dataviews-view-config/index.js +48 -43
  17. package/build/components/dataviews-view-config/index.js.map +1 -1
  18. package/build/dataviews-layouts/grid/index.js +12 -5
  19. package/build/dataviews-layouts/grid/index.js.map +1 -1
  20. package/build/dataviews-layouts/grid/preview-size-picker.js +22 -25
  21. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  22. package/build/dataviews-layouts/list/index.js +27 -20
  23. package/build/dataviews-layouts/list/index.js.map +1 -1
  24. package/build/dataviews-layouts/table/column-header-menu.js +102 -99
  25. package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
  26. package/build/dataviews-layouts/table/column-primary.js +7 -3
  27. package/build/dataviews-layouts/table/column-primary.js.map +1 -1
  28. package/build/dataviews-layouts/table/index.js +4 -0
  29. package/build/dataviews-layouts/table/index.js.map +1 -1
  30. package/build/types.js.map +1 -1
  31. package/build-module/components/dataviews/index.js +12 -1
  32. package/build-module/components/dataviews/index.js.map +1 -1
  33. package/build-module/components/dataviews-context/index.js +2 -1
  34. package/build-module/components/dataviews-context/index.js.map +1 -1
  35. package/build-module/components/dataviews-filters/add-filter.js +36 -31
  36. package/build-module/components/dataviews-filters/add-filter.js.map +1 -1
  37. package/build-module/components/dataviews-filters/index.js +3 -1
  38. package/build-module/components/dataviews-filters/index.js.map +1 -1
  39. package/build-module/components/dataviews-item-actions/index.js +24 -19
  40. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  41. package/build-module/components/dataviews-layout/index.js +2 -0
  42. package/build-module/components/dataviews-layout/index.js.map +1 -1
  43. package/build-module/components/dataviews-view-config/index.js +49 -44
  44. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  45. package/build-module/dataviews-layouts/grid/index.js +14 -7
  46. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  47. package/build-module/dataviews-layouts/grid/preview-size-picker.js +22 -25
  48. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  49. package/build-module/dataviews-layouts/list/index.js +27 -20
  50. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  51. package/build-module/dataviews-layouts/table/column-header-menu.js +102 -99
  52. package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
  53. package/build-module/dataviews-layouts/table/column-primary.js +7 -3
  54. package/build-module/dataviews-layouts/table/column-primary.js.map +1 -1
  55. package/build-module/dataviews-layouts/table/index.js +4 -0
  56. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  57. package/build-module/types.js.map +1 -1
  58. package/build-style/style-rtl.css +45 -55
  59. package/build-style/style.css +45 -55
  60. package/build-types/components/dataviews/index.d.ts +2 -1
  61. package/build-types/components/dataviews/index.d.ts.map +1 -1
  62. package/build-types/components/dataviews-context/index.d.ts +2 -0
  63. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  64. package/build-types/components/dataviews-filters/add-filter.d.ts +3 -2
  65. package/build-types/components/dataviews-filters/add-filter.d.ts.map +1 -1
  66. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  67. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  68. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  69. package/build-types/dataviews-layouts/grid/index.d.ts.map +1 -1
  70. package/build-types/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  71. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  72. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  73. package/build-types/dataviews-layouts/table/column-primary.d.ts +2 -1
  74. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  75. package/build-types/dataviews-layouts/table/index.d.ts +1 -1
  76. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  77. package/build-types/test/dataform.d.ts +2 -0
  78. package/build-types/test/dataform.d.ts.map +1 -0
  79. package/build-types/test/dataviews.d.ts +2 -0
  80. package/build-types/test/dataviews.d.ts.map +1 -0
  81. package/build-types/test/normalize-fields.d.ts +2 -0
  82. package/build-types/test/normalize-fields.d.ts.map +1 -0
  83. package/build-types/test/validation.d.ts +2 -0
  84. package/build-types/test/validation.d.ts.map +1 -0
  85. package/build-types/types.d.ts +5 -0
  86. package/build-types/types.d.ts.map +1 -1
  87. package/build-wp/index.js +1209 -999
  88. package/package.json +13 -12
  89. package/src/components/dataviews/index.tsx +15 -1
  90. package/src/components/dataviews/style.scss +0 -1
  91. package/src/components/dataviews-context/index.ts +3 -0
  92. package/src/components/dataviews-filters/add-filter.tsx +43 -39
  93. package/src/components/dataviews-filters/index.tsx +1 -1
  94. package/src/components/dataviews-footer/style.scss +0 -3
  95. package/src/components/dataviews-item-actions/index.tsx +25 -27
  96. package/src/components/dataviews-layout/index.tsx +2 -0
  97. package/src/components/dataviews-view-config/index.tsx +52 -43
  98. package/src/components/dataviews-view-config/style.scss +0 -1
  99. package/src/dataviews-layouts/grid/index.tsx +17 -5
  100. package/src/dataviews-layouts/grid/preview-size-picker.tsx +25 -30
  101. package/src/dataviews-layouts/grid/style.scss +24 -33
  102. package/src/dataviews-layouts/list/index.tsx +35 -27
  103. package/src/dataviews-layouts/list/style.scss +5 -5
  104. package/src/dataviews-layouts/table/column-header-menu.tsx +152 -148
  105. package/src/dataviews-layouts/table/column-primary.tsx +7 -0
  106. package/src/dataviews-layouts/table/index.tsx +10 -0
  107. package/src/dataviews-layouts/table/style.scss +0 -1
  108. package/src/test/dataform.tsx +348 -0
  109. package/src/test/dataviews.tsx +380 -0
  110. package/src/types.ts +6 -0
  111. package/tsconfig.json +14 -4
  112. package/tsconfig.tsbuildinfo +1 -1
  113. package/build/components/form-field-visibility/index.js +0 -32
  114. package/build/components/form-field-visibility/index.js.map +0 -1
  115. package/build-module/components/form-field-visibility/index.js +0 -26
  116. package/build-module/components/form-field-visibility/index.js.map +0 -1
  117. package/build-types/components/form-field-visibility/index.d.ts +0 -11
  118. package/build-types/components/form-field-visibility/index.d.ts.map +0 -1
  119. package/src/components/form-field-visibility/index.tsx +0 -32
@@ -40,6 +40,7 @@ interface TableColumnFieldProps< Item > {
40
40
  interface TableRowProps< Item > {
41
41
  hasBulkActions: boolean;
42
42
  item: Item;
43
+ level?: number;
43
44
  actions: Action< Item >[];
44
45
  fields: NormalizedField< Item >[];
45
46
  id: string;
@@ -75,6 +76,7 @@ function TableColumnField< Item >( {
75
76
  function TableRow< Item >( {
76
77
  hasBulkActions,
77
78
  item,
79
+ level,
78
80
  actions,
79
81
  fields,
80
82
  id,
@@ -160,6 +162,7 @@ function TableRow< Item >( {
160
162
  <td>
161
163
  <ColumnPrimary
162
164
  item={ item }
165
+ level={ level }
163
166
  titleField={ showTitle ? titleField : undefined }
164
167
  mediaField={ showMedia ? mediaField : undefined }
165
168
  descriptionField={
@@ -210,6 +213,7 @@ function ViewTable< Item >( {
210
213
  data,
211
214
  fields,
212
215
  getItemId,
216
+ getItemLevel,
213
217
  isLoading = false,
214
218
  onChangeView,
215
219
  onChangeSelection,
@@ -375,6 +379,12 @@ function ViewTable< Item >( {
375
379
  <TableRow
376
380
  key={ getItemId( item ) }
377
381
  item={ item }
382
+ level={
383
+ view.showLevels &&
384
+ typeof getItemLevel === 'function'
385
+ ? getItemLevel( item )
386
+ : undefined
387
+ }
378
388
  hasBulkActions={ hasBulkActions }
379
389
  actions={ actions }
380
390
  fields={ fields }
@@ -203,7 +203,6 @@
203
203
  }
204
204
  }
205
205
 
206
- /* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */
207
206
  @container (max-width: 430px) {
208
207
  .dataviews-view-table tr td:first-child,
209
208
  .dataviews-view-table tr th:first-child {
@@ -0,0 +1,348 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import Dataform from '../components/dataform/index';
11
+
12
+ const noop = () => {};
13
+
14
+ const fields = [
15
+ {
16
+ id: 'title',
17
+ label: 'Title',
18
+ type: 'text' as const,
19
+ },
20
+ {
21
+ id: 'order',
22
+ label: 'Order',
23
+ type: 'integer' as const,
24
+ },
25
+ {
26
+ id: 'author',
27
+ label: 'Author',
28
+ type: 'integer' as const,
29
+ elements: [
30
+ { value: 1, label: 'Jane' },
31
+ { value: 2, label: 'John' },
32
+ ],
33
+ },
34
+ ];
35
+
36
+ const form = {
37
+ fields: [ 'title', 'order', 'author' ],
38
+ };
39
+
40
+ const data = {
41
+ title: 'Hello World',
42
+ author: 1,
43
+ order: 1,
44
+ };
45
+
46
+ const fieldsSelector = {
47
+ title: {
48
+ view: () =>
49
+ screen.getByRole( 'button', {
50
+ name: /edit title/i,
51
+ } ),
52
+ edit: () =>
53
+ screen.getByRole( 'textbox', {
54
+ name: /title/i,
55
+ } ),
56
+ },
57
+ author: {
58
+ view: () =>
59
+ screen.getByRole( 'button', {
60
+ name: /edit author/i,
61
+ } ),
62
+ edit: () =>
63
+ screen.queryByRole( 'combobox', {
64
+ name: /author/i,
65
+ } ),
66
+ },
67
+ order: {
68
+ view: () =>
69
+ screen.getByRole( 'button', {
70
+ name: /edit order/i,
71
+ } ),
72
+ edit: () =>
73
+ screen.getByRole( 'spinbutton', {
74
+ name: /order/i,
75
+ } ),
76
+ },
77
+ };
78
+
79
+ describe( 'DataForm component', () => {
80
+ describe( 'in regular mode', () => {
81
+ it( 'should display fields', () => {
82
+ render(
83
+ <Dataform
84
+ onChange={ noop }
85
+ fields={ fields }
86
+ form={ form }
87
+ data={ data }
88
+ />
89
+ );
90
+
91
+ expect( fieldsSelector.title.edit() ).toBeInTheDocument();
92
+ expect( fieldsSelector.order.edit() ).toBeInTheDocument();
93
+ expect( fieldsSelector.author.edit() ).toBeInTheDocument();
94
+ } );
95
+
96
+ it( 'should render custom Edit component', () => {
97
+ const fieldsWithCustomEditComponent = fields.map( ( field ) => {
98
+ if ( field.id === 'title' ) {
99
+ return {
100
+ ...field,
101
+ Edit: () => {
102
+ return <span>This is the Title Field</span>;
103
+ },
104
+ };
105
+ }
106
+ return field;
107
+ } );
108
+
109
+ render(
110
+ <Dataform
111
+ onChange={ noop }
112
+ fields={ fieldsWithCustomEditComponent }
113
+ form={ form }
114
+ data={ data }
115
+ />
116
+ );
117
+
118
+ const titleField = screen.getByText( 'This is the Title Field' );
119
+ expect( titleField ).toBeInTheDocument();
120
+ } );
121
+
122
+ it( 'should call onChange with the correct value for each typed character', async () => {
123
+ const onChange = jest.fn();
124
+ render(
125
+ <Dataform
126
+ onChange={ onChange }
127
+ fields={ fields }
128
+ form={ form }
129
+ data={ { ...data, title: '' } }
130
+ />
131
+ );
132
+
133
+ const titleInput = fieldsSelector.title.edit();
134
+ const user = userEvent.setup();
135
+ await user.clear( titleInput );
136
+ expect( titleInput ).toHaveValue( '' );
137
+ const newValue = 'Hello folks!';
138
+ await user.type( titleInput, newValue );
139
+ expect( onChange ).toHaveBeenCalledTimes( newValue.length );
140
+ for ( let i = 0; i < newValue.length; i++ ) {
141
+ expect( onChange ).toHaveBeenNthCalledWith( i + 1, {
142
+ title: newValue[ i ],
143
+ } );
144
+ }
145
+ } );
146
+
147
+ it( 'should wrap fields in HStack when labelPosition is set to side', async () => {
148
+ const { container } = render(
149
+ <Dataform
150
+ onChange={ noop }
151
+ fields={ fields }
152
+ form={ { ...form, labelPosition: 'side' } }
153
+ data={ data }
154
+ />
155
+ );
156
+
157
+ expect(
158
+ // It is used here to ensure that the fields are wrapped in HStack. This happens when the labelPosition is set to side.
159
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
160
+ container.querySelectorAll( "[data-wp-component='HStack']" )
161
+ ).toHaveLength( 3 );
162
+ } );
163
+
164
+ it( 'should render combined fields correctly', async () => {
165
+ const formWithCombinedFields = {
166
+ fields: [
167
+ 'order',
168
+ {
169
+ id: 'title',
170
+ children: [ 'title', 'author' ],
171
+ label: "Title and author's name",
172
+ },
173
+ ],
174
+ };
175
+
176
+ render(
177
+ <Dataform
178
+ onChange={ noop }
179
+ fields={ fields }
180
+ form={ formWithCombinedFields }
181
+ data={ data }
182
+ />
183
+ );
184
+
185
+ expect(
186
+ screen.getByText( "Title and author's name" )
187
+ ).toBeInTheDocument();
188
+ } );
189
+ } );
190
+
191
+ describe( 'in panel mode', () => {
192
+ const formPanelMode = {
193
+ ...form,
194
+ type: 'panel' as const,
195
+ };
196
+ it( 'should display fields', async () => {
197
+ render(
198
+ <Dataform
199
+ onChange={ noop }
200
+ fields={ fields }
201
+ form={ formPanelMode }
202
+ data={ data }
203
+ />
204
+ );
205
+
206
+ const user = await userEvent.setup();
207
+
208
+ for ( const field of Object.values( fieldsSelector ) ) {
209
+ const button = field.view();
210
+ await user.click( button );
211
+ expect( field.edit() ).toBeInTheDocument();
212
+ }
213
+ } );
214
+
215
+ it( 'should call onChange with the correct value for each typed character', async () => {
216
+ const onChange = jest.fn();
217
+ render(
218
+ <Dataform
219
+ onChange={ onChange }
220
+ fields={ fields }
221
+ form={ formPanelMode }
222
+ data={ { ...data, title: '' } }
223
+ />
224
+ );
225
+
226
+ const titleButton = fieldsSelector.title.view();
227
+ const user = await userEvent.setup();
228
+ await user.click( titleButton );
229
+ const input = fieldsSelector.title.edit();
230
+ expect( input ).toHaveValue( '' );
231
+ const newValue = 'Hello folks!';
232
+ await user.type( input, newValue );
233
+ expect( onChange ).toHaveBeenCalledTimes( newValue.length );
234
+ for ( let i = 0; i < newValue.length; i++ ) {
235
+ expect( onChange ).toHaveBeenNthCalledWith( i + 1, {
236
+ title: newValue[ i ],
237
+ } );
238
+ }
239
+ } );
240
+
241
+ it( 'should wrap fields in HStack when labelPosition is set to side', async () => {
242
+ const { container } = render(
243
+ <Dataform
244
+ onChange={ noop }
245
+ fields={ fields }
246
+ form={ { ...formPanelMode, labelPosition: 'side' } }
247
+ data={ data }
248
+ />
249
+ );
250
+
251
+ expect(
252
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
253
+ container.querySelectorAll( "[data-wp-component='HStack']" )
254
+ ).toHaveLength( 3 );
255
+ } );
256
+
257
+ it( 'should render combined fields correctly', async () => {
258
+ const formWithCombinedFields = {
259
+ ...formPanelMode,
260
+ fields: [
261
+ 'order',
262
+ {
263
+ id: 'title',
264
+ children: [ 'title', 'author' ],
265
+ label: "Title and author's name",
266
+ },
267
+ ],
268
+ };
269
+
270
+ render(
271
+ <Dataform
272
+ onChange={ noop }
273
+ fields={ fields }
274
+ form={ formWithCombinedFields }
275
+ data={ data }
276
+ />
277
+ );
278
+
279
+ const button = screen.getByRole( 'button', {
280
+ name: /edit title and author's name/i,
281
+ } );
282
+ const user = await userEvent.setup();
283
+ await user.click( button );
284
+ expect( fieldsSelector.title.edit() ).toBeInTheDocument();
285
+ expect( fieldsSelector.author.edit() ).toBeInTheDocument();
286
+ } );
287
+
288
+ it( 'should render custom render component', async () => {
289
+ const fieldsWithCustomRenderFunction = fields.map( ( field ) => {
290
+ return {
291
+ ...field,
292
+ render: () => {
293
+ return <span>This is the { field.id } field</span>;
294
+ },
295
+ };
296
+ } );
297
+
298
+ render(
299
+ <Dataform
300
+ onChange={ noop }
301
+ fields={ fieldsWithCustomRenderFunction }
302
+ form={ formPanelMode }
303
+ data={ data }
304
+ />
305
+ );
306
+
307
+ const titleField = screen.getByText( 'This is the title field' );
308
+ const orderField = screen.getByText( 'This is the order field' );
309
+ const authorField = screen.getByText( 'This is the author field' );
310
+ expect( titleField ).toBeInTheDocument();
311
+ expect( orderField ).toBeInTheDocument();
312
+ expect( authorField ).toBeInTheDocument();
313
+ } );
314
+
315
+ it( 'should render custom Edit component', async () => {
316
+ const fieldsWithTitleCustomEditComponent = fields.map(
317
+ ( field ) => {
318
+ if ( field.id === 'title' ) {
319
+ return {
320
+ ...field,
321
+ Edit: () => {
322
+ return <span>This is the Title Field</span>;
323
+ },
324
+ };
325
+ }
326
+ return field;
327
+ }
328
+ );
329
+
330
+ render(
331
+ <Dataform
332
+ onChange={ noop }
333
+ fields={ fieldsWithTitleCustomEditComponent }
334
+ form={ formPanelMode }
335
+ data={ data }
336
+ />
337
+ );
338
+
339
+ const titleField = screen.getByText( data.title );
340
+ const user = await userEvent.setup();
341
+ await user.click( titleField );
342
+ const titleEditField = screen.getByText(
343
+ 'This is the Title Field'
344
+ );
345
+ expect( titleEditField ).toBeInTheDocument();
346
+ } );
347
+ } );
348
+ } );