@wordpress/dataviews 13.1.1-next.v.202603161435.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 (183) hide show
  1. package/CHANGELOG.md +15 -6
  2. package/README.md +17 -2
  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-layouts/grid/composite-grid.cjs +378 -249
  14. package/build/components/dataviews-layouts/grid/composite-grid.cjs.map +2 -2
  15. package/build/components/dataviews-layouts/picker-grid/index.cjs +63 -30
  16. package/build/components/dataviews-layouts/picker-grid/index.cjs.map +2 -2
  17. package/build/components/dataviews-layouts/picker-table/index.cjs +34 -22
  18. package/build/components/dataviews-layouts/picker-table/index.cjs.map +2 -2
  19. package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs +62 -0
  20. package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs.map +7 -0
  21. package/build/components/dataviews-picker-footer/index.cjs +23 -4
  22. package/build/components/dataviews-picker-footer/index.cjs.map +2 -2
  23. package/build/components/dataviews-search/index.cjs +2 -1
  24. package/build/components/dataviews-search/index.cjs.map +2 -2
  25. package/build/components/dataviews-selection-checkbox/index.cjs +3 -2
  26. package/build/components/dataviews-selection-checkbox/index.cjs.map +2 -2
  27. package/build/components/dataviews-view-config/index.cjs +0 -2
  28. package/build/components/dataviews-view-config/index.cjs.map +3 -3
  29. package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs +0 -3
  30. package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs.map +2 -2
  31. package/build/dataviews/index.cjs +37 -37
  32. package/build/dataviews/index.cjs.map +3 -3
  33. package/build/dataviews-picker/index.cjs +25 -24
  34. package/build/dataviews-picker/index.cjs.map +3 -3
  35. package/build/hooks/index.cjs +11 -2
  36. package/build/hooks/index.cjs.map +2 -2
  37. package/build/hooks/use-data.cjs +146 -9
  38. package/build/hooks/use-data.cjs.map +2 -2
  39. package/build/hooks/use-infinite-scroll.cjs +208 -0
  40. package/build/hooks/use-infinite-scroll.cjs.map +7 -0
  41. package/build/hooks/use-selected-items.cjs +57 -0
  42. package/build/hooks/use-selected-items.cjs.map +7 -0
  43. package/build/types/dataviews.cjs.map +1 -1
  44. package/build/types/field-api.cjs.map +1 -1
  45. package/build/utils/filter-sort-and-paginate.cjs +5 -1
  46. package/build/utils/filter-sort-and-paginate.cjs.map +2 -2
  47. package/build/utils/get-footer-message.cjs +8 -8
  48. package/build/utils/get-footer-message.cjs.map +2 -2
  49. package/build-module/components/dataform-controls/datetime.mjs +8 -4
  50. package/build-module/components/dataform-controls/datetime.mjs.map +2 -2
  51. package/build-module/components/dataform-layouts/card/index.mjs +132 -133
  52. package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
  53. package/build-module/components/dataviews-bulk-actions/index.mjs +28 -5
  54. package/build-module/components/dataviews-bulk-actions/index.mjs.map +2 -2
  55. package/build-module/components/dataviews-context/index.mjs +2 -2
  56. package/build-module/components/dataviews-context/index.mjs.map +2 -2
  57. package/build-module/components/dataviews-footer/index.mjs +2 -3
  58. package/build-module/components/dataviews-footer/index.mjs.map +2 -2
  59. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs +387 -250
  60. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs.map +2 -2
  61. package/build-module/components/dataviews-layouts/picker-grid/index.mjs +67 -31
  62. package/build-module/components/dataviews-layouts/picker-grid/index.mjs.map +2 -2
  63. package/build-module/components/dataviews-layouts/picker-table/index.mjs +34 -22
  64. package/build-module/components/dataviews-layouts/picker-table/index.mjs.map +2 -2
  65. package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs +26 -0
  66. package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs.map +7 -0
  67. package/build-module/components/dataviews-picker-footer/index.mjs +23 -4
  68. package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
  69. package/build-module/components/dataviews-search/index.mjs +2 -1
  70. package/build-module/components/dataviews-search/index.mjs.map +2 -2
  71. package/build-module/components/dataviews-selection-checkbox/index.mjs +3 -2
  72. package/build-module/components/dataviews-selection-checkbox/index.mjs.map +2 -2
  73. package/build-module/components/dataviews-view-config/index.mjs +0 -2
  74. package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
  75. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs +0 -3
  76. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs.map +2 -2
  77. package/build-module/dataviews/index.mjs +45 -39
  78. package/build-module/dataviews/index.mjs.map +2 -2
  79. package/build-module/dataviews-picker/index.mjs +33 -26
  80. package/build-module/dataviews-picker/index.mjs.map +2 -2
  81. package/build-module/hooks/index.mjs +7 -1
  82. package/build-module/hooks/index.mjs.map +2 -2
  83. package/build-module/hooks/use-data.mjs +147 -10
  84. package/build-module/hooks/use-data.mjs.map +2 -2
  85. package/build-module/hooks/use-infinite-scroll.mjs +188 -0
  86. package/build-module/hooks/use-infinite-scroll.mjs.map +7 -0
  87. package/build-module/hooks/use-selected-items.mjs +36 -0
  88. package/build-module/hooks/use-selected-items.mjs.map +7 -0
  89. package/build-module/utils/filter-sort-and-paginate.mjs +5 -1
  90. package/build-module/utils/filter-sort-and-paginate.mjs.map +2 -2
  91. package/build-module/utils/get-footer-message.mjs +8 -8
  92. package/build-module/utils/get-footer-message.mjs.map +2 -2
  93. package/build-style/style-rtl.css +61 -37
  94. package/build-style/style.css +61 -37
  95. package/build-types/components/dataform-controls/datetime.d.ts +1 -1
  96. package/build-types/components/dataform-controls/datetime.d.ts.map +1 -1
  97. package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
  98. package/build-types/components/dataviews-bulk-actions/index.d.ts +2 -1
  99. package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
  100. package/build-types/components/dataviews-context/index.d.ts +1 -1
  101. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  102. package/build-types/components/dataviews-footer/index.d.ts.map +1 -1
  103. package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts.map +1 -1
  104. package/build-types/components/dataviews-layouts/picker-grid/index.d.ts.map +1 -1
  105. package/build-types/components/dataviews-layouts/picker-table/index.d.ts.map +1 -1
  106. package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts +22 -0
  107. package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts.map +1 -0
  108. package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
  109. package/build-types/components/dataviews-search/index.d.ts.map +1 -1
  110. package/build-types/components/dataviews-selection-checkbox/index.d.ts.map +1 -1
  111. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  112. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +1 -1
  113. package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -1
  114. package/build-types/dataviews/index.d.ts +0 -1
  115. package/build-types/dataviews/index.d.ts.map +1 -1
  116. package/build-types/dataviews/stories/fixtures.d.ts.map +1 -1
  117. package/build-types/dataviews/stories/free-composition.d.ts.map +1 -1
  118. package/build-types/dataviews/stories/index.story.d.ts +11 -0
  119. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  120. package/build-types/dataviews/stories/infinite-scroll.d.ts.map +1 -1
  121. package/build-types/dataviews/stories/with-card.d.ts.map +1 -1
  122. package/build-types/dataviews-picker/index.d.ts +0 -1
  123. package/build-types/dataviews-picker/index.d.ts.map +1 -1
  124. package/build-types/dataviews-picker/stories/fixtures.d.ts.map +1 -1
  125. package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
  126. package/build-types/field-types/stories/index.story.d.ts.map +1 -1
  127. package/build-types/hooks/index.d.ts +3 -0
  128. package/build-types/hooks/index.d.ts.map +1 -1
  129. package/build-types/hooks/test/use-data.d.ts +2 -0
  130. package/build-types/hooks/test/use-data.d.ts.map +1 -0
  131. package/build-types/hooks/use-data.d.ts +41 -3
  132. package/build-types/hooks/use-data.d.ts.map +1 -1
  133. package/build-types/hooks/use-infinite-scroll.d.ts +21 -0
  134. package/build-types/hooks/use-infinite-scroll.d.ts.map +1 -0
  135. package/build-types/hooks/use-selected-items.d.ts +19 -0
  136. package/build-types/hooks/use-selected-items.d.ts.map +1 -0
  137. package/build-types/types/dataviews.d.ts +7 -1
  138. package/build-types/types/dataviews.d.ts.map +1 -1
  139. package/build-types/types/field-api.d.ts +15 -4
  140. package/build-types/types/field-api.d.ts.map +1 -1
  141. package/build-types/utils/filter-sort-and-paginate.d.ts.map +1 -1
  142. package/build-types/utils/get-footer-message.d.ts +3 -2
  143. package/build-types/utils/get-footer-message.d.ts.map +1 -1
  144. package/build-wp/index.js +3013 -2613
  145. package/package.json +19 -19
  146. package/src/components/dataform-controls/datetime.tsx +19 -11
  147. package/src/components/dataform-layouts/card/index.tsx +171 -146
  148. package/src/components/dataform-layouts/card/style.scss +8 -5
  149. package/src/components/dataviews-bulk-actions/index.tsx +28 -1
  150. package/src/components/dataviews-context/index.ts +2 -2
  151. package/src/components/dataviews-footer/index.tsx +1 -6
  152. package/src/components/dataviews-layouts/grid/composite-grid.tsx +433 -284
  153. package/src/components/dataviews-layouts/grid/style.scss +4 -0
  154. package/src/components/dataviews-layouts/picker-grid/index.tsx +53 -15
  155. package/src/components/dataviews-layouts/picker-table/index.tsx +42 -22
  156. package/src/components/dataviews-layouts/utils/use-infinite-scroll.ts +64 -0
  157. package/src/components/dataviews-picker-footer/index.tsx +21 -1
  158. package/src/components/dataviews-search/index.tsx +2 -1
  159. package/src/components/dataviews-selection-checkbox/index.tsx +4 -2
  160. package/src/components/dataviews-view-config/index.tsx +0 -2
  161. package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +0 -5
  162. package/src/dataviews/index.tsx +57 -52
  163. package/src/dataviews/stories/fixtures.tsx +288 -0
  164. package/src/dataviews/stories/free-composition.tsx +12 -11
  165. package/src/dataviews/stories/index.story.tsx +19 -2
  166. package/src/dataviews/stories/infinite-scroll.tsx +12 -92
  167. package/src/dataviews/stories/with-card.tsx +30 -23
  168. package/src/dataviews/style.scss +5 -7
  169. package/src/dataviews/test/dataviews.tsx +21 -9
  170. package/src/dataviews-picker/index.tsx +40 -34
  171. package/src/dataviews-picker/stories/fixtures.tsx +270 -0
  172. package/src/dataviews-picker/stories/index.story.tsx +62 -129
  173. package/src/field-types/stories/index.story.tsx +12 -0
  174. package/src/hooks/index.ts +3 -0
  175. package/src/hooks/test/use-data.ts +791 -0
  176. package/src/hooks/use-data.ts +288 -21
  177. package/src/hooks/use-infinite-scroll.ts +304 -0
  178. package/src/hooks/use-selected-items.ts +72 -0
  179. package/src/types/dataviews.ts +8 -1
  180. package/src/types/field-api.ts +16 -3
  181. package/src/utils/filter-sort-and-paginate.ts +13 -1
  182. package/src/utils/get-footer-message.ts +12 -9
  183. package/src/utils/test/filter-sort-and-paginate.js +78 -54
@@ -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,90 +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
- [ `has-${ view.layout?.density }-density` ]:
377
- view.layout?.density &&
378
- [ 'compact', 'comfortable' ].includes(
379
- view.layout.density
380
- ),
381
- } ) }
382
- focusWrap
383
- aria-busy={ isLoading }
384
- aria-rowcount={ isInfiniteScroll ? undefined : totalRows }
385
- ref={ resizeObserverRef }
386
- // @ts-ignore
387
- inert={ inert }
388
- >
389
- { chunk( data, gridColumns ).map( ( row, i ) => (
390
- <Composite.Row
391
- key={ i }
392
- render={
393
- <div
394
- role="row"
395
- aria-rowindex={ i + 1 }
396
- aria-label={ sprintf(
397
- /* translators: %d: The row number in the grid */
398
- __( 'Row %d' ),
399
- i + 1
400
- ) }
401
- className="dataviews-view-grid__row"
402
- style={ {
403
- gridTemplateColumns: `repeat( ${ gridColumns }, minmax(0, 1fr) )`,
404
- } }
405
- />
406
- }
407
- >
408
- { row.map( ( item, indexInRow ) => {
409
- const index = i * gridColumns + indexInRow;
410
- return (
411
- <Composite.Item
412
- key={ getItemId( item ) }
413
- render={ ( props ) => (
414
- <GridItem
415
- { ...props }
416
- role={
417
- isInfiniteScroll
418
- ? 'article'
419
- : 'gridcell'
420
- }
421
- aria-setsize={
422
- isInfiniteScroll
423
- ? paginationInfo.totalItems
424
- : undefined
425
- }
426
- aria-posinset={
427
- isInfiniteScroll
428
- ? index + 1
429
- : undefined
430
- }
431
- view={ view }
432
- selection={ selection }
433
- onChangeSelection={ onChangeSelection }
434
- onClickItem={ onClickItem }
435
- isItemClickable={ isItemClickable }
436
- renderItemLink={ renderItemLink }
437
- getItemId={ getItemId }
438
- item={ item }
439
- actions={ actions }
440
- mediaField={ mediaField }
441
- titleField={ titleField }
442
- descriptionField={ descriptionField }
443
- regularFields={ regularFields }
444
- badgeFields={ badgeFields }
445
- hasBulkActions={ hasBulkActions }
446
- config={ {
447
- sizes: size,
448
- } }
449
- />
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
+ }
450
442
  ) }
443
+ previewSize={ view.layout?.previewSize }
444
+ aria-busy={ isLoading }
445
+ ref={ resizeObserverRef }
451
446
  />
452
- );
453
- } ) }
454
- </Composite.Row>
455
- ) ) }
456
- </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
+ </>
457
606
  );
458
607
  }