@wordpress/dataviews 13.1.1-next.v.202603102151.0 → 14.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 (247) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +19 -3
  3. package/build/components/dataform-controls/datetime.cjs +8 -4
  4. package/build/components/dataform-controls/datetime.cjs.map +2 -2
  5. package/build/components/dataform-layouts/card/index.cjs +132 -128
  6. package/build/components/dataform-layouts/card/index.cjs.map +3 -3
  7. package/build/components/dataviews-bulk-actions/index.cjs +28 -5
  8. package/build/components/dataviews-bulk-actions/index.cjs.map +2 -2
  9. package/build/components/dataviews-context/index.cjs +2 -2
  10. package/build/components/dataviews-context/index.cjs.map +2 -2
  11. package/build/components/dataviews-footer/index.cjs +2 -3
  12. package/build/components/dataviews-footer/index.cjs.map +2 -2
  13. package/build/components/dataviews-layout/index.cjs +12 -3
  14. package/build/components/dataviews-layout/index.cjs.map +2 -2
  15. package/build/components/dataviews-layouts/grid/composite-grid.cjs +378 -245
  16. package/build/components/dataviews-layouts/grid/composite-grid.cjs.map +2 -2
  17. package/build/components/dataviews-layouts/index.cjs +3 -3
  18. package/build/components/dataviews-layouts/index.cjs.map +3 -3
  19. package/build/components/dataviews-layouts/picker-grid/index.cjs +76 -32
  20. package/build/components/dataviews-layouts/picker-grid/index.cjs.map +2 -2
  21. package/build/components/dataviews-layouts/picker-table/index.cjs +34 -22
  22. package/build/components/dataviews-layouts/picker-table/index.cjs.map +2 -2
  23. package/build/components/dataviews-layouts/table/index.cjs +97 -88
  24. package/build/components/dataviews-layouts/table/index.cjs.map +2 -2
  25. package/build/components/dataviews-layouts/table/{use-is-horizontal-scroll-end.cjs → use-scroll-state.cjs} +29 -33
  26. package/build/components/dataviews-layouts/table/use-scroll-state.cjs.map +7 -0
  27. package/build/components/dataviews-layouts/utils/density-picker.cjs.map +2 -2
  28. package/build/components/dataviews-layouts/utils/grid-config-options.cjs +45 -0
  29. package/build/components/dataviews-layouts/utils/grid-config-options.cjs.map +7 -0
  30. package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs +62 -0
  31. package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs.map +7 -0
  32. package/build/components/dataviews-picker-footer/index.cjs +23 -4
  33. package/build/components/dataviews-picker-footer/index.cjs.map +2 -2
  34. package/build/components/dataviews-search/index.cjs +2 -1
  35. package/build/components/dataviews-search/index.cjs.map +2 -2
  36. package/build/components/dataviews-selection-checkbox/index.cjs +3 -2
  37. package/build/components/dataviews-selection-checkbox/index.cjs.map +2 -2
  38. package/build/components/dataviews-view-config/index.cjs +0 -2
  39. package/build/components/dataviews-view-config/index.cjs.map +3 -3
  40. package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs +0 -3
  41. package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs.map +2 -2
  42. package/build/dataviews/index.cjs +38 -34
  43. package/build/dataviews/index.cjs.map +3 -3
  44. package/build/dataviews-picker/index.cjs +26 -25
  45. package/build/dataviews-picker/index.cjs.map +3 -3
  46. package/build/hooks/index.cjs +11 -2
  47. package/build/hooks/index.cjs.map +2 -2
  48. package/build/hooks/use-data.cjs +146 -9
  49. package/build/hooks/use-data.cjs.map +2 -2
  50. package/build/hooks/use-infinite-scroll.cjs +208 -0
  51. package/build/hooks/use-infinite-scroll.cjs.map +7 -0
  52. package/build/hooks/use-selected-items.cjs +57 -0
  53. package/build/hooks/use-selected-items.cjs.map +7 -0
  54. package/build/types/dataviews.cjs.map +1 -1
  55. package/build/types/field-api.cjs.map +1 -1
  56. package/build/utils/filter-sort-and-paginate.cjs +5 -1
  57. package/build/utils/filter-sort-and-paginate.cjs.map +2 -2
  58. package/build/utils/get-footer-message.cjs +8 -8
  59. package/build/utils/get-footer-message.cjs.map +2 -2
  60. package/build-module/components/dataform-controls/datetime.mjs +8 -4
  61. package/build-module/components/dataform-controls/datetime.mjs.map +2 -2
  62. package/build-module/components/dataform-layouts/card/index.mjs +132 -133
  63. package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
  64. package/build-module/components/dataviews-bulk-actions/index.mjs +28 -5
  65. package/build-module/components/dataviews-bulk-actions/index.mjs.map +2 -2
  66. package/build-module/components/dataviews-context/index.mjs +2 -2
  67. package/build-module/components/dataviews-context/index.mjs.map +2 -2
  68. package/build-module/components/dataviews-footer/index.mjs +2 -3
  69. package/build-module/components/dataviews-footer/index.mjs.map +2 -2
  70. package/build-module/components/dataviews-layout/index.mjs +12 -3
  71. package/build-module/components/dataviews-layout/index.mjs.map +2 -2
  72. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs +387 -246
  73. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs.map +2 -2
  74. package/build-module/components/dataviews-layouts/index.mjs +3 -3
  75. package/build-module/components/dataviews-layouts/index.mjs.map +2 -2
  76. package/build-module/components/dataviews-layouts/picker-grid/index.mjs +80 -33
  77. package/build-module/components/dataviews-layouts/picker-grid/index.mjs.map +2 -2
  78. package/build-module/components/dataviews-layouts/picker-table/index.mjs +34 -22
  79. package/build-module/components/dataviews-layouts/picker-table/index.mjs.map +2 -2
  80. package/build-module/components/dataviews-layouts/table/index.mjs +97 -88
  81. package/build-module/components/dataviews-layouts/table/index.mjs.map +2 -2
  82. package/build-module/components/dataviews-layouts/table/use-scroll-state.mjs +46 -0
  83. package/build-module/components/dataviews-layouts/table/use-scroll-state.mjs.map +7 -0
  84. package/build-module/components/dataviews-layouts/utils/density-picker.mjs.map +2 -2
  85. package/build-module/components/dataviews-layouts/utils/grid-config-options.mjs +14 -0
  86. package/build-module/components/dataviews-layouts/utils/grid-config-options.mjs.map +7 -0
  87. package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs +26 -0
  88. package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs.map +7 -0
  89. package/build-module/components/dataviews-picker-footer/index.mjs +23 -4
  90. package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
  91. package/build-module/components/dataviews-search/index.mjs +2 -1
  92. package/build-module/components/dataviews-search/index.mjs.map +2 -2
  93. package/build-module/components/dataviews-selection-checkbox/index.mjs +3 -2
  94. package/build-module/components/dataviews-selection-checkbox/index.mjs.map +2 -2
  95. package/build-module/components/dataviews-view-config/index.mjs +0 -2
  96. package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
  97. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs +0 -3
  98. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs.map +2 -2
  99. package/build-module/dataviews/index.mjs +46 -36
  100. package/build-module/dataviews/index.mjs.map +2 -2
  101. package/build-module/dataviews-picker/index.mjs +34 -27
  102. package/build-module/dataviews-picker/index.mjs.map +2 -2
  103. package/build-module/hooks/index.mjs +7 -1
  104. package/build-module/hooks/index.mjs.map +2 -2
  105. package/build-module/hooks/use-data.mjs +147 -10
  106. package/build-module/hooks/use-data.mjs.map +2 -2
  107. package/build-module/hooks/use-infinite-scroll.mjs +188 -0
  108. package/build-module/hooks/use-infinite-scroll.mjs.map +7 -0
  109. package/build-module/hooks/use-selected-items.mjs +36 -0
  110. package/build-module/hooks/use-selected-items.mjs.map +7 -0
  111. package/build-module/utils/filter-sort-and-paginate.mjs +5 -1
  112. package/build-module/utils/filter-sort-and-paginate.mjs.map +2 -2
  113. package/build-module/utils/get-footer-message.mjs +8 -8
  114. package/build-module/utils/get-footer-message.mjs.map +2 -2
  115. package/build-style/style-rtl.css +107 -41
  116. package/build-style/style.css +107 -41
  117. package/build-types/components/dataform-controls/datetime.d.ts +1 -1
  118. package/build-types/components/dataform-controls/datetime.d.ts.map +1 -1
  119. package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
  120. package/build-types/components/dataviews-bulk-actions/index.d.ts +2 -1
  121. package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
  122. package/build-types/components/dataviews-context/index.d.ts +1 -1
  123. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  124. package/build-types/components/dataviews-footer/index.d.ts.map +1 -1
  125. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  126. package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts.map +1 -1
  127. package/build-types/components/dataviews-layouts/index.d.ts +3 -3
  128. package/build-types/components/dataviews-layouts/index.d.ts.map +1 -1
  129. package/build-types/components/dataviews-layouts/picker-grid/index.d.ts.map +1 -1
  130. package/build-types/components/dataviews-layouts/picker-table/index.d.ts.map +1 -1
  131. package/build-types/components/dataviews-layouts/table/index.d.ts.map +1 -1
  132. package/build-types/components/dataviews-layouts/table/use-scroll-state.d.ts +25 -0
  133. package/build-types/components/dataviews-layouts/table/use-scroll-state.d.ts.map +1 -0
  134. package/build-types/components/dataviews-layouts/utils/density-picker.d.ts.map +1 -1
  135. package/build-types/components/dataviews-layouts/utils/grid-config-options.d.ts +2 -0
  136. package/build-types/components/dataviews-layouts/utils/grid-config-options.d.ts.map +1 -0
  137. package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts +22 -0
  138. package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts.map +1 -0
  139. package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
  140. package/build-types/components/dataviews-search/index.d.ts.map +1 -1
  141. package/build-types/components/dataviews-selection-checkbox/index.d.ts.map +1 -1
  142. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  143. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +1 -1
  144. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -1
  145. package/build-types/dataviews/index.d.ts +0 -1
  146. package/build-types/dataviews/index.d.ts.map +1 -1
  147. package/build-types/dataviews/stories/empty.d.ts +1 -2
  148. package/build-types/dataviews/stories/empty.d.ts.map +1 -1
  149. package/build-types/dataviews/stories/fixtures.d.ts.map +1 -1
  150. package/build-types/dataviews/stories/free-composition.d.ts.map +1 -1
  151. package/build-types/dataviews/stories/index.story.d.ts +18 -10
  152. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  153. package/build-types/dataviews/stories/infinite-scroll.d.ts.map +1 -1
  154. package/build-types/dataviews/stories/layout-activity.d.ts.map +1 -1
  155. package/build-types/dataviews/stories/layout-custom.d.ts +3 -1
  156. package/build-types/dataviews/stories/layout-custom.d.ts.map +1 -1
  157. package/build-types/dataviews/stories/layout-grid.d.ts.map +1 -1
  158. package/build-types/dataviews/stories/layout-list.d.ts.map +1 -1
  159. package/build-types/dataviews/stories/layout-table.d.ts.map +1 -1
  160. package/build-types/dataviews/stories/with-card.d.ts +3 -1
  161. package/build-types/dataviews/stories/with-card.d.ts.map +1 -1
  162. package/build-types/dataviews-picker/index.d.ts +0 -1
  163. package/build-types/dataviews-picker/index.d.ts.map +1 -1
  164. package/build-types/dataviews-picker/stories/fixtures.d.ts.map +1 -1
  165. package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
  166. package/build-types/field-types/stories/index.story.d.ts.map +1 -1
  167. package/build-types/hooks/index.d.ts +3 -0
  168. package/build-types/hooks/index.d.ts.map +1 -1
  169. package/build-types/hooks/test/use-data.d.ts +2 -0
  170. package/build-types/hooks/test/use-data.d.ts.map +1 -0
  171. package/build-types/hooks/use-data.d.ts +41 -3
  172. package/build-types/hooks/use-data.d.ts.map +1 -1
  173. package/build-types/hooks/use-infinite-scroll.d.ts +21 -0
  174. package/build-types/hooks/use-infinite-scroll.d.ts.map +1 -0
  175. package/build-types/hooks/use-selected-items.d.ts +19 -0
  176. package/build-types/hooks/use-selected-items.d.ts.map +1 -0
  177. package/build-types/types/dataviews.d.ts +15 -1
  178. package/build-types/types/dataviews.d.ts.map +1 -1
  179. package/build-types/types/field-api.d.ts +15 -4
  180. package/build-types/types/field-api.d.ts.map +1 -1
  181. package/build-types/utils/filter-sort-and-paginate.d.ts.map +1 -1
  182. package/build-types/utils/get-footer-message.d.ts +3 -2
  183. package/build-types/utils/get-footer-message.d.ts.map +1 -1
  184. package/build-wp/index.js +3202 -2761
  185. package/package.json +19 -19
  186. package/src/components/dataform-controls/datetime.tsx +19 -11
  187. package/src/components/dataform-layouts/card/index.tsx +171 -146
  188. package/src/components/dataform-layouts/card/style.scss +8 -5
  189. package/src/components/dataviews-bulk-actions/index.tsx +28 -1
  190. package/src/components/dataviews-context/index.ts +2 -2
  191. package/src/components/dataviews-footer/index.tsx +1 -6
  192. package/src/components/dataviews-layout/index.tsx +41 -19
  193. package/src/components/dataviews-layout/style.scss +8 -0
  194. package/src/components/dataviews-layouts/grid/composite-grid.tsx +433 -278
  195. package/src/components/dataviews-layouts/grid/style.scss +22 -2
  196. package/src/components/dataviews-layouts/index.ts +3 -3
  197. package/src/components/dataviews-layouts/picker-grid/index.tsx +70 -17
  198. package/src/components/dataviews-layouts/picker-grid/style.scss +10 -0
  199. package/src/components/dataviews-layouts/picker-table/index.tsx +42 -22
  200. package/src/components/dataviews-layouts/table/index.tsx +10 -4
  201. package/src/components/dataviews-layouts/table/style.scss +13 -0
  202. package/src/components/dataviews-layouts/table/use-scroll-state.ts +79 -0
  203. package/src/components/dataviews-layouts/utils/density-picker.tsx +12 -2
  204. package/src/components/dataviews-layouts/utils/grid-config-options.tsx +14 -0
  205. package/src/components/dataviews-layouts/utils/grid-items.scss +9 -1
  206. package/src/components/dataviews-layouts/utils/use-infinite-scroll.ts +64 -0
  207. package/src/components/dataviews-picker-footer/index.tsx +21 -1
  208. package/src/components/dataviews-search/index.tsx +2 -1
  209. package/src/components/dataviews-selection-checkbox/index.tsx +4 -2
  210. package/src/components/dataviews-view-config/index.tsx +0 -2
  211. package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +0 -5
  212. package/src/dataviews/index.tsx +58 -45
  213. package/src/dataviews/stories/empty.tsx +1 -3
  214. package/src/dataviews/stories/fixtures.tsx +288 -0
  215. package/src/dataviews/stories/free-composition.tsx +44 -45
  216. package/src/dataviews/stories/index.story.tsx +31 -8
  217. package/src/dataviews/stories/infinite-scroll.tsx +7 -93
  218. package/src/dataviews/stories/layout-activity.tsx +1 -0
  219. package/src/dataviews/stories/layout-custom.tsx +7 -3
  220. package/src/dataviews/stories/layout-grid.tsx +1 -0
  221. package/src/dataviews/stories/layout-list.tsx +1 -0
  222. package/src/dataviews/stories/layout-table.tsx +1 -0
  223. package/src/dataviews/stories/style.css +0 -5
  224. package/src/dataviews/stories/with-card.tsx +35 -24
  225. package/src/dataviews/style.scss +5 -8
  226. package/src/dataviews/test/dataviews.tsx +54 -1
  227. package/src/dataviews-picker/index.tsx +41 -35
  228. package/src/dataviews-picker/stories/fixtures.tsx +270 -0
  229. package/src/dataviews-picker/stories/index.story.tsx +62 -129
  230. package/src/field-types/stories/index.story.tsx +12 -0
  231. package/src/hooks/index.ts +3 -0
  232. package/src/hooks/test/use-data.ts +791 -0
  233. package/src/hooks/use-data.ts +288 -21
  234. package/src/hooks/use-infinite-scroll.ts +304 -0
  235. package/src/hooks/use-selected-items.ts +72 -0
  236. package/src/style.scss +1 -0
  237. package/src/types/dataviews.ts +18 -1
  238. package/src/types/field-api.ts +16 -3
  239. package/src/utils/filter-sort-and-paginate.ts +13 -1
  240. package/src/utils/get-footer-message.ts +12 -9
  241. package/src/utils/test/filter-sort-and-paginate.js +78 -54
  242. package/build/components/dataviews-layouts/table/use-is-horizontal-scroll-end.cjs.map +0 -7
  243. package/build-module/components/dataviews-layouts/table/use-is-horizontal-scroll-end.mjs +0 -50
  244. package/build-module/components/dataviews-layouts/table/use-is-horizontal-scroll-end.mjs.map +0 -7
  245. package/build-types/components/dataviews-layouts/table/use-is-horizontal-scroll-end.d.ts +0 -19
  246. package/build-types/components/dataviews-layouts/table/use-is-horizontal-scroll-end.d.ts.map +0 -1
  247. package/src/components/dataviews-layouts/table/use-is-horizontal-scroll-end.ts +0 -82
