@wordpress/dataviews 15.0.0 → 16.0.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 (67) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/build/components/dataform-controls/datetime.cjs.map +2 -2
  3. package/build/components/dataviews-context/index.cjs.map +2 -2
  4. package/build/components/dataviews-layouts/index.cjs +9 -0
  5. package/build/components/dataviews-layouts/index.cjs.map +3 -3
  6. package/build/components/dataviews-layouts/picker-activity/index.cjs +304 -0
  7. package/build/components/dataviews-layouts/picker-activity/index.cjs.map +7 -0
  8. package/build/components/dataviews-layouts/table/use-scroll-state.cjs.map +1 -1
  9. package/build/components/dataviews-layouts/utils/item-click-wrapper.cjs.map +2 -2
  10. package/build/components/dataviews-view-config/index.cjs +1 -0
  11. package/build/components/dataviews-view-config/index.cjs.map +2 -2
  12. package/build/constants.cjs +3 -0
  13. package/build/constants.cjs.map +2 -2
  14. package/build/hooks/use-form-validity.cjs.map +1 -1
  15. package/build/types/dataviews.cjs.map +1 -1
  16. package/build-module/components/dataform-controls/datetime.mjs.map +2 -2
  17. package/build-module/components/dataviews-context/index.mjs.map +2 -2
  18. package/build-module/components/dataviews-layouts/index.mjs +11 -1
  19. package/build-module/components/dataviews-layouts/index.mjs.map +2 -2
  20. package/build-module/components/dataviews-layouts/picker-activity/index.mjs +273 -0
  21. package/build-module/components/dataviews-layouts/picker-activity/index.mjs.map +7 -0
  22. package/build-module/components/dataviews-layouts/table/use-scroll-state.mjs.map +1 -1
  23. package/build-module/components/dataviews-layouts/utils/item-click-wrapper.mjs.map +2 -2
  24. package/build-module/components/dataviews-view-config/index.mjs +1 -0
  25. package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
  26. package/build-module/constants.mjs +2 -0
  27. package/build-module/constants.mjs.map +2 -2
  28. package/build-module/hooks/use-form-validity.mjs.map +1 -1
  29. package/build-style/style-rtl.css +199 -13
  30. package/build-style/style.css +199 -13
  31. package/build-types/components/dataviews-context/index.d.ts +2 -2
  32. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  33. package/build-types/components/dataviews-layouts/index.d.ts +8 -0
  34. package/build-types/components/dataviews-layouts/index.d.ts.map +1 -1
  35. package/build-types/components/dataviews-layouts/picker-activity/index.d.ts +3 -0
  36. package/build-types/components/dataviews-layouts/picker-activity/index.d.ts.map +1 -0
  37. package/build-types/components/dataviews-layouts/table/use-scroll-state.d.ts +5 -9
  38. package/build-types/components/dataviews-layouts/table/use-scroll-state.d.ts.map +1 -1
  39. package/build-types/components/dataviews-layouts/utils/item-click-wrapper.d.ts.map +1 -1
  40. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  41. package/build-types/constants.d.ts +1 -0
  42. package/build-types/constants.d.ts.map +1 -1
  43. package/build-types/dataform/stories/index.story.d.ts +1 -0
  44. package/build-types/dataform/stories/index.story.d.ts.map +1 -1
  45. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  46. package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
  47. package/build-types/types/dataviews.d.ts +16 -2
  48. package/build-types/types/dataviews.d.ts.map +1 -1
  49. package/build-wp/index.js +1088 -820
  50. package/package.json +24 -21
  51. package/src/components/dataform-controls/datetime.tsx +1 -1
  52. package/src/components/dataviews-context/index.ts +4 -2
  53. package/src/components/dataviews-layouts/index.ts +10 -0
  54. package/src/components/dataviews-layouts/picker-activity/index.tsx +359 -0
  55. package/src/components/dataviews-layouts/picker-activity/style.scss +227 -0
  56. package/src/components/dataviews-layouts/table/use-scroll-state.ts +6 -6
  57. package/src/components/dataviews-layouts/utils/item-click-wrapper.tsx +1 -3
  58. package/src/components/dataviews-view-config/index.tsx +1 -0
  59. package/src/constants.ts +1 -0
  60. package/src/dataform/stories/content.story.tsx +1 -1
  61. package/src/dataform/stories/index.story.tsx +1 -0
  62. package/src/dataviews/stories/index.story.tsx +1 -0
  63. package/src/dataviews-picker/stories/index.story.tsx +6 -0
  64. package/src/dataviews-picker/test/dataviews-picker.tsx +5 -0
  65. package/src/hooks/use-form-validity.ts +2 -2
  66. package/src/style.scss +1 -0
  67. package/src/types/dataviews.ts +21 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/dataviews",
3
- "version": "15.0.0",
3
+ "version": "16.0.1",
4
4
  "description": "DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.).",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -48,43 +48,46 @@
48
48
  },
49
49
  "./build-style/": "./build-style/"
50
50
  },
51
- "react-native": "src/index",
52
51
  "types": "build-types",
53
52
  "sideEffects": false,
54
53
  "dependencies": {
55
54
  "@ariakit/react": "^0.4.21",
56
- "@wordpress/base-styles": "^9.0.0",
57
- "@wordpress/components": "^34.0.0",
58
- "@wordpress/compose": "^8.0.0",
59
- "@wordpress/data": "^10.47.0",
60
- "@wordpress/date": "^5.47.0",
61
- "@wordpress/deprecated": "^4.47.0",
62
- "@wordpress/element": "^7.0.0",
63
- "@wordpress/i18n": "^6.20.0",
64
- "@wordpress/icons": "^13.2.0",
65
- "@wordpress/keycodes": "^4.47.0",
66
- "@wordpress/primitives": "^4.47.0",
67
- "@wordpress/private-apis": "^1.47.0",
68
- "@wordpress/ui": "^0.14.0",
69
- "@wordpress/warning": "^3.47.0",
55
+ "@types/react": "^18.3.27",
56
+ "@wordpress/base-styles": "^10.0.1",
57
+ "@wordpress/components": "^35.0.1",
58
+ "@wordpress/compose": "^8.1.1",
59
+ "@wordpress/data": "^10.48.1",
60
+ "@wordpress/date": "^5.48.1",
61
+ "@wordpress/deprecated": "^4.48.1",
62
+ "@wordpress/element": "^8.0.1",
63
+ "@wordpress/i18n": "^6.21.1",
64
+ "@wordpress/icons": "^14.0.1",
65
+ "@wordpress/keycodes": "^4.48.1",
66
+ "@wordpress/primitives": "^4.48.1",
67
+ "@wordpress/private-apis": "^1.48.1",
68
+ "@wordpress/ui": "^0.15.1",
69
+ "@wordpress/warning": "^3.48.1",
70
70
  "clsx": "^2.1.1",
71
- "colord": "^2.7.0",
71
+ "colord": "^2.9.3",
72
72
  "date-fns": "^4.1.0",
73
- "deepmerge": "4.3.1",
73
+ "deepmerge": "^4.3.1",
74
74
  "fast-deep-equal": "^3.1.3",
75
75
  "remove-accents": "^0.5.0"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@storybook/addon-docs": "^10.2.8",
79
79
  "@storybook/react-vite": "^10.2.8",
80
+ "@testing-library/dom": "^10.4.1",
80
81
  "@testing-library/jest-dom": "^6.9.1",
82
+ "@testing-library/react": "^16.3.2",
83
+ "@testing-library/user-event": "^14.6.1",
81
84
  "@types/jest": "^29.5.14",
82
85
  "esbuild": "^0.27.2",
83
86
  "storybook": "^10.2.8"
84
87
  },
85
88
  "peerDependencies": {
86
- "react": "^19.2.4",
87
- "react-dom": "^19.2.4"
89
+ "react": "^18.0.0",
90
+ "react-dom": "^18.0.0"
88
91
  },
89
92
  "publishConfig": {
90
93
  "access": "public"
@@ -92,5 +95,5 @@
92
95
  "scripts": {
93
96
  "build:wp": "node build.cjs"
94
97
  },
95
- "gitHead": "d653c5fd6161571a0c2ebde28553d6e25624eacc"
98
+ "gitHead": "99df7432c5c7cb83ba41146fd1f57f3c19004305"
96
99
  }
@@ -54,7 +54,7 @@ function CalendarDateTimeControl< Item >( {
54
54
  const inputControlRef = useRef< HTMLInputElement >( null );
55
55
  const validationTimeoutRef =
56
56
  useRef< ReturnType< typeof setTimeout > >( undefined );
57
- const previousFocusRef = useRef< Element >( null );
57
+ const previousFocusRef = useRef< Element | null >( null );
58
58
 
59
59
  const { minConstraint, maxConstraint, disabledMatchers } =
60
60
  useDisabledDateMatchers( isValid, parseDateTime );
@@ -46,8 +46,10 @@ type DataViewsContextType< Item > = {
46
46
  ) => ReactElement;
47
47
  isItemClickable: ( item: Item ) => boolean;
48
48
  containerWidth: number;
49
- containerRef: React.RefObject< HTMLDivElement | null >;
50
- resizeObserverRef: React.Ref< HTMLDivElement | null >;
49
+ containerRef: React.MutableRefObject< HTMLDivElement | null >;
50
+ resizeObserverRef:
51
+ | ( ( element?: HTMLDivElement | null ) => void )
52
+ | React.RefObject< HTMLDivElement >;
51
53
  defaultLayouts: NormalizedSupportedLayouts;
52
54
  filters: NormalizedFilter[];
53
55
  isShowingFilter: boolean;
@@ -19,6 +19,7 @@ import ViewList from './list';
19
19
  import ViewActivity from './activity';
20
20
  import ViewPickerGrid from './picker-grid';
21
21
  import ViewPickerTable from './picker-table';
22
+ import ViewPickerActivity from './picker-activity';
22
23
  import {
23
24
  LAYOUT_GRID,
24
25
  LAYOUT_LIST,
@@ -26,6 +27,7 @@ import {
26
27
  LAYOUT_ACTIVITY,
27
28
  LAYOUT_PICKER_GRID,
28
29
  LAYOUT_PICKER_TABLE,
30
+ LAYOUT_PICKER_ACTIVITY,
29
31
  } from '../../constants';
30
32
  import DensityPicker from './utils/density-picker';
31
33
  import GridConfigOptions from './utils/grid-config-options';
@@ -75,4 +77,12 @@ export const VIEW_LAYOUTS = [
75
77
  viewConfigOptions: DensityPicker,
76
78
  isPicker: true,
77
79
  },
80
+ {
81
+ type: LAYOUT_PICKER_ACTIVITY,
82
+ label: __( 'Activity' ),
83
+ component: ViewPickerActivity,
84
+ icon: scheduled,
85
+ viewConfigOptions: DensityPicker,
86
+ isPicker: true,
87
+ },
78
88
  ];
@@ -0,0 +1,359 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ReactNode } from 'react';
5
+ import clsx from 'clsx';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { Spinner, Composite } from '@wordpress/components';
11
+ import { useContext, useMemo, useRef } from '@wordpress/element';
12
+ import { useInstanceId } from '@wordpress/compose';
13
+ import { __, sprintf } from '@wordpress/i18n';
14
+ import { Stack, VisuallyHidden } from '@wordpress/ui';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import DataViewsContext from '../../dataviews-context';
20
+ import { useIsMultiselectPicker } from '../../dataviews-picker-footer';
21
+ import getDataByGroup from '../utils/get-data-by-group';
22
+ import { useIntersectionObserver } from '../utils/use-infinite-scroll';
23
+ import type {
24
+ NormalizedField,
25
+ ViewPickerActivity as ViewPickerActivityType,
26
+ ViewPickerActivityProps,
27
+ } from '../../../types';
28
+ import type { SetSelection } from '../../../types/private';
29
+
30
+ function isDefined< T >( item: T | undefined ): item is T {
31
+ return !! item;
32
+ }
33
+
34
+ interface PickerActivityItemProps< Item > {
35
+ view: ViewPickerActivityType;
36
+ multiselect?: boolean;
37
+ selection: string[];
38
+ onChangeSelection: SetSelection;
39
+ getItemId: ( item: Item ) => string;
40
+ item: Item;
41
+ titleField?: NormalizedField< Item >;
42
+ mediaField?: NormalizedField< Item >;
43
+ descriptionField?: NormalizedField< Item >;
44
+ otherFields: NormalizedField< Item >[];
45
+ posinset?: number;
46
+ setsize?: number;
47
+ }
48
+
49
+ function PickerActivityItem< Item >( {
50
+ view,
51
+ multiselect,
52
+ selection,
53
+ onChangeSelection,
54
+ getItemId,
55
+ item,
56
+ titleField,
57
+ mediaField,
58
+ descriptionField,
59
+ otherFields,
60
+ posinset,
61
+ setsize,
62
+ }: PickerActivityItemProps< Item > ) {
63
+ const elementRef = useRef< HTMLButtonElement >( null );
64
+ useIntersectionObserver( elementRef, posinset );
65
+ const { showTitle = true, showMedia = true, showDescription = true } = view;
66
+ const id = getItemId( item );
67
+ const isSelected = selection.includes( id );
68
+ const density = view.layout?.density ?? 'balanced';
69
+
70
+ const mediaContent =
71
+ showMedia && density !== 'compact' && mediaField?.render ? (
72
+ <mediaField.render
73
+ item={ item }
74
+ field={ mediaField }
75
+ config={ {
76
+ sizes: density === 'comfortable' ? '32px' : '24px',
77
+ } }
78
+ />
79
+ ) : null;
80
+
81
+ const renderedMediaField = (
82
+ <div className="dataviews-view-picker-activity__item-type-icon">
83
+ { mediaContent || (
84
+ <span
85
+ className="dataviews-view-picker-activity__item-bullet"
86
+ aria-hidden="true"
87
+ />
88
+ ) }
89
+ </div>
90
+ );
91
+
92
+ const renderedTitleField =
93
+ showTitle && titleField?.render ? (
94
+ <titleField.render item={ item } field={ titleField } />
95
+ ) : null;
96
+
97
+ const renderedDescriptionField =
98
+ showDescription && descriptionField?.render ? (
99
+ <descriptionField.render item={ item } field={ descriptionField } />
100
+ ) : null;
101
+
102
+ const verticalGap = useMemo( () => {
103
+ switch ( density ) {
104
+ case 'comfortable':
105
+ return 'md';
106
+ default:
107
+ return 'sm';
108
+ }
109
+ }, [ density ] );
110
+
111
+ return (
112
+ <Composite.Item
113
+ ref={ elementRef }
114
+ role="option"
115
+ aria-label={
116
+ titleField
117
+ ? titleField.getValue( { item } ) || undefined
118
+ : undefined
119
+ }
120
+ aria-posinset={ posinset }
121
+ aria-setsize={ setsize }
122
+ aria-selected={ isSelected }
123
+ className={ clsx(
124
+ 'dataviews-view-picker-activity__item',
125
+ density === 'compact' && 'is-compact',
126
+ density === 'balanced' && 'is-balanced',
127
+ density === 'comfortable' && 'is-comfortable',
128
+ isSelected && 'is-selected'
129
+ ) }
130
+ onClick={ () => {
131
+ if ( isSelected ) {
132
+ onChangeSelection(
133
+ selection.filter( ( itemId ) => id !== itemId )
134
+ );
135
+ } else {
136
+ const newSelection = multiselect
137
+ ? [ ...selection, id ]
138
+ : [ id ];
139
+ onChangeSelection( newSelection );
140
+ }
141
+ } }
142
+ render={ <div /> }
143
+ >
144
+ <Stack direction="row" gap="lg" justify="start" align="flex-start">
145
+ <Stack
146
+ direction="column"
147
+ gap="xs"
148
+ align="center"
149
+ className="dataviews-view-picker-activity__item-type"
150
+ >
151
+ { renderedMediaField }
152
+ </Stack>
153
+ <Stack
154
+ direction="column"
155
+ gap={ verticalGap }
156
+ align="flex-start"
157
+ className="dataviews-view-picker-activity__item-content"
158
+ >
159
+ { renderedTitleField && (
160
+ <div className="dataviews-view-picker-activity__item-title">
161
+ { renderedTitleField }
162
+ </div>
163
+ ) }
164
+ { renderedDescriptionField && (
165
+ <div className="dataviews-view-picker-activity__item-description">
166
+ { renderedDescriptionField }
167
+ </div>
168
+ ) }
169
+ <div className="dataviews-view-picker-activity__item-fields">
170
+ { otherFields.map( ( field ) => (
171
+ <div
172
+ key={ field.id }
173
+ className="dataviews-view-picker-activity__item-field"
174
+ >
175
+ <VisuallyHidden
176
+ render={ <span /> }
177
+ className="dataviews-view-picker-activity__item-field-label"
178
+ >
179
+ { field.label }
180
+ </VisuallyHidden>
181
+ <span className="dataviews-view-picker-activity__item-field-value">
182
+ <field.render
183
+ item={ item }
184
+ field={ field }
185
+ />
186
+ </span>
187
+ </div>
188
+ ) ) }
189
+ </div>
190
+ </Stack>
191
+ </Stack>
192
+ </Composite.Item>
193
+ );
194
+ }
195
+
196
+ function PickerActivityGroup< Item >( {
197
+ groupName,
198
+ groupField,
199
+ showLabel = true,
200
+ children,
201
+ }: {
202
+ groupName: string;
203
+ groupField: NormalizedField< Item >;
204
+ showLabel?: boolean;
205
+ children: ReactNode;
206
+ } ) {
207
+ const headerId = useInstanceId(
208
+ PickerActivityGroup,
209
+ 'dataviews-view-picker-activity-group__header'
210
+ );
211
+ return (
212
+ <Stack
213
+ direction="column"
214
+ role="group"
215
+ aria-labelledby={ headerId }
216
+ className="dataviews-view-picker-activity-group"
217
+ >
218
+ <h3
219
+ className="dataviews-view-picker-activity-group__header"
220
+ id={ headerId }
221
+ >
222
+ { showLabel
223
+ ? sprintf(
224
+ // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022".
225
+ __( '%1$s: %2$s' ),
226
+ groupField.label,
227
+ groupName
228
+ )
229
+ : groupName }
230
+ </h3>
231
+ { children }
232
+ </Stack>
233
+ );
234
+ }
235
+
236
+ export default function ViewPickerActivity< Item >( {
237
+ data,
238
+ fields,
239
+ getItemId,
240
+ isLoading,
241
+ onChangeSelection,
242
+ selection,
243
+ view,
244
+ actions,
245
+ className,
246
+ empty,
247
+ }: ViewPickerActivityProps< Item > ) {
248
+ const { itemListLabel, paginationInfo } = useContext( DataViewsContext );
249
+ const isMultiselect = useIsMultiselectPicker( actions );
250
+
251
+ const titleField = fields.find(
252
+ ( field ) => field.id === view?.titleField
253
+ );
254
+ const mediaField = fields.find(
255
+ ( field ) => field.id === view?.mediaField
256
+ );
257
+ const descriptionField = fields.find(
258
+ ( field ) => field.id === view?.descriptionField
259
+ );
260
+ const otherFields = ( view?.fields ?? [] )
261
+ .map( ( fieldId ) => fields.find( ( f ) => fieldId === f.id ) )
262
+ .filter( isDefined );
263
+
264
+ const groupField = view.groupBy?.field
265
+ ? fields.find( ( f ) => f.id === view.groupBy?.field )
266
+ : null;
267
+ const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
268
+
269
+ const isInfiniteScroll =
270
+ ( view.infiniteScrollEnabled && ! dataByGroup ) ?? false;
271
+ const setsize = isInfiniteScroll ? paginationInfo?.totalItems : undefined;
272
+
273
+ const hasData = !! data?.length;
274
+ const isGrouped = !! ( groupField && dataByGroup );
275
+
276
+ const renderItem = ( item: Item ) => (
277
+ <PickerActivityItem
278
+ key={ getItemId( item ) }
279
+ view={ view }
280
+ multiselect={ isMultiselect }
281
+ selection={ selection }
282
+ onChangeSelection={ onChangeSelection }
283
+ getItemId={ getItemId }
284
+ item={ item }
285
+ titleField={ titleField }
286
+ mediaField={ mediaField }
287
+ descriptionField={ descriptionField }
288
+ otherFields={ otherFields }
289
+ posinset={ ( item as { position?: number } ).position }
290
+ setsize={ setsize }
291
+ />
292
+ );
293
+
294
+ if ( ! hasData ) {
295
+ return (
296
+ <div
297
+ className={ clsx( {
298
+ 'dataviews-loading': isLoading,
299
+ 'dataviews-no-results': ! isLoading,
300
+ } ) }
301
+ >
302
+ { isLoading ? (
303
+ <p>
304
+ <Spinner />
305
+ </p>
306
+ ) : (
307
+ empty
308
+ ) }
309
+ </div>
310
+ );
311
+ }
312
+
313
+ return (
314
+ <>
315
+ <Composite
316
+ virtualFocus
317
+ orientation="vertical"
318
+ role="listbox"
319
+ aria-multiselectable={ isMultiselect }
320
+ aria-label={ itemListLabel }
321
+ aria-busy={ isLoading }
322
+ render={
323
+ isGrouped ? (
324
+ <Stack direction="column" gap="sm" />
325
+ ) : undefined
326
+ }
327
+ className={ clsx(
328
+ 'dataviews-view-picker-activity',
329
+ className
330
+ ) }
331
+ >
332
+ { isGrouped && dataByGroup
333
+ ? Array.from( dataByGroup.entries() ).map(
334
+ ( [ groupName, groupItems ]: [
335
+ string,
336
+ Item[],
337
+ ] ) => (
338
+ <PickerActivityGroup< Item >
339
+ key={ groupName }
340
+ groupName={ groupName }
341
+ groupField={ groupField }
342
+ showLabel={
343
+ view.groupBy?.showLabel !== false
344
+ }
345
+ >
346
+ { groupItems.map( renderItem ) }
347
+ </PickerActivityGroup>
348
+ )
349
+ )
350
+ : data.map( renderItem ) }
351
+ </Composite>
352
+ { isLoading && (
353
+ <p className="dataviews-loading-more">
354
+ <Spinner />
355
+ </p>
356
+ ) }
357
+ </>
358
+ );
359
+ }