@wordpress/dataviews 17.0.1 → 17.1.2-next.v.202607070741.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 (96) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/build/components/dataform-controls/select.cjs +0 -1
  3. package/build/components/dataform-controls/select.cjs.map +2 -2
  4. package/build/components/dataform-controls/toggle-group.cjs +0 -1
  5. package/build/components/dataform-controls/toggle-group.cjs.map +2 -2
  6. package/build/components/dataform-controls/utils/relative-date-control.cjs +0 -1
  7. package/build/components/dataform-controls/utils/relative-date-control.cjs.map +2 -2
  8. package/build/components/dataviews-footer/index.cjs +6 -2
  9. package/build/components/dataviews-footer/index.cjs.map +2 -2
  10. package/build/components/dataviews-layouts/grid/preview-size-picker.cjs +0 -1
  11. package/build/components/dataviews-layouts/grid/preview-size-picker.cjs.map +2 -2
  12. package/build/components/dataviews-layouts/list/index.cjs +13 -1
  13. package/build/components/dataviews-layouts/list/index.cjs.map +2 -2
  14. package/build/components/dataviews-layouts/utils/density-picker.cjs +0 -1
  15. package/build/components/dataviews-layouts/utils/density-picker.cjs.map +2 -2
  16. package/build/components/dataviews-layouts/utils/preview-size-picker.cjs +0 -1
  17. package/build/components/dataviews-layouts/utils/preview-size-picker.cjs.map +2 -2
  18. package/build/components/dataviews-pagination/index.cjs +12 -13
  19. package/build/components/dataviews-pagination/index.cjs.map +2 -2
  20. package/build/components/dataviews-view-config/index.cjs +0 -3
  21. package/build/components/dataviews-view-config/index.cjs.map +2 -2
  22. package/build/dataform/index.cjs.map +2 -2
  23. package/build/dataviews/index.cjs.map +2 -2
  24. package/build/dataviews-picker/index.cjs.map +2 -2
  25. package/build/hooks/use-infinite-scroll.cjs +2 -1
  26. package/build/hooks/use-infinite-scroll.cjs.map +2 -2
  27. package/build-module/components/dataform-controls/select.mjs +0 -1
  28. package/build-module/components/dataform-controls/select.mjs.map +2 -2
  29. package/build-module/components/dataform-controls/toggle-group.mjs +0 -1
  30. package/build-module/components/dataform-controls/toggle-group.mjs.map +2 -2
  31. package/build-module/components/dataform-controls/utils/relative-date-control.mjs +0 -1
  32. package/build-module/components/dataform-controls/utils/relative-date-control.mjs.map +2 -2
  33. package/build-module/components/dataviews-footer/index.mjs +9 -3
  34. package/build-module/components/dataviews-footer/index.mjs.map +2 -2
  35. package/build-module/components/dataviews-layouts/grid/preview-size-picker.mjs +0 -1
  36. package/build-module/components/dataviews-layouts/grid/preview-size-picker.mjs.map +2 -2
  37. package/build-module/components/dataviews-layouts/list/index.mjs +13 -1
  38. package/build-module/components/dataviews-layouts/list/index.mjs.map +2 -2
  39. package/build-module/components/dataviews-layouts/utils/density-picker.mjs +0 -1
  40. package/build-module/components/dataviews-layouts/utils/density-picker.mjs.map +2 -2
  41. package/build-module/components/dataviews-layouts/utils/preview-size-picker.mjs +0 -1
  42. package/build-module/components/dataviews-layouts/utils/preview-size-picker.mjs.map +2 -2
  43. package/build-module/components/dataviews-pagination/index.mjs +10 -12
  44. package/build-module/components/dataviews-pagination/index.mjs.map +2 -2
  45. package/build-module/components/dataviews-view-config/index.mjs +0 -3
  46. package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
  47. package/build-module/dataform/index.mjs.map +2 -2
  48. package/build-module/dataviews/index.mjs.map +2 -2
  49. package/build-module/dataviews-picker/index.mjs.map +2 -2
  50. package/build-module/hooks/use-infinite-scroll.mjs +2 -1
  51. package/build-module/hooks/use-infinite-scroll.mjs.map +2 -2
  52. package/build-style/style-rtl.css +9 -2
  53. package/build-style/style.css +9 -2
  54. package/build-types/components/dataform-controls/select.d.ts.map +1 -1
  55. package/build-types/components/dataform-controls/toggle-group.d.ts.map +1 -1
  56. package/build-types/components/dataform-controls/utils/relative-date-control.d.ts.map +1 -1
  57. package/build-types/components/dataviews-footer/index.d.ts +1 -1
  58. package/build-types/components/dataviews-footer/index.d.ts.map +1 -1
  59. package/build-types/components/dataviews-layouts/grid/preview-size-picker.d.ts.map +1 -1
  60. package/build-types/components/dataviews-layouts/list/index.d.ts.map +1 -1
  61. package/build-types/components/dataviews-layouts/utils/density-picker.d.ts.map +1 -1
  62. package/build-types/components/dataviews-layouts/utils/preview-size-picker.d.ts.map +1 -1
  63. package/build-types/components/dataviews-pagination/index.d.ts +6 -1
  64. package/build-types/components/dataviews-pagination/index.d.ts.map +1 -1
  65. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  66. package/build-types/dataform/index.d.ts +5 -0
  67. package/build-types/dataform/index.d.ts.map +1 -1
  68. package/build-types/dataviews/index.d.ts +5 -0
  69. package/build-types/dataviews/index.d.ts.map +1 -1
  70. package/build-types/dataviews/stories/async-infinite-scroll.d.ts +8 -0
  71. package/build-types/dataviews/stories/async-infinite-scroll.d.ts.map +1 -0
  72. package/build-types/dataviews/stories/index.story.d.ts +19 -0
  73. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  74. package/build-types/dataviews-picker/index.d.ts +5 -0
  75. package/build-types/dataviews-picker/index.d.ts.map +1 -1
  76. package/build-types/hooks/use-infinite-scroll.d.ts.map +1 -1
  77. package/build-wp/index.js +1476 -1227
  78. package/package.json +17 -17
  79. package/src/components/dataform-controls/select.tsx +0 -1
  80. package/src/components/dataform-controls/toggle-group.tsx +0 -1
  81. package/src/components/dataform-controls/utils/relative-date-control.tsx +0 -1
  82. package/src/components/dataviews-footer/index.tsx +27 -26
  83. package/src/components/dataviews-layouts/grid/preview-size-picker.tsx +0 -1
  84. package/src/components/dataviews-layouts/list/index.tsx +14 -2
  85. package/src/components/dataviews-layouts/utils/density-picker.tsx +0 -1
  86. package/src/components/dataviews-layouts/utils/preview-size-picker.tsx +0 -1
  87. package/src/components/dataviews-pagination/index.tsx +82 -78
  88. package/src/components/dataviews-view-config/index.tsx +0 -3
  89. package/src/components/dataviews-view-config/style.scss +0 -1
  90. package/src/dataform/index.tsx +5 -0
  91. package/src/dataviews/index.tsx +5 -0
  92. package/src/dataviews/stories/async-infinite-scroll.tsx +115 -0
  93. package/src/dataviews/stories/index.story.tsx +27 -0
  94. package/src/dataviews/style.scss +5 -1
  95. package/src/dataviews-picker/index.tsx +5 -0
  96. package/src/hooks/use-infinite-scroll.ts +10 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/dataviews",
3
- "version": "17.0.1",
3
+ "version": "17.1.2-next.v.202607070741.0+a51d59513",
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",
@@ -51,21 +51,21 @@
51
51
  "types": "build-types",
52
52
  "sideEffects": false,
53
53
  "dependencies": {
54
- "@ariakit/react": "^0.4.29",
55
- "@wordpress/base-styles": "^10.1.0",
56
- "@wordpress/components": "^36.0.1",
57
- "@wordpress/compose": "^8.2.0",
58
- "@wordpress/data": "^10.49.0",
59
- "@wordpress/date": "^5.49.0",
60
- "@wordpress/deprecated": "^4.49.0",
61
- "@wordpress/element": "^8.1.0",
62
- "@wordpress/i18n": "^6.22.0",
63
- "@wordpress/icons": "^15.0.0",
64
- "@wordpress/keycodes": "^4.49.0",
65
- "@wordpress/primitives": "^4.49.0",
66
- "@wordpress/private-apis": "^1.49.0",
67
- "@wordpress/ui": "^0.16.1",
68
- "@wordpress/warning": "^3.49.0",
54
+ "@ariakit/react": "^0.4.32",
55
+ "@wordpress/base-styles": "^10.3.1-next.v.202607070741.0+a51d59513",
56
+ "@wordpress/components": "^37.0.1-next.v.202607070741.0+a51d59513",
57
+ "@wordpress/compose": "^8.3.1-next.v.202607070741.0+a51d59513",
58
+ "@wordpress/data": "^10.50.1-next.v.202607070741.0+a51d59513",
59
+ "@wordpress/date": "^5.50.1-next.v.202607070741.0+a51d59513",
60
+ "@wordpress/deprecated": "^4.50.1-next.v.202607070741.0+a51d59513",
61
+ "@wordpress/element": "^8.2.1-next.v.202607070741.0+a51d59513",
62
+ "@wordpress/i18n": "^6.23.1-next.v.202607070741.0+a51d59513",
63
+ "@wordpress/icons": "^15.1.1-next.v.202607070741.0+a51d59513",
64
+ "@wordpress/keycodes": "^4.50.1-next.v.202607070741.0+a51d59513",
65
+ "@wordpress/primitives": "^4.50.1-next.v.202607070741.0+a51d59513",
66
+ "@wordpress/private-apis": "^1.50.1-next.v.202607070741.0+a51d59513",
67
+ "@wordpress/ui": "^0.18.1-next.v.202607070741.0+a51d59513",
68
+ "@wordpress/warning": "^3.50.1-next.v.202607070741.0+a51d59513",
69
69
  "clsx": "^2.1.1",
70
70
  "colord": "^2.9.3",
71
71
  "date-fns": "^4.1.0",
@@ -100,5 +100,5 @@
100
100
  "scripts": {
101
101
  "build:wp": "node build.cjs"
102
102
  },
103
- "gitHead": "9a75283890dda96ae1d37197b5070fae8c9cf66f"
103
+ "gitHead": "f637726e370c8b23695ed9af82adbe171ad235d8"
104
104
  }
@@ -53,7 +53,6 @@ export default function Select< Item >( {
53
53
  help={ description }
54
54
  options={ elements }
55
55
  onChange={ onChangeControl }
56
- __next40pxDefaultSize
57
56
  hideLabelFromVision={ hideLabelFromVision }
58
57
  multiple={ isMultiple }
59
58
  disabled={ disabled }
@@ -55,7 +55,6 @@ export default function ToggleGroup< Item >( {
55
55
  required={ !! field.isValid?.required }
56
56
  markWhenOptional={ markWhenOptional }
57
57
  customValidity={ getCustomValidity( isValid, validity ) }
58
- __next40pxDefaultSize
59
58
  isBlock
60
59
  label={ field.label }
61
60
  help={ selectedOption?.description || field.description }
@@ -107,7 +107,6 @@ export default function RelativeDateControl< Item >( {
107
107
  />
108
108
  <SelectControl
109
109
  className="dataviews-controls__relative-date-unit"
110
- __next40pxDefaultSize
111
110
  label={ __( 'Unit' ) }
112
111
  value={ unit }
113
112
  options={ options }
@@ -13,7 +13,9 @@ import { Stack } from '@wordpress/ui';
13
13
  * Internal dependencies
14
14
  */
15
15
  import DataViewsContext from '../dataviews-context';
16
- import DataViewsPagination from '../dataviews-pagination';
16
+ import DataViewsPagination, {
17
+ hasPaginationControls,
18
+ } from '../dataviews-pagination';
17
19
  import {
18
20
  BulkActionsFooter,
19
21
  useSomeItemHasAPossibleBulkAction,
@@ -41,34 +43,33 @@ export default function DataViewsFooter() {
41
43
  useSomeItemHasAPossibleBulkAction( actions, data ) &&
42
44
  [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type );
43
45
 
44
- if (
45
- ! isRefreshing &&
46
- ( ! totalItems ||
47
- ! totalPages ||
48
- ( totalPages <= 1 && ! hasBulkActions ) )
49
- ) {
46
+ const hasPagination = hasPaginationControls( view, {
47
+ totalItems,
48
+ totalPages,
49
+ } );
50
+
51
+ if ( ! totalItems || ( ! hasBulkActions && ! hasPagination ) ) {
50
52
  return null;
51
53
  }
54
+
52
55
  return (
53
- ( !! totalItems || isRefreshing ) && (
54
- <div
55
- className="dataviews-footer"
56
- // @ts-ignore
57
- inert={ isRefreshing ? 'true' : undefined }
56
+ <div
57
+ className="dataviews-footer"
58
+ // @ts-ignore
59
+ inert={ isRefreshing ? 'true' : undefined }
60
+ >
61
+ <Stack
62
+ direction="row"
63
+ justify="end"
64
+ align="center"
65
+ className={ clsx( 'dataviews-footer__content', {
66
+ 'is-refreshing': isDelayedRefreshing,
67
+ } ) }
68
+ gap="sm"
58
69
  >
59
- <Stack
60
- direction="row"
61
- justify="end"
62
- align="center"
63
- className={ clsx( 'dataviews-footer__content', {
64
- 'is-refreshing': isDelayedRefreshing,
65
- } ) }
66
- gap="sm"
67
- >
68
- { hasBulkActions && <BulkActionsFooter /> }
69
- <DataViewsPagination />
70
- </Stack>
71
- </div>
72
- )
70
+ { hasBulkActions && <BulkActionsFooter /> }
71
+ <DataViewsPagination />
72
+ </Stack>
73
+ </div>
73
74
  );
74
75
  }
@@ -84,7 +84,6 @@ export default function PreviewSizePicker() {
84
84
 
85
85
  return (
86
86
  <RangeControl
87
- __next40pxDefaultSize
88
87
  showTooltip={ false }
89
88
  label={ __( 'Preview size' ) }
90
89
  value={ previewSizeToUse }
@@ -394,6 +394,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
394
394
  } = props;
395
395
  const baseId = useInstanceId( ViewList, 'view-list' );
396
396
  const isDelayedLoading = useDelayedLoading( !! isLoading );
397
+ const { paginationInfo } = useContext( DataViewsContext );
397
398
 
398
399
  const selectedItem = data?.findLast( ( item ) =>
399
400
  selection.includes( getItemId( item ) )
@@ -535,6 +536,11 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
535
536
  const dataByGroup =
536
537
  hasData && groupField ? getDataByGroup( data, groupField ) : null;
537
538
  const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
539
+ // Whether the server has more rows beyond the current window.
540
+ const hasMoreItems =
541
+ isInfiniteScroll &&
542
+ ( view.startPosition ?? 1 ) + ( view.perPage ?? 0 ) <
543
+ paginationInfo.totalItems;
538
544
  if ( ! hasData ) {
539
545
  return (
540
546
  <div
@@ -663,8 +669,14 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
663
669
  );
664
670
  } ) }
665
671
  </Composite>
666
- { isInfiniteScroll && isLoading && (
667
- <p className="dataviews-loading-more">
672
+ { ( hasMoreItems || ( isInfiniteScroll && isLoading ) ) && (
673
+ // Keep the spinner's height reserved while loading more so the
674
+ // scroll position doesn't bounce. Hidden, and silent to a11y,
675
+ // while idle.
676
+ <p
677
+ className="dataviews-loading-more"
678
+ aria-hidden={ ! isLoading }
679
+ >
668
680
  <Spinner />
669
681
  </p>
670
682
  ) }
@@ -29,7 +29,6 @@ export default function DensityPicker() {
29
29
  | ViewPickerGrid;
30
30
  return (
31
31
  <ToggleGroupControl
32
- size="__unstable-large"
33
32
  label={ __( 'Density' ) }
34
33
  value={ view.layout?.density || 'balanced' }
35
34
  onChange={ ( value ) => {
@@ -63,7 +63,6 @@ export default function PreviewSizePicker() {
63
63
 
64
64
  return (
65
65
  <RangeControl
66
- __next40pxDefaultSize
67
66
  showTooltip={ false }
68
67
  label={ __( 'Preview size' ) }
69
68
  value={ previewSizeToUse }
@@ -11,18 +11,28 @@ import { Stack } from '@wordpress/ui';
11
11
  * Internal dependencies
12
12
  */
13
13
  import DataViewsContext from '../dataviews-context';
14
+ import type { View } from '../../types';
15
+
16
+ export function hasPaginationControls(
17
+ view: View,
18
+ paginationInfo: { totalItems: number; totalPages: number }
19
+ ): boolean {
20
+ return (
21
+ ! view.infiniteScrollEnabled &&
22
+ paginationInfo.totalItems > 0 &&
23
+ paginationInfo.totalPages > 1
24
+ );
25
+ }
14
26
 
15
27
  export function DataViewsPagination() {
16
- const {
17
- view,
18
- onChangeView,
19
- paginationInfo: { totalItems = 0, totalPages },
20
- } = useContext( DataViewsContext );
28
+ const { view, onChangeView, paginationInfo } =
29
+ useContext( DataViewsContext );
21
30
 
22
- if ( ! totalItems || ! totalPages || view.infiniteScrollEnabled ) {
31
+ if ( ! hasPaginationControls( view, paginationInfo ) ) {
23
32
  return null;
24
33
  }
25
34
 
35
+ const { totalPages } = paginationInfo;
26
36
  const currentPage = view.page ?? 1;
27
37
  const pageSelectOptions = Array.from( Array( totalPages ) ).map(
28
38
  ( _, i ) => {
@@ -44,84 +54,78 @@ export function DataViewsPagination() {
44
54
  );
45
55
 
46
56
  return (
47
- !! totalItems &&
48
- totalPages !== 1 && (
57
+ <Stack
58
+ direction="row"
59
+ className="dataviews-pagination"
60
+ justify="end"
61
+ align="center"
62
+ gap="xl"
63
+ >
49
64
  <Stack
50
65
  direction="row"
51
- className="dataviews-pagination"
52
- justify="end"
66
+ justify="flex-start"
53
67
  align="center"
54
- gap="xl"
68
+ gap="xs"
69
+ className="dataviews-pagination__page-select"
55
70
  >
56
- <Stack
57
- direction="row"
58
- justify="flex-start"
59
- align="center"
60
- gap="xs"
61
- className="dataviews-pagination__page-select"
62
- >
63
- { createInterpolateElement(
64
- sprintf(
65
- // translators: 1: Current page number, 2: Total number of pages.
66
- _x(
67
- '<div>Page</div>%1$s<div>of %2$d</div>',
68
- 'paging'
69
- ),
70
- '<CurrentPage />',
71
- totalPages
71
+ { createInterpolateElement(
72
+ sprintf(
73
+ // translators: 1: Current page number, 2: Total number of pages.
74
+ _x( '<div>Page</div>%1$s<div>of %2$d</div>', 'paging' ),
75
+ '<CurrentPage />',
76
+ totalPages
77
+ ),
78
+ {
79
+ div: <div aria-hidden />,
80
+ // @ts-expect-error Tag injected via sprintf argument, not visible in format string.
81
+ CurrentPage: (
82
+ <SelectControl
83
+ aria-label={ __( 'Current page' ) }
84
+ value={ currentPage.toString() }
85
+ options={ pageSelectOptions }
86
+ onChange={ ( newValue ) => {
87
+ onChangeView( {
88
+ ...view,
89
+ page: +newValue,
90
+ } );
91
+ } }
92
+ size="small"
93
+ variant="minimal"
94
+ />
72
95
  ),
73
- {
74
- div: <div aria-hidden />,
75
- // @ts-expect-error — Tag injected via sprintf argument, not visible in format string.
76
- CurrentPage: (
77
- <SelectControl
78
- aria-label={ __( 'Current page' ) }
79
- value={ currentPage.toString() }
80
- options={ pageSelectOptions }
81
- onChange={ ( newValue ) => {
82
- onChangeView( {
83
- ...view,
84
- page: +newValue,
85
- } );
86
- } }
87
- size="small"
88
- variant="minimal"
89
- />
90
- ),
91
- }
92
- ) }
93
- </Stack>
94
- <Stack direction="row" gap="xs" align="center">
95
- <Button
96
- onClick={ () =>
97
- onChangeView( {
98
- ...view,
99
- page: currentPage - 1,
100
- } )
101
- }
102
- disabled={ currentPage === 1 }
103
- accessibleWhenDisabled
104
- label={ __( 'Previous page' ) }
105
- icon={ isRTL() ? next : previous }
106
- showTooltip
107
- size="compact"
108
- tooltipPosition="top"
109
- />
110
- <Button
111
- onClick={ () =>
112
- onChangeView( { ...view, page: currentPage + 1 } )
113
- }
114
- disabled={ currentPage >= totalPages }
115
- accessibleWhenDisabled
116
- label={ __( 'Next page' ) }
117
- icon={ isRTL() ? previous : next }
118
- showTooltip
119
- size="compact"
120
- tooltipPosition="top"
121
- />
122
- </Stack>
96
+ }
97
+ ) }
98
+ </Stack>
99
+ <Stack direction="row" gap="xs" align="center">
100
+ <Button
101
+ onClick={ () =>
102
+ onChangeView( {
103
+ ...view,
104
+ page: currentPage - 1,
105
+ } )
106
+ }
107
+ disabled={ currentPage === 1 }
108
+ accessibleWhenDisabled
109
+ label={ __( 'Previous page' ) }
110
+ icon={ isRTL() ? next : previous }
111
+ showTooltip
112
+ size="compact"
113
+ tooltipPosition="top"
114
+ />
115
+ <Button
116
+ onClick={ () =>
117
+ onChangeView( { ...view, page: currentPage + 1 } )
118
+ }
119
+ disabled={ currentPage >= totalPages }
120
+ accessibleWhenDisabled
121
+ label={ __( 'Next page' ) }
122
+ icon={ isRTL() ? previous : next }
123
+ showTooltip
124
+ size="compact"
125
+ tooltipPosition="top"
126
+ />
123
127
  </Stack>
124
- )
128
+ </Stack>
125
129
  );
126
130
  }
127
131
 
@@ -125,7 +125,6 @@ function SortFieldControl() {
125
125
 
126
126
  return (
127
127
  <SelectControl
128
- __next40pxDefaultSize
129
128
  label={ __( 'Sort by' ) }
130
129
  value={ view.sort?.field }
131
130
  options={ orderOptions }
@@ -160,7 +159,6 @@ function SortDirectionControl() {
160
159
  return (
161
160
  <ToggleGroupControl
162
161
  className="dataviews-view-config__sort-direction"
163
- __next40pxDefaultSize
164
162
  isBlock
165
163
  label={ __( 'Order' ) }
166
164
  value={ value }
@@ -214,7 +212,6 @@ function ItemsPerPageControl() {
214
212
 
215
213
  return (
216
214
  <ToggleGroupControl
217
- __next40pxDefaultSize
218
215
  isBlock
219
216
  label={ __( 'Items per page' ) }
220
217
  value={ view.perPage || 10 }
@@ -1,6 +1,5 @@
1
1
  .dataviews-view-config {
2
2
  width: 320px;
3
- /* stylelint-disable-next-line property-no-unknown -- the linter needs to be updated to accepted the container-type property */
4
3
  container-type: inline-size;
5
4
  font-size: var(--wpds-typography-font-size-md);
6
5
  line-height: var(--wpds-typography-line-height-sm);
@@ -12,6 +12,11 @@ import { DataFormProvider } from '../components/dataform-context';
12
12
  import { DataFormLayout } from '../components/dataform-layouts/data-form-layout';
13
13
  import normalizeForm from '../components/dataform-layouts/normalize-form';
14
14
 
15
+ /**
16
+ * `DataForm` renders an auto-generated form for viewing and editing the fields
17
+ * of a data item, driven by a fields and form layout configuration. Use it to
18
+ * edit items of a dataset, often alongside `DataViews`.
19
+ */
15
20
  export default function DataForm< Item >( {
16
21
  data,
17
22
  form,
@@ -310,6 +310,11 @@ function DataViews< Item >( {
310
310
  );
311
311
  }
312
312
 
313
+ /**
314
+ * `DataViews` renders a dataset using configurable layouts (table, grid, list)
315
+ * with built-in search, filtering, sorting, pagination, and actions. Use it to
316
+ * display and manage a collection of records.
317
+ */
313
318
  // Populate the DataViews sub components
314
319
  const DataViewsSubComponents = DataViews as typeof DataViews & {
315
320
  BulkActionToolbar: typeof BulkActionsFooter;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState, useMemo, useEffect } from '@wordpress/element';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import DataViews from '../index';
10
+ import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../../constants';
11
+ import filterSortAndPaginate from '../../utils/filter-sort-and-paginate';
12
+ import type { View } from '../../types';
13
+ import { actions, data as fixtureData, fields } from './fixtures';
14
+
15
+ // The fixtures only contain ~50 rows; repeat them (with unique ids) so there
16
+ // are enough rows to page through several times.
17
+ const data = Array.from( { length: 8 }, ( _, batch ) =>
18
+ fixtureData.map( ( item ) => ( {
19
+ ...item,
20
+ id: item.id + batch * 1000,
21
+ name: {
22
+ ...item.name,
23
+ title: `${ item.name.title } #${ batch + 1 }`,
24
+ },
25
+ } ) )
26
+ ).flat();
27
+
28
+ const PAGE = 20;
29
+ // Simulated per-page network latency.
30
+ const LOAD_DELAY_MS = 800;
31
+
32
+ /**
33
+ * A realistic network-backed infinite-scroll consumer: the hook advances
34
+ * `view.startPosition`, and the consumer fetches that window asynchronously
35
+ * (toggling `isLoading`) while reporting the true server total.
36
+ */
37
+ const AsyncInfiniteScroll = () => {
38
+ const [ view, setView ] = useState< View >( {
39
+ type: LAYOUT_LIST,
40
+ search: '',
41
+ startPosition: 1,
42
+ perPage: PAGE,
43
+ filters: [],
44
+ fields: [ 'satellites' ],
45
+ titleField: 'title',
46
+ descriptionField: 'description',
47
+ mediaField: 'image',
48
+ infiniteScrollEnabled: true,
49
+ } );
50
+
51
+ // The "server": how many rows have been delivered so far, and whether a
52
+ // request for the current window is in flight.
53
+ const [ loadedCount, setLoadedCount ] = useState( PAGE );
54
+ const [ isLoading, setIsLoading ] = useState( false );
55
+
56
+ const startPosition = view.startPosition ?? 1;
57
+ const perPage = view.perPage ?? PAGE;
58
+ // Rows required to fill the window the hook is currently asking for.
59
+ const needed = Math.min( startPosition - 1 + perPage, data.length );
60
+
61
+ useEffect( () => {
62
+ if ( needed <= loadedCount ) {
63
+ return undefined;
64
+ }
65
+ setIsLoading( true );
66
+ const timer = setTimeout( () => {
67
+ setLoadedCount( needed );
68
+ setIsLoading( false );
69
+ }, LOAD_DELAY_MS );
70
+ return () => clearTimeout( timer );
71
+ }, [ needed, loadedCount ] );
72
+
73
+ const loadedData = useMemo(
74
+ () => data.slice( 0, loadedCount ),
75
+ [ loadedCount ]
76
+ );
77
+ const { data: shownData, paginationInfo } = useMemo(
78
+ () => filterSortAndPaginate( loadedData, view, fields ),
79
+ [ loadedData, view ]
80
+ );
81
+ // Report the true server total (not just what's loaded) so infinite scroll
82
+ // keeps requesting until the dataset is exhausted.
83
+ const serverPaginationInfo = useMemo(
84
+ () => ( { ...paginationInfo, totalItems: data.length } ),
85
+ [ paginationInfo ]
86
+ );
87
+
88
+ return (
89
+ <div style={ { height: '100%' } }>
90
+ <style>{ `
91
+ .dataviews-wrapper {
92
+ height: 100%;
93
+ overflow: auto;
94
+ }
95
+ ` }</style>
96
+ <DataViews
97
+ getItemId={ ( item ) => item.id.toString() }
98
+ paginationInfo={ serverPaginationInfo }
99
+ data={ shownData }
100
+ view={ view }
101
+ fields={ fields }
102
+ onChangeView={ setView }
103
+ isLoading={ isLoading }
104
+ actions={ actions }
105
+ defaultLayouts={ {
106
+ [ LAYOUT_TABLE ]: true,
107
+ [ LAYOUT_GRID ]: true,
108
+ [ LAYOUT_LIST ]: true,
109
+ } }
110
+ />
111
+ </div>
112
+ );
113
+ };
114
+
115
+ export default AsyncInfiniteScroll;
@@ -13,6 +13,7 @@ import LayoutGridComponent from './layout-grid';
13
13
  import LayoutListComponent from './layout-list';
14
14
  import LayoutCustomComponent from './layout-custom';
15
15
  import InfiniteScrollComponent from './infinite-scroll';
16
+ import AsyncInfiniteScrollComponent from './async-infinite-scroll';
16
17
  import WithCardComponent from './with-card';
17
18
  import FreeCompositionComponent from './free-composition';
18
19
  import MinimalUIComponent from './minimal-ui';
@@ -24,6 +25,11 @@ const meta = {
24
25
  tags: [ 'manifest' ],
25
26
  title: 'DataViews/DataViews',
26
27
  component: DataViews,
28
+ // Temporary: Due to an upstream bug, render the root explicitly so the
29
+ // components manifest extractor can resolve props from the JSX.
30
+ //
31
+ // See: https://github.com/storybookjs/storybook/issues/34877
32
+ render: ( args ) => <DataViews { ...args } />,
27
33
  args: {
28
34
  containerHeight: 'auto',
29
35
  },
@@ -278,3 +284,24 @@ export const InfiniteScroll = {
278
284
  },
279
285
  },
280
286
  };
287
+
288
+ /**
289
+ * Infinite scroll where pages load asynchronously (with `isLoading`), like a
290
+ * real network-backed consumer that fetches one window at a time. Reproduces
291
+ * the scroll-position jump that the synchronous story does not.
292
+ */
293
+ export const AsyncInfiniteScroll = {
294
+ render: AsyncInfiniteScrollComponent,
295
+ parameters: {
296
+ // Fill the viewport so the list bottom is the window bottom.
297
+ containerHeight: 'calc(100vh - 2rem)',
298
+ },
299
+ argTypes: {
300
+ containerHeight: {
301
+ control: false,
302
+ table: {
303
+ disable: true,
304
+ },
305
+ },
306
+ },
307
+ };
@@ -17,7 +17,6 @@
17
17
  min-height: 0;
18
18
  box-sizing: border-box;
19
19
  scroll-padding-bottom: 64px;
20
- /* stylelint-disable-next-line property-no-unknown -- '@container' not globally permitted */
21
20
  container: dataviews-wrapper / inline-size;
22
21
  display: flex;
23
22
  flex-direction: column;
@@ -70,6 +69,11 @@
70
69
 
71
70
  .dataviews-loading-more {
72
71
  text-align: center;
72
+
73
+ // Hidden but keeps its box so the reserved height stays constant.
74
+ &[aria-hidden="true"] {
75
+ visibility: hidden;
76
+ }
73
77
  }
74
78
 
75
79
  @container (max-width: 430px) {
@@ -269,6 +269,11 @@ function DataViewsPicker< Item >( {
269
269
  );
270
270
  }
271
271
 
272
+ /**
273
+ * `DataViewsPicker` renders a dataset allowing users to select one or multiple
274
+ * items. It shares the layouts, search, and filtering of `DataViews` but is
275
+ * geared toward choosing items rather than managing them.
276
+ */
272
277
  // Populate the DataViews sub components
273
278
  const DataViewsPickerSubComponents =
274
279
  DataViewsPicker as typeof DataViewsPicker & {