@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,12 +9,12 @@ import type { ReactNode, ComponentProps, ReactElement } from 'react';
9
9
  import { __experimentalHStack as HStack } from '@wordpress/components';
10
10
  import {
11
11
  useContext,
12
+ useEffect,
12
13
  useMemo,
13
14
  useRef,
14
15
  useState,
15
- useEffect,
16
16
  } from '@wordpress/element';
17
- import { useResizeObserver } from '@wordpress/compose';
17
+ import { useResizeObserver, throttle } from '@wordpress/compose';
18
18
 
19
19
  /**
20
20
  * Internal dependencies
@@ -51,6 +51,7 @@ type DataViewsProps< Item > = {
51
51
  paginationInfo: {
52
52
  totalItems: number;
53
53
  totalPages: number;
54
+ infiniteScrollHandler?: () => void;
54
55
  };
55
56
  defaultLayouts: SupportedLayouts;
56
57
  selection?: string[];
@@ -65,7 +66,11 @@ type DataViewsProps< Item > = {
65
66
  header?: ReactNode;
66
67
  getItemLevel?: ( item: Item ) => number;
67
68
  children?: ReactNode;
68
- perPageSizes?: number[];
69
+ config?:
70
+ | false
71
+ | {
72
+ perPageSizes: number[];
73
+ };
69
74
  empty?: ReactNode;
70
75
  } & ( Item extends ItemWithId
71
76
  ? { getItemId?: ( item: Item ) => string }
@@ -85,7 +90,7 @@ function DefaultUI( {
85
90
  search = true,
86
91
  searchLabel = undefined,
87
92
  }: DefaultUIProps ) {
88
- const { isShowingFilter } = useContext( DataViewsContext );
93
+ const { isShowingFilter, config } = useContext( DataViewsContext );
89
94
  return (
90
95
  <>
91
96
  <HStack
@@ -102,14 +107,16 @@ function DefaultUI( {
102
107
  { search && <DataViewsSearch label={ searchLabel } /> }
103
108
  <FiltersToggle />
104
109
  </HStack>
105
- <HStack
106
- spacing={ 1 }
107
- expanded={ false }
108
- style={ { flexShrink: 0 } }
109
- >
110
- <DataViewsViewConfig />
111
- { header }
112
- </HStack>
110
+ { ( config || header ) && (
111
+ <HStack
112
+ spacing={ 1 }
113
+ expanded={ false }
114
+ style={ { flexShrink: 0 } }
115
+ >
116
+ config && <DataViewsViewConfig />
117
+ { header }
118
+ </HStack>
119
+ ) }
113
120
  </HStack>
114
121
  { isShowingFilter && (
115
122
  <DataViewsFilters className="dataviews-filters__container" />
@@ -140,9 +147,10 @@ function DataViews< Item >( {
140
147
  isItemClickable = defaultIsItemClickable,
141
148
  header,
142
149
  children,
143
- perPageSizes = [ 10, 20, 50, 100 ],
150
+ config = { perPageSizes: [ 10, 20, 50, 100 ] },
144
151
  empty,
145
152
  }: DataViewsProps< Item > ) {
153
+ const { infiniteScrollHandler } = paginationInfo;
146
154
  const containerRef = useRef< HTMLDivElement | null >( null );
147
155
  const [ containerWidth, setContainerWidth ] = useState( 0 );
148
156
  const resizeObserverRef = useResizeObserver(
@@ -193,6 +201,33 @@ function DataViews< Item >( {
193
201
  }
194
202
  }, [ hasPrimaryOrLockedFilters, isShowingFilter ] );
195
203
 
204
+ // Attach scroll event listener for infinite scroll
205
+ useEffect( () => {
206
+ if ( ! view.infiniteScrollEnabled || ! containerRef.current ) {
207
+ return;
208
+ }
209
+
210
+ const handleScroll = throttle( ( event: unknown ) => {
211
+ const target = ( event as Event ).target as HTMLElement;
212
+ const scrollTop = target.scrollTop;
213
+ const scrollHeight = target.scrollHeight;
214
+ const clientHeight = target.clientHeight;
215
+
216
+ // Check if user has scrolled near the bottom
217
+ if ( scrollTop + clientHeight >= scrollHeight - 100 ) {
218
+ infiniteScrollHandler?.();
219
+ }
220
+ }, 100 ); // Throttle to 100ms
221
+
222
+ const container = containerRef.current;
223
+ container.addEventListener( 'scroll', handleScroll );
224
+
225
+ return () => {
226
+ container.removeEventListener( 'scroll', handleScroll );
227
+ handleScroll.cancel(); // Cancel any pending throttled calls
228
+ };
229
+ }, [ infiniteScrollHandler, view.infiniteScrollEnabled ] );
230
+
196
231
  return (
197
232
  <DataViewsContext.Provider
198
233
  value={ {
@@ -219,8 +254,9 @@ function DataViews< Item >( {
219
254
  filters,
220
255
  isShowingFilter,
221
256
  setIsShowingFilter,
222
- perPageSizes,
257
+ config,
223
258
  empty,
259
+ hasInfiniteScrollHandler: !! infiniteScrollHandler,
224
260
  } }
225
261
  >
226
262
  <div className="dataviews-wrapper" ref={ containerRef }>
@@ -80,6 +80,58 @@ export const data: SpaceObject[] = [
80
80
  },
81
81
  {
82
82
  id: 4,
83
+ title: 'Ganymede',
84
+ description: 'Largest moon of Jupiter',
85
+ image: 'https://live.staticflickr.com/7816/33436473218_a836235935_k.jpg',
86
+ type: 'Satellite',
87
+ isPlanet: false,
88
+ categories: [ 'Solar system', 'Satellite', 'Jupiter', 'Moon' ],
89
+ satellites: 0,
90
+ date: '2022-01-04',
91
+ datetime: '2022-01-04T12:30:00Z',
92
+ email: 'ganymede@example.com',
93
+ },
94
+ {
95
+ id: 5,
96
+ title: 'Callisto',
97
+ description: 'Outermost Galilean moon of Jupiter',
98
+ image: 'https://live.staticflickr.com/804/27604150528_4512448a9c_c.jpg',
99
+ type: 'Satellite',
100
+ isPlanet: false,
101
+ categories: [ 'Solar system', 'Satellite', 'Jupiter', 'Moon' ],
102
+ satellites: 0,
103
+ date: '2021-01-05',
104
+ datetime: '2021-01-05T14:15:30Z',
105
+ email: 'callisto@example.com',
106
+ },
107
+ {
108
+ id: 6,
109
+ title: 'Amalthea',
110
+ description: 'Small irregular moon of Jupiter',
111
+ image: 'https://upload.wikimedia.org/wikipedia/commons/6/62/Amalthea.gif',
112
+ type: 'Satellite',
113
+ isPlanet: false,
114
+ categories: [ 'Solar system', 'Satellite', 'Jupiter', 'Moon' ],
115
+ satellites: 0,
116
+ date: '2020-01-06',
117
+ datetime: '2020-01-06T10:45:15Z',
118
+ email: 'amalthea@example.com',
119
+ },
120
+ {
121
+ id: 7,
122
+ title: 'Himalia',
123
+ description: 'Largest irregular moon of Jupiter',
124
+ image: 'https://upload.wikimedia.org/wikipedia/commons/c/c2/Cassini-Huygens_Image_of_Himalia.png',
125
+ type: 'Satellite',
126
+ isPlanet: false,
127
+ categories: [ 'Solar system', 'Satellite', 'Jupiter', 'Moon' ],
128
+ satellites: 0,
129
+ date: '2019-01-07',
130
+ datetime: '2019-01-07T16:20:45Z',
131
+ email: 'himalia@example.com',
132
+ },
133
+ {
134
+ id: 8,
83
135
  title: 'Neptune',
84
136
  description: 'Ice giant in the Solar system',
85
137
  image: 'https://live.staticflickr.com/65535/29523683990_000ff4720c_z.jpg',
@@ -92,7 +144,46 @@ export const data: SpaceObject[] = [
92
144
  email: 'neptune@example.com',
93
145
  },
94
146
  {
95
- id: 5,
147
+ id: 9,
148
+ title: 'Triton',
149
+ description: 'Largest moon of Neptune',
150
+ image: 'https://live.staticflickr.com/65535/50728384241_02c5126c30_h.jpg',
151
+ type: 'Satellite',
152
+ isPlanet: false,
153
+ categories: [ 'Solar system', 'Satellite', 'Neptune', 'Moon' ],
154
+ satellites: 0,
155
+ date: '2021-02-01',
156
+ datetime: '2021-02-01T11:30:00Z',
157
+ email: 'triton@example.com',
158
+ },
159
+ {
160
+ id: 10,
161
+ title: 'Nereid',
162
+ description: 'Irregular moon of Neptune',
163
+ image: 'https://upload.wikimedia.org/wikipedia/commons/b/b0/Nereid-Voyager2.jpg',
164
+ type: 'Satellite',
165
+ isPlanet: false,
166
+ categories: [ 'Solar system', 'Satellite', 'Neptune', 'Moon' ],
167
+ satellites: 0,
168
+ date: '2020-02-02',
169
+ datetime: '2020-02-02T15:45:30Z',
170
+ email: 'nereid@example.com',
171
+ },
172
+ {
173
+ id: 11,
174
+ title: 'Proteus',
175
+ description: 'Second-largest moon of Neptune',
176
+ image: 'https://live.staticflickr.com/65535/50727825808_bf427e007b_c.jpg',
177
+ type: 'Satellite',
178
+ isPlanet: false,
179
+ categories: [ 'Solar system', 'Satellite', 'Neptune', 'Moon' ],
180
+ satellites: 0,
181
+ date: '2019-02-03',
182
+ datetime: '2019-02-03T09:20:15Z',
183
+ email: 'proteus@example.com',
184
+ },
185
+ {
186
+ id: 12,
96
187
  title: 'Mercury',
97
188
  description: 'Terrestrial planet in the Solar system',
98
189
  image: 'https://live.staticflickr.com/813/40199101735_e5e92ffd11_z.jpg',
@@ -105,7 +196,7 @@ export const data: SpaceObject[] = [
105
196
  email: 'mercury@example.com',
106
197
  },
107
198
  {
108
- id: 6,
199
+ id: 13,
109
200
  title: 'Venus',
110
201
  description: 'La planète Vénus',
111
202
  image: 'https://live.staticflickr.com/8025/7544560662_900e717727_z.jpg',
@@ -118,7 +209,7 @@ export const data: SpaceObject[] = [
118
209
  email: 'venus@example.com',
119
210
  },
120
211
  {
121
- id: 7,
212
+ id: 14,
122
213
  title: 'Earth',
123
214
  description: 'Terrestrial planet in the Solar system',
124
215
  image: 'https://live.staticflickr.com/3762/9460163562_964fe6af07_z.jpg',
@@ -131,7 +222,7 @@ export const data: SpaceObject[] = [
131
222
  email: 'earth@example.com',
132
223
  },
133
224
  {
134
- id: 8,
225
+ id: 15,
135
226
  title: 'Mars',
136
227
  description: 'Terrestrial planet in the Solar system',
137
228
  image: 'https://live.staticflickr.com/8151/7651156426_e047f4d219_z.jpg',
@@ -144,7 +235,7 @@ export const data: SpaceObject[] = [
144
235
  email: 'mars@example.com',
145
236
  },
146
237
  {
147
- id: 9,
238
+ id: 16,
148
239
  title: 'Jupiter',
149
240
  description: 'Gas giant in the Solar system',
150
241
  image: 'https://staging-jubilee.flickr.com/2853/9458010071_6e6fc41408_z.jpg',
@@ -157,7 +248,7 @@ export const data: SpaceObject[] = [
157
248
  email: 'jupiter@example.com',
158
249
  },
159
250
  {
160
- id: 10,
251
+ id: 17,
161
252
  title: 'Saturn',
162
253
  description: 'Gas giant in the Solar system',
163
254
  image: 'https://live.staticflickr.com/5524/9464658509_fc2d83dff5_z.jpg',
@@ -170,7 +261,7 @@ export const data: SpaceObject[] = [
170
261
  email: 'saturn@example.com',
171
262
  },
172
263
  {
173
- id: 11,
264
+ id: 18,
174
265
  title: 'Uranus',
175
266
  description: 'Ice giant in the Solar system',
176
267
  image: 'https://live.staticflickr.com/65535/5553350875_3072df91e2_c.jpg',
@@ -9,6 +9,8 @@ import type { Meta } from '@storybook/react';
9
9
  import {
10
10
  useState,
11
11
  useMemo,
12
+ useCallback,
13
+ useEffect,
12
14
  createInterpolateElement,
13
15
  } from '@wordpress/element';
14
16
  import {
@@ -37,7 +39,7 @@ import {
37
39
  } from './fixtures';
38
40
  import { LAYOUT_GRID, LAYOUT_LIST, LAYOUT_TABLE } from '../../../constants';
39
41
  import { filterSortAndPaginate } from '../../../filter-and-sort-data-view';
40
- import type { View } from '../../../types';
42
+ import type { Field, View } from '../../../types';
41
43
 
42
44
  import './style.css';
43
45
 
@@ -87,7 +89,7 @@ export const Default = ( { perPageSizes = [ 10, 25, 50, 100 ] } ) => {
87
89
  ) }
88
90
  isItemClickable={ () => true }
89
91
  defaultLayouts={ defaultLayouts }
90
- perPageSizes={ perPageSizes }
92
+ config={ { perPageSizes } }
91
93
  />
92
94
  );
93
95
  };
@@ -144,19 +146,23 @@ export const CustomEmpty = () => {
144
146
  );
145
147
  };
146
148
 
147
- export const FieldsNoSortableNoHidable = () => {
149
+ export const MinimalUI = () => {
148
150
  const [ view, setView ] = useState< View >( {
149
151
  ...DEFAULT_VIEW,
150
152
  fields: [ 'title', 'description', 'categories' ],
153
+ layout: {
154
+ enableMoving: false,
155
+ },
151
156
  } );
152
157
  const { data: shownData, paginationInfo } = useMemo( () => {
153
158
  return filterSortAndPaginate( data, view, fields );
154
159
  }, [ view ] );
155
160
 
156
- const _fields = fields.map( ( field ) => ( {
161
+ const _fields: Field< SpaceObject >[] = fields.map( ( field ) => ( {
157
162
  ...field,
158
163
  enableSorting: false,
159
164
  enableHiding: false,
165
+ filterBy: false,
160
166
  } ) );
161
167
 
162
168
  return (
@@ -166,6 +172,8 @@ export const FieldsNoSortableNoHidable = () => {
166
172
  data={ shownData }
167
173
  view={ view }
168
174
  fields={ _fields }
175
+ config={ false }
176
+ search={ false }
169
177
  onChangeView={ setView }
170
178
  defaultLayouts={ {
171
179
  table: {},
@@ -383,3 +391,128 @@ export const GroupByLayout = () => {
383
391
  />
384
392
  );
385
393
  };
394
+
395
+ export const InfiniteScroll = () => {
396
+ const [ view, setView ] = useState< View >( {
397
+ type: LAYOUT_GRID,
398
+ search: '',
399
+ page: 1,
400
+ perPage: 6, // Start with a small number to demonstrate pagination
401
+ filters: [],
402
+ fields: [ 'satellites' ],
403
+ titleField: 'title',
404
+ descriptionField: 'description',
405
+ mediaField: 'image',
406
+ infiniteScrollEnabled: true, // Enable infinite scroll by default
407
+ } );
408
+ const { data: shownData } = useMemo( () => {
409
+ return filterSortAndPaginate( data, view, fields );
410
+ }, [ view ] );
411
+
412
+ // Custom pagination handler that simulates server-side pagination
413
+ const [ allLoadedRecords, setAllLoadedRecords ] = useState< SpaceObject[] >(
414
+ []
415
+ );
416
+ const [ isLoadingMore, setIsLoadingMore ] = useState( false );
417
+
418
+ const totalItems = data.length;
419
+ const totalPages = Math.ceil( totalItems / 6 ); // perPage is 6.
420
+ const currentPage = view.page || 1;
421
+ const hasMoreData = currentPage < totalPages;
422
+ const getItemId = ( item: {
423
+ id: any;
424
+ title?: string;
425
+ description?: string;
426
+ image?: string;
427
+ type?: string;
428
+ isPlanet?: boolean;
429
+ categories?: string[];
430
+ satellites?: number;
431
+ date?: string;
432
+ datetime?: string;
433
+ email?: string;
434
+ } ) => item.id.toString();
435
+
436
+ const infiniteScrollHandler = useCallback( () => {
437
+ if ( isLoadingMore || currentPage >= totalPages ) {
438
+ return;
439
+ }
440
+
441
+ setIsLoadingMore( true );
442
+
443
+ setView( {
444
+ ...view,
445
+ page: currentPage + 1,
446
+ } );
447
+ }, [ isLoadingMore, currentPage, totalPages, view ] );
448
+
449
+ // Initialize data on first load or when view changes significantly
450
+ useEffect( () => {
451
+ if ( currentPage === 1 || ! view.infiniteScrollEnabled ) {
452
+ // First page - replace all data
453
+ setAllLoadedRecords( shownData );
454
+ } else {
455
+ // Subsequent pages - append to existing data
456
+ setAllLoadedRecords( ( prev ) => {
457
+ const existingIds = new Set( prev.map( getItemId ) );
458
+ const newRecords = shownData.filter(
459
+ ( record ) => ! existingIds.has( getItemId( record ) )
460
+ );
461
+ return [ ...prev, ...newRecords ];
462
+ } );
463
+ }
464
+ setIsLoadingMore( false );
465
+ }, [
466
+ view.search,
467
+ view.filters,
468
+ view.perPage,
469
+ currentPage,
470
+ view.infiniteScrollEnabled,
471
+ ] );
472
+
473
+ const paginationInfo = {
474
+ totalItems,
475
+ totalPages,
476
+ infiniteScrollHandler,
477
+ };
478
+
479
+ return (
480
+ <>
481
+ <style>{ `
482
+ .dataviews-wrapper {
483
+ height: 600px;
484
+ overflow: auto;
485
+ }
486
+ ` }</style>
487
+ <Text
488
+ style={ {
489
+ marginBottom: '16px',
490
+ padding: '8px',
491
+ background: '#f0f0f0',
492
+ borderRadius: '4px',
493
+ display: 'block',
494
+ } }
495
+ >
496
+ { __( 'Infinite Scroll Demo' ) }: { allLoadedRecords.length } of{ ' ' }
497
+ { totalItems } items loaded.
498
+ { isLoadingMore && __( 'Loading more…' ) }
499
+ { ! hasMoreData && __( 'All items loaded!' ) }
500
+ </Text>
501
+ <DataViews
502
+ getItemId={ ( item ) => item.id.toString() }
503
+ paginationInfo={ paginationInfo }
504
+ data={ allLoadedRecords }
505
+ view={ view }
506
+ fields={ fields }
507
+ onChangeView={ setView }
508
+ actions={ actions }
509
+ isLoading={ isLoadingMore }
510
+ defaultLayouts={ {
511
+ [ LAYOUT_GRID ]: {},
512
+ [ LAYOUT_LIST ]: {},
513
+ [ LAYOUT_TABLE ]: {},
514
+ } }
515
+ />
516
+ </>
517
+ );
518
+ };
@@ -37,6 +37,10 @@
37
37
  }
38
38
  }
39
39
 
40
+ .dataviews-loading-more {
41
+ text-align: center;
42
+ }
43
+
40
44
  @container (max-width: 430px) {
41
45
  .dataviews__view-actions,
42
46
  .dataviews-filters__container {
@@ -54,8 +54,9 @@ type DataViewsContextType< Item > = {
54
54
  filters: NormalizedFilter[];
55
55
  isShowingFilter: boolean;
56
56
  setIsShowingFilter: ( value: boolean ) => void;
57
- perPageSizes: number[];
57
+ config: false | { perPageSizes: number[] };
58
58
  empty?: ReactNode;
59
+ hasInfiniteScrollHandler: boolean;
59
60
  };
60
61
 
61
62
  const DataViewsContext = createContext< DataViewsContextType< any > >( {
@@ -81,7 +82,10 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
81
82
  filters: [],
82
83
  isShowingFilter: false,
83
84
  setIsShowingFilter: () => {},
84
- perPageSizes: [],
85
+ hasInfiniteScrollHandler: false,
86
+ config: {
87
+ perPageSizes: [],
88
+ },
85
89
  } );
86
90
 
87
91
  export default DataViewsContext;
@@ -179,17 +179,6 @@ export default function ItemActions< Item >( {
179
179
  );
180
180
  }
181
181
 
182
- // If all actions are primary, there is no need to render the dropdown.
183
- if ( primaryActions.length === eligibleActions.length ) {
184
- return (
185
- <PrimaryActions
186
- item={ item }
187
- actions={ primaryActions }
188
- registry={ registry }
189
- />
190
- );
191
- }
192
-
193
182
  return (
194
183
  <HStack
195
184
  spacing={ 1 }
@@ -205,11 +194,13 @@ export default function ItemActions< Item >( {
205
194
  actions={ primaryActions }
206
195
  registry={ registry }
207
196
  />
208
- <CompactItemActions
209
- item={ item }
210
- actions={ eligibleActions }
211
- registry={ registry }
212
- />
197
+ { primaryActions.length < eligibleActions.length && (
198
+ <CompactItemActions
199
+ item={ item }
200
+ actions={ eligibleActions }
201
+ registry={ registry }
202
+ />
203
+ ) }
213
204
  </HStack>
214
205
  );
215
206
  }
@@ -22,7 +22,7 @@ export function DataViewsPagination() {
22
22
  paginationInfo: { totalItems = 0, totalPages },
23
23
  } = useContext( DataViewsContext );
24
24
 
25
- if ( ! totalItems || ! totalPages ) {
25
+ if ( ! totalItems || ! totalPages || view.infiniteScrollEnabled ) {
26
26
  return null;
27
27
  }
28
28
 
@@ -47,6 +47,7 @@ import { SORTING_DIRECTIONS, sortIcons, sortLabels } from '../../constants';
47
47
  import { VIEW_LAYOUTS } from '../../dataviews-layouts';
48
48
  import type { NormalizedField, View } from '../../types';
49
49
  import DataViewsContext from '../dataviews-context';
50
+ import InfiniteScrollToggle from './infinite-scroll-toggle';
50
51
  import { unlock } from '../../lock-unlock';
51
52
 
52
53
  const { Menu } = unlock( componentsPrivateApis );
@@ -102,12 +103,11 @@ export function ViewTypeMenu() {
102
103
  if ( 'layout' in viewWithoutLayout ) {
103
104
  delete viewWithoutLayout.layout;
104
105
  }
105
- // @ts-expect-error
106
106
  return onChangeView( {
107
107
  ...viewWithoutLayout,
108
108
  type: e.target.value,
109
109
  ...defaultLayouts[ e.target.value ],
110
- } );
110
+ } as View );
111
111
  }
112
112
  warning( 'Invalid dataview' );
113
113
  } }
@@ -214,8 +214,15 @@ function SortDirectionControl() {
214
214
  }
215
215
 
216
216
  function ItemsPerPageControl() {
217
- const { view, perPageSizes, onChangeView } = useContext( DataViewsContext );
218
- if ( perPageSizes.length < 2 || perPageSizes.length > 6 ) {
217
+ const { view, config, onChangeView } = useContext( DataViewsContext );
218
+ const { infiniteScrollEnabled } = view;
219
+ if (
220
+ ! config ||
221
+ ! config.perPageSizes ||
222
+ config.perPageSizes.length < 2 ||
223
+ config.perPageSizes.length > 6 ||
224
+ infiniteScrollEnabled
225
+ ) {
219
226
  return null;
220
227
  }
221
228
 
@@ -240,7 +247,7 @@ function ItemsPerPageControl() {
240
247
  } );
241
248
  } }
242
249
  >
243
- { perPageSizes.map( ( value ) => {
250
+ { config.perPageSizes.map( ( value ) => {
244
251
  return (
245
252
  <ToggleGroupControlOption
246
253
  key={ value }
@@ -801,6 +808,7 @@ export function DataviewsViewConfigDropdown() {
801
808
  { !! activeLayout?.viewConfigOptions && (
802
809
  <activeLayout.viewConfigOptions />
803
810
  ) }
811
+ <InfiniteScrollToggle />
804
812
  <ItemsPerPageControl />
805
813
  </SettingsSection>
806
814
  <SettingsSection title={ __( 'Properties' ) }>
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { ToggleControl } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { useContext } from '@wordpress/element';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import DataViewsContext from '../dataviews-context';
12
+
13
+ export default function InfiniteScrollToggle() {
14
+ const context = useContext( DataViewsContext );
15
+ const { view, onChangeView } = context;
16
+ const infiniteScrollEnabled = view.infiniteScrollEnabled ?? false;
17
+
18
+ // Only render the toggle if an infinite scroll handler is available
19
+ if ( ! context.hasInfiniteScrollHandler ) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <ToggleControl
25
+ __nextHasNoMarginBottom
26
+ label={ __( 'Enable infinite scroll' ) }
27
+ help={ __(
28
+ 'Automatically load more content as you scroll, instead of showing pagination links.'
29
+ ) }
30
+ checked={ infiniteScrollEnabled }
31
+ onChange={ ( newValue ) => {
32
+ onChangeView( {
33
+ ...view,
34
+ infiniteScrollEnabled: newValue,
35
+ } );
36
+ } }
37
+ />
38
+ );
39
+ }