@@ -18,7 +18,12 @@ import { Stack } from '@wordpress/ui';
18
18
  import { __, sprintf } from '@wordpress/i18n';
19
19
  import { useInstanceId } from '@wordpress/compose';
20
20
  import { isAppleOS } from '@wordpress/keycodes';
21
- import { useContext, forwardRef } from '@wordpress/element';
21
+ import {
22
+ useCallback,
23
+ useContext,
24
+ useRef,
25
+ forwardRef,
26
+ } from '@wordpress/element';
22
27
 
23
28
  /**
24
29
  * Internal dependencies
@@ -40,6 +45,11 @@ import type { SetSelection } from '../../../types/private';
40
45
  import { ItemClickWrapper } from '../utils/item-click-wrapper';
41
46
  const { Badge } = unlock( componentsPrivateApis );
42
47
  import { useGridColumns } from './preview-size-picker';
48
+ import { GridItems } from '../utils/grid-items';
49
+ import {
50
+ useIntersectionObserver,
51
+ usePlaceholdersNeeded,
52
+ } from '../utils/use-infinite-scroll';
43
53
 
44
54
  function chunk< T >( array: T[], size: number ): T[][] {
45
55
  const chunks: T[][] = [];
@@ -72,223 +82,257 @@ interface GridItemProps< Item > extends HTMLAttributes< HTMLDivElement > {
72
82
  config: {
73
83
  sizes: string;
74
84
  };
85
+ posinset?: number;
86
+ setsize?: number;
75
87
  }
76
88
 
77
- const GridItem = forwardRef( function GridItem< Item >(
78
- {
79
- view,
80
- selection,
81
- onChangeSelection,
82
- onClickItem,
83
- isItemClickable,
84
- renderItemLink,
85
- getItemId,
86
- item,
87
- actions,
88
- mediaField,
89
- titleField,
90
- descriptionField,
91
- regularFields,
92
- badgeFields,
93
- hasBulkActions,
94
- config,
95
- ...props
96
- }: GridItemProps< Item >,
97
- ref: React.ForwardedRef< HTMLDivElement >
98
- ) {
99
- const { showTitle = true, showMedia = true, showDescription = true } = view;
100
- const hasBulkAction = useHasAPossibleBulkAction( actions, item );
101
- const id = getItemId( item );
102
- const instanceId = useInstanceId( GridItem );
103
- const isSelected = selection.includes( id );
104
- const mediaPlaceholder = (
105
- <span className="dataviews-view-grid__media-placeholder" />
106
- );
107
- const rendersMediaField = showMedia && mediaField?.render;
108
- const renderedMediaField = rendersMediaField ? (
109
- <mediaField.render
110
- item={ item }
111
- field={ mediaField }
112
- config={ config }
113
- />
114
- ) : (
115
- mediaPlaceholder
116
- );
117
- const renderedTitleField =
118
- showTitle && titleField?.render ? (
119
- <titleField.render item={ item } field={ titleField } />
120
- ) : null;
121
- let mediaA11yProps;
122
- let titleA11yProps;
123
- if ( isItemClickable( item ) && onClickItem ) {
124
- if ( renderedTitleField ) {
125
- mediaA11yProps = {
126
- 'aria-labelledby': `dataviews-view-grid__title-field-${ instanceId }`,
127
- };
128
- titleA11yProps = {
129
- id: `dataviews-view-grid__title-field-${ instanceId }`,
130
- };
131
- } else {
132
- mediaA11yProps = {
133
- 'aria-label': __( 'Navigate to item' ),
134
- };
135
- }
136
- }
137
- return (
138
- <Stack
139
- direction="column"
140
- { ...props }
141
- ref={ ref }
142
- className={ clsx(
143
- props.className,
144
- 'dataviews-view-grid__row__gridcell',
145
- 'dataviews-view-grid__card',
146
- {
147
- 'is-selected': hasBulkAction && isSelected,
148
- }
149
- ) }
150
- onClickCapture={ ( event ) => {
151
- props.onClickCapture?.( event );
152
- if ( isAppleOS() ? event.metaKey : event.ctrlKey ) {
153
- event.stopPropagation();
154
- event.preventDefault();
155
- if ( ! hasBulkAction ) {
156
- return;
157
- }
158
- onChangeSelection(
159
- selection.includes( id )
160
- ? selection.filter( ( itemId ) => id !== itemId )
161
- : [ ...selection, id ]
162
- );
89
+ const GridItem = forwardRef< HTMLDivElement, GridItemProps< any > >(
90
+ function GridItem(
91
+ {
92
+ view,
93
+ selection,
94
+ onChangeSelection,
95
+ onClickItem,
96
+ isItemClickable,
97
+ renderItemLink,
98
+ getItemId,
99
+ item,
100
+ actions,
101
+ mediaField,
102
+ titleField,
103
+ descriptionField,
104
+ regularFields,
105
+ badgeFields,
106
+ hasBulkActions,
107
+ config,
108
+ posinset,
109
+ setsize,
110
+ ...props
111
+ },
112
+ forwardedRef
113
+ ) {
114
+ const {
115
+ showTitle = true,
116
+ showMedia = true,
117
+ showDescription = true,
118
+ } = view;
119
+ const hasBulkAction = useHasAPossibleBulkAction( actions, item );
120
+ const id = getItemId( item );
121
+ const elementRef = useRef< HTMLDivElement | null >( null );
122
+
123
+ // Merge refs callback
124
+ const setRefs = useCallback(
125
+ ( node: HTMLDivElement | null ) => {
126
+ elementRef.current = node;
127
+ if ( typeof forwardedRef === 'function' ) {
128
+ forwardedRef( node );
129
+ } else if ( forwardedRef ) {
130
+ forwardedRef.current = node;
163
131
  }
164
- } }
165
- >
166
- <ItemClickWrapper
132
+ },
133
+ [ forwardedRef ]
134
+ );
135
+ useIntersectionObserver( elementRef, posinset );
136
+ const instanceId = useInstanceId( GridItem );
137
+
138
+ const isSelected = selection.includes( id );
139
+
140
+ const mediaPlaceholder = (
141
+ <span className="dataviews-view-grid__media-placeholder" />
142
+ );
143
+ const rendersMediaField = showMedia && mediaField?.render;
144
+ const renderedMediaField = rendersMediaField ? (
145
+ <mediaField.render
167
146
  item={ item }
168
- isItemClickable={ isItemClickable }
169
- onClickItem={ onClickItem }
170
- renderItemLink={ renderItemLink }
171
- className={ clsx( 'dataviews-view-grid__media', {
172
- 'dataviews-view-grid__media--placeholder':
173
- ! rendersMediaField,
174
- } ) }
175
- { ...mediaA11yProps }
147
+ field={ mediaField }
148
+ config={ config }
149
+ />
150
+ ) : (
151
+ mediaPlaceholder
152
+ );
153
+ const renderedTitleField =
154
+ showTitle && titleField?.render ? (
155
+ <titleField.render item={ item } field={ titleField } />
156
+ ) : null;
157
+ let mediaA11yProps;
158
+ let titleA11yProps;
159
+ if ( isItemClickable( item ) && onClickItem ) {
160
+ if ( renderedTitleField ) {
161
+ mediaA11yProps = {
162
+ 'aria-labelledby': `dataviews-view-grid__title-field-${ instanceId }`,
163
+ };
164
+ titleA11yProps = {
165
+ id: `dataviews-view-grid__title-field-${ instanceId }`,
166
+ };
167
+ } else {
168
+ mediaA11yProps = {
169
+ 'aria-label': __( 'Navigate to item' ),
170
+ };
171
+ }
172
+ }
173
+ return (
174
+ <Stack
175
+ direction="column"
176
+ { ...props }
177
+ ref={ setRefs }
178
+ aria-setsize={ setsize }
179
+ aria-posinset={ posinset }
180
+ className={ clsx(
181
+ props.className,
182
+ 'dataviews-view-grid__row__gridcell',
183
+ 'dataviews-view-grid__card',
184
+ {
185
+ 'is-selected': hasBulkAction && isSelected,
186
+ }
187
+ ) }
188
+ onClickCapture={ ( event ) => {
189
+ props.onClickCapture?.( event );
190
+ if ( isAppleOS() ? event.metaKey : event.ctrlKey ) {
191
+ event.stopPropagation();
192
+ event.preventDefault();
193
+ if ( ! hasBulkAction ) {
194
+ return;
195
+ }
196
+ onChangeSelection(
197
+ isSelected
198
+ ? selection.filter(
199
+ ( itemId ) => id !== itemId
200
+ )
201
+ : [ ...selection, id ]
202
+ );
203
+ }
204
+ } }
176
205
  >
177
- { renderedMediaField }
178
- </ItemClickWrapper>
179
- { hasBulkActions && (
180
- <DataViewsSelectionCheckbox
206
+ <ItemClickWrapper
181
207
  item={ item }
182
- selection={ selection }
183
- onChangeSelection={ onChangeSelection }
184
- getItemId={ getItemId }
185
- titleField={ titleField }
186
- disabled={ ! hasBulkAction }
187
- />
188
- ) }
189
- { !! actions?.length && (
190
- <div className="dataviews-view-grid__media-actions">
191
- <ItemActions item={ item } actions={ actions } isCompact />
192
- </div>
193
- ) }
194
- { showTitle && (
195
- <div className="dataviews-view-grid__title">
196
- <ItemClickWrapper
197
- item={ item }
198
- isItemClickable={ isItemClickable }
199
- onClickItem={ onClickItem }
200
- renderItemLink={ renderItemLink }
201
- className="dataviews-view-grid__title-field dataviews-title-field"
202
- { ...titleA11yProps }
203
- title={
204
- titleField?.getValueFormatted( {
205
- item,
206
- field: titleField,
207
- } ) || undefined
208
- }
209
- >
210
- { renderedTitleField }
211
- </ItemClickWrapper>
212
- </div>
213
- ) }
214
- <Stack direction="column" gap="xs">
215
- { showDescription && descriptionField?.render && (
216
- <descriptionField.render
208
+ isItemClickable={ isItemClickable }
209
+ onClickItem={ onClickItem }
210
+ renderItemLink={ renderItemLink }
211
+ className={ clsx( 'dataviews-view-grid__media', {
212
+ 'dataviews-view-grid__media--placeholder':
213
+ ! rendersMediaField,
214
+ } ) }
215
+ { ...mediaA11yProps }
216
+ >
217
+ { renderedMediaField }
218
+ </ItemClickWrapper>
219
+ { hasBulkActions && (
220
+ <DataViewsSelectionCheckbox
217
221
  item={ item }
218
- field={ descriptionField }
222
+ selection={ selection }
223
+ onChangeSelection={ onChangeSelection }
224
+ getItemId={ getItemId }
225
+ titleField={ titleField }
226
+ disabled={ ! hasBulkAction }
219
227
  />
220
228
  ) }
221
- { !! badgeFields?.length && (
222
- <Stack
223
- direction="row"
224
- className="dataviews-view-grid__badge-fields"
225
- gap="sm"
226
- wrap="wrap"
227
- align="top"
228
- justify="flex-start"
229
- >
230
- { badgeFields.map( ( field ) => {
231
- return (
232
- <Badge
233
- key={ field.id }
234
- className="dataviews-view-grid__field-value"
235
- >
236
- <field.render
237
- item={ item }
238
- field={ field }
239
- />
240
- </Badge>
241
- );
242
- } ) }
243
- </Stack>
229
+ { !! actions?.length && (
230
+ <div className="dataviews-view-grid__media-actions">
231
+ <ItemActions
232
+ item={ item }
233
+ actions={ actions }
234
+ isCompact
235
+ />
236
+ </div>
244
237
  ) }
245
- { !! regularFields?.length && (
246
- <Stack
247
- direction="column"
248
- className="dataviews-view-grid__fields"
249
- gap="xs"
250
- >
251
- { regularFields.map( ( field ) => {
252
- return (
253
- <Flex
254
- className="dataviews-view-grid__field"
255
- key={ field.id }
256
- gap={ 1 }
257
- justify="flex-start"
258
- expanded
259
- style={ { height: 'auto' } }
260
- direction="row"
261
- >
262
- <>
263
- <Tooltip text={ field.label }>
264
- <FlexItem className="dataviews-view-grid__field-name">
265
- { field.header }
266
- </FlexItem>
267
- </Tooltip>
268
- <FlexItem
269
- className="dataviews-view-grid__field-value"
270
- style={ { maxHeight: 'none' } }
271
- >
272
- <field.render
273
- item={ item }
274
- field={ field }
275
- />
276
- </FlexItem>
277
- </>
278
- </Flex>
279
- );
280
- } ) }
281
- </Stack>
238
+ { showTitle && (
239
+ <div className="dataviews-view-grid__title">
240
+ <ItemClickWrapper
241
+ item={ item }
242
+ isItemClickable={ isItemClickable }
243
+ onClickItem={ onClickItem }
244
+ renderItemLink={ renderItemLink }
245
+ className="dataviews-view-grid__title-field dataviews-title-field"
246
+ { ...titleA11yProps }
247
+ title={
248
+ titleField?.getValueFormatted( {
249
+ item,
250
+ field: titleField,
251
+ } ) || undefined
252
+ }
253
+ >
254
+ { renderedTitleField }
255
+ </ItemClickWrapper>
256
+ </div>
282
257
  ) }
258
+ <Stack direction="column" gap="xs">
259
+ { showDescription && descriptionField?.render && (
260
+ <descriptionField.render
261
+ item={ item }
262
+ field={ descriptionField }
263
+ />
264
+ ) }
265
+ { !! badgeFields?.length && (
266
+ <Stack
267
+ direction="row"
268
+ className="dataviews-view-grid__badge-fields"
269
+ gap="sm"
270
+ wrap="wrap"
271
+ align="top"
272
+ justify="flex-start"
273
+ >
274
+ { badgeFields.map( ( field ) => {
275
+ return (
276
+ <Badge
277
+ key={ field.id }
278
+ className="dataviews-view-grid__field-value"
279
+ >
280
+ <field.render
281
+ item={ item }
282
+ field={ field }
283
+ />
284
+ </Badge>
285
+ );
286
+ } ) }
287
+ </Stack>
288
+ ) }
289
+ { !! regularFields?.length && (
290
+ <Stack
291
+ direction="column"
292
+ className="dataviews-view-grid__fields"
293
+ gap="xs"
294
+ >
295
+ { regularFields.map( ( field ) => {
296
+ return (
297
+ <Flex
298
+ className="dataviews-view-grid__field"
299
+ key={ field.id }
300
+ gap={ 1 }
301
+ justify="flex-start"
302
+ expanded
303
+ style={ { height: 'auto' } }
304
+ direction="row"
305
+ >
306
+ <>
307
+ <Tooltip text={ field.label }>
308
+ <FlexItem className="dataviews-view-grid__field-name">
309
+ { field.header }
310
+ </FlexItem>
311
+ </Tooltip>
312
+ <FlexItem
313
+ className="dataviews-view-grid__field-value"
314
+ style={ { maxHeight: 'none' } }
315
+ >
316
+ <field.render
317
+ item={ item }
318
+ field={ field }
319
+ />
320
+ </FlexItem>
321
+ </>
322
+ </Flex>
323
+ );
324
+ } ) }
325
+ </Stack>
326
+ ) }
327
+ </Stack>
283
328
  </Stack>
284
- </Stack>
285
- );
286
- } ) as < Item >(
329
+ );
330
+ }
331
+ ) as < Item >(
287
332
  props: GridItemProps< Item > & {
288
333
  ref?: React.ForwardedRef< HTMLDivElement >;
289
334
  }
290
335
  ) => React.ReactNode;
291
-
292
336
  interface CompositeGridProps< Item > {
293
337
  data: Item[];
294
338
  isInfiniteScroll: boolean;
@@ -369,84 +413,195 @@ export default function CompositeGrid< Item >( {
369
413
  const size = '900px';
370
414
  const totalRows = Math.ceil( data.length / gridColumns );
371
415
 
416
+ // Calculate placeholders needed for infinite scroll
417
+ const placeholdersNeeded = usePlaceholdersNeeded(
418
+ data,
419
+ isInfiniteScroll,
420
+ gridColumns
421
+ );
422
+
372
423
  return (
373
- <Composite
374
- role={ isInfiniteScroll ? 'feed' : 'grid' }
375
- className={ clsx( 'dataviews-view-grid', className ) }
376
- focusWrap
377
- aria-busy={ isLoading }
378
- aria-rowcount={ isInfiniteScroll ? undefined : totalRows }
379
- ref={ resizeObserverRef }
380
- // @ts-ignore
381
- inert={ inert }
382
- >
383
- { chunk( data, gridColumns ).map( ( row, i ) => (
384
- <Composite.Row
385
- key={ i }
386
- render={
387
- <div
388
- role="row"
389
- aria-rowindex={ i + 1 }
390
- aria-label={ sprintf(
391
- /* translators: %d: The row number in the grid */
392
- __( 'Row %d' ),
393
- i + 1
394
- ) }
395
- className="dataviews-view-grid__row"
396
- style={ {
397
- gridTemplateColumns: `repeat( ${ gridColumns }, minmax(0, 1fr) )`,
398
- } }
399
- />
400
- }
401
- >
402
- { row.map( ( item, indexInRow ) => {
403
- const index = i * gridColumns + indexInRow;
404
- return (
405
- <Composite.Item
406
- key={ getItemId( item ) }
407
- render={ ( props ) => (
408
- <GridItem
409
- { ...props }
410
- role={
411
- isInfiniteScroll
412
- ? 'article'
413
- : 'gridcell'
414
- }
415
- aria-setsize={
416
- isInfiniteScroll
417
- ? paginationInfo.totalItems
418
- : undefined
419
- }
420
- aria-posinset={
421
- isInfiniteScroll
422
- ? index + 1
423
- : undefined
424
- }
425
- view={ view }
426
- selection={ selection }
427
- onChangeSelection={ onChangeSelection }
428
- onClickItem={ onClickItem }
429
- isItemClickable={ isItemClickable }
430
- renderItemLink={ renderItemLink }
431
- getItemId={ getItemId }
432
- item={ item }
433
- actions={ actions }
434
- mediaField={ mediaField }
435
- titleField={ titleField }
436
- descriptionField={ descriptionField }
437
- regularFields={ regularFields }
438
- badgeFields={ badgeFields }
439
- hasBulkActions={ hasBulkActions }
440
- config={ {
441
- sizes: size,
442
- } }
443
- />
424
+ <>
425
+ {
426
+ // Render infinite scroll layout (no rows, feed semantics)
427
+ isInfiniteScroll && (
428
+ <Composite
429
+ render={
430
+ <GridItems
431
+ className={ clsx(
432
+ 'dataviews-view-grid-infinite-scroll',
433
+ className,
434
+ {
435
+ [ `has-${ view.layout?.density }-density` ]:
436
+ view.layout?.density &&
437
+ [
438
+ 'compact',
439
+ 'comfortable',
440
+ ].includes( view.layout.density ),
441
+ }
444
442
  ) }
443
+ previewSize={ view.layout?.previewSize }
444
+ aria-busy={ isLoading }
445
+ ref={ resizeObserverRef }
445
446
  />
446
- );
447
- } ) }
448
- </Composite.Row>
449
- ) ) }
450
- </Composite>
447
+ }
448
+ role="feed"
449
+ focusWrap
450
+ // @ts-ignore
451
+ inert={ inert }
452
+ >
453
+ { /* Render placeholders for unloaded items in first row */ }
454
+ { Array.from( { length: placeholdersNeeded } ).map(
455
+ ( _, index ) => (
456
+ <Composite.Item
457
+ key={ `placeholder-${ index }` }
458
+ render={ ( props ) => (
459
+ <Stack
460
+ { ...props }
461
+ direction="column"
462
+ role="article"
463
+ className="dataviews-view-grid__row__gridcell dataviews-view-grid__card dataviews-view-grid__placeholder"
464
+ />
465
+ ) }
466
+ aria-hidden
467
+ tabIndex={ -1 }
468
+ />
469
+ )
470
+ ) }
471
+ { data.map( ( item ) => {
472
+ const itemId = getItemId( item );
473
+ // Use position from item for infinite scroll
474
+ const stablePosition = ( item as any ).position;
475
+ return (
476
+ <Composite.Item
477
+ key={ itemId }
478
+ render={ ( props ) => (
479
+ <GridItem
480
+ { ...props }
481
+ id={ itemId }
482
+ role="article"
483
+ view={ view }
484
+ selection={ selection }
485
+ onChangeSelection={
486
+ onChangeSelection
487
+ }
488
+ onClickItem={ onClickItem }
489
+ isItemClickable={ isItemClickable }
490
+ renderItemLink={ renderItemLink }
491
+ getItemId={ getItemId }
492
+ item={ item }
493
+ actions={ actions }
494
+ mediaField={ mediaField }
495
+ titleField={ titleField }
496
+ descriptionField={
497
+ descriptionField
498
+ }
499
+ regularFields={ regularFields }
500
+ badgeFields={ badgeFields }
501
+ hasBulkActions={ hasBulkActions }
502
+ posinset={ stablePosition }
503
+ setsize={
504
+ paginationInfo.totalItems
505
+ }
506
+ config={ {
507
+ sizes: size,
508
+ } }
509
+ />
510
+ ) }
511
+ />
512
+ );
513
+ } ) }
514
+ </Composite>
515
+ )
516
+ }
517
+ {
518
+ // Render standard grid layout (with rows, grid semantics)
519
+ ! isInfiniteScroll && (
520
+ <Composite
521
+ role="grid"
522
+ className={ clsx( 'dataviews-view-grid', className, {
523
+ [ `has-${ view.layout?.density }-density` ]:
524
+ view.layout?.density &&
525
+ [ 'compact', 'comfortable' ].includes(
526
+ view.layout.density
527
+ ),
528
+ } ) }
529
+ focusWrap
530
+ aria-busy={ isLoading }
531
+ aria-rowcount={ totalRows }
532
+ ref={ resizeObserverRef }
533
+ // @ts-ignore
534
+ inert={ inert }
535
+ >
536
+ { chunk( data, gridColumns ).map( ( row, i ) => (
537
+ <Composite.Row
538
+ key={ i }
539
+ render={
540
+ <div
541
+ role="row"
542
+ aria-rowindex={ i + 1 }
543
+ aria-label={ sprintf(
544
+ /* translators: %d: The row number in the grid */
545
+ __( 'Row %d' ),
546
+ i + 1
547
+ ) }
548
+ className="dataviews-view-grid__row"
549
+ style={ {
550
+ gridTemplateColumns: `repeat( ${ gridColumns }, minmax(0, 1fr) )`,
551
+ } }
552
+ />
553
+ }
554
+ >
555
+ { row.map( ( item ) => {
556
+ const itemId = getItemId( item );
557
+ return (
558
+ <Composite.Item
559
+ key={ itemId }
560
+ render={ ( props ) => (
561
+ <GridItem
562
+ { ...props }
563
+ id={ itemId }
564
+ role="gridcell"
565
+ view={ view }
566
+ selection={ selection }
567
+ onChangeSelection={
568
+ onChangeSelection
569
+ }
570
+ onClickItem={ onClickItem }
571
+ isItemClickable={
572
+ isItemClickable
573
+ }
574
+ renderItemLink={
575
+ renderItemLink
576
+ }
577
+ getItemId={ getItemId }
578
+ item={ item }
579
+ actions={ actions }
580
+ mediaField={ mediaField }
581
+ titleField={ titleField }
582
+ descriptionField={
583
+ descriptionField
584
+ }
585
+ regularFields={
586
+ regularFields
587
+ }
588
+ badgeFields={ badgeFields }
589
+ hasBulkActions={
590
+ hasBulkActions
591
+ }
592
+ config={ {
593
+ sizes: size,
594
+ } }
595
+ />
596
+ ) }
597
+ />
598
+ );
599
+ } ) }
600
+ </Composite.Row>
601
+ ) ) }
602
+ </Composite>
603
+ )
604
+ }
605
+ </>
451
606
  );
452
607
  }