@wordpress/dataviews 4.1.0 → 4.3.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 (223) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +77 -29
  3. package/build/components/dataviews/index.js +10 -14
  4. package/build/components/dataviews/index.js.map +1 -1
  5. package/build/components/dataviews-bulk-actions/index.js +145 -141
  6. package/build/components/dataviews-bulk-actions/index.js.map +1 -1
  7. package/build/components/dataviews-filters/add-filter.js +4 -6
  8. package/build/components/dataviews-filters/add-filter.js.map +1 -1
  9. package/build/components/dataviews-filters/index.js +3 -0
  10. package/build/components/dataviews-filters/index.js.map +1 -1
  11. package/build/components/dataviews-filters/search-widget.js +30 -23
  12. package/build/components/dataviews-filters/search-widget.js.map +1 -1
  13. package/build/components/dataviews-footer/index.js +45 -0
  14. package/build/components/dataviews-footer/index.js.map +1 -0
  15. package/build/components/dataviews-item-actions/index.js +5 -8
  16. package/build/components/dataviews-item-actions/index.js.map +1 -1
  17. package/build/components/dataviews-pagination/index.js +27 -19
  18. package/build/components/dataviews-pagination/index.js.map +1 -1
  19. package/build/components/dataviews-view-config/index.js +197 -41
  20. package/build/components/dataviews-view-config/index.js.map +1 -1
  21. package/build/dataform-controls/datetime.js +49 -0
  22. package/build/dataform-controls/datetime.js.map +1 -0
  23. package/build/dataform-controls/index.js +50 -0
  24. package/build/dataform-controls/index.js.map +1 -0
  25. package/build/dataform-controls/integer.js +45 -0
  26. package/build/dataform-controls/integer.js.map +1 -0
  27. package/build/dataform-controls/radio.js +45 -0
  28. package/build/dataform-controls/radio.js.map +1 -0
  29. package/build/dataform-controls/select.js +58 -0
  30. package/build/dataform-controls/select.js.map +1 -0
  31. package/build/dataform-controls/text.js +45 -0
  32. package/build/dataform-controls/text.js.map +1 -0
  33. package/build/dataforms-layouts/panel/index.js +10 -4
  34. package/build/dataforms-layouts/panel/index.js.map +1 -1
  35. package/build/dataforms-layouts/regular/index.js +6 -3
  36. package/build/dataforms-layouts/regular/index.js.map +1 -1
  37. package/build/dataviews-layouts/grid/density-picker.js +23 -52
  38. package/build/dataviews-layouts/grid/density-picker.js.map +1 -1
  39. package/build/dataviews-layouts/grid/index.js +1 -1
  40. package/build/dataviews-layouts/grid/index.js.map +1 -1
  41. package/build/dataviews-layouts/index.js +48 -2
  42. package/build/dataviews-layouts/index.js.map +1 -1
  43. package/build/dataviews-layouts/list/index.js +124 -80
  44. package/build/dataviews-layouts/list/index.js.map +1 -1
  45. package/build/dataviews-layouts/table/column-header-menu.js +52 -57
  46. package/build/dataviews-layouts/table/column-header-menu.js.map +1 -1
  47. package/build/dataviews-layouts/table/index.js +7 -35
  48. package/build/dataviews-layouts/table/index.js.map +1 -1
  49. package/build/field-types/datetime.js +30 -0
  50. package/build/field-types/datetime.js.map +1 -0
  51. package/build/field-types/index.js +4 -0
  52. package/build/field-types/index.js.map +1 -1
  53. package/build/field-types/integer.js +1 -60
  54. package/build/field-types/integer.js.map +1 -1
  55. package/build/field-types/text.js +1 -60
  56. package/build/field-types/text.js.map +1 -1
  57. package/build/normalize-fields.js +10 -9
  58. package/build/normalize-fields.js.map +1 -1
  59. package/build/types.js.map +1 -1
  60. package/build-module/components/dataviews/index.js +10 -14
  61. package/build-module/components/dataviews/index.js.map +1 -1
  62. package/build-module/components/dataviews-bulk-actions/index.js +145 -143
  63. package/build-module/components/dataviews-bulk-actions/index.js.map +1 -1
  64. package/build-module/components/dataviews-filters/add-filter.js +4 -6
  65. package/build-module/components/dataviews-filters/add-filter.js.map +1 -1
  66. package/build-module/components/dataviews-filters/index.js +3 -0
  67. package/build-module/components/dataviews-filters/index.js.map +1 -1
  68. package/build-module/components/dataviews-filters/search-widget.js +30 -23
  69. package/build-module/components/dataviews-filters/search-widget.js.map +1 -1
  70. package/build-module/components/dataviews-footer/index.js +38 -0
  71. package/build-module/components/dataviews-footer/index.js.map +1 -0
  72. package/build-module/components/dataviews-item-actions/index.js +5 -8
  73. package/build-module/components/dataviews-item-actions/index.js.map +1 -1
  74. package/build-module/components/dataviews-pagination/index.js +28 -20
  75. package/build-module/components/dataviews-pagination/index.js.map +1 -1
  76. package/build-module/components/dataviews-view-config/index.js +203 -47
  77. package/build-module/components/dataviews-view-config/index.js.map +1 -1
  78. package/build-module/dataform-controls/datetime.js +43 -0
  79. package/build-module/dataform-controls/datetime.js.map +1 -0
  80. package/build-module/dataform-controls/index.js +42 -0
  81. package/build-module/dataform-controls/index.js.map +1 -0
  82. package/build-module/dataform-controls/integer.js +38 -0
  83. package/build-module/dataform-controls/integer.js.map +1 -0
  84. package/build-module/dataform-controls/radio.js +38 -0
  85. package/build-module/dataform-controls/radio.js.map +1 -0
  86. package/build-module/dataform-controls/select.js +51 -0
  87. package/build-module/dataform-controls/select.js.map +1 -0
  88. package/build-module/dataform-controls/text.js +38 -0
  89. package/build-module/dataform-controls/text.js.map +1 -0
  90. package/build-module/dataforms-layouts/panel/index.js +10 -4
  91. package/build-module/dataforms-layouts/panel/index.js.map +1 -1
  92. package/build-module/dataforms-layouts/regular/index.js +6 -3
  93. package/build-module/dataforms-layouts/regular/index.js.map +1 -1
  94. package/build-module/dataviews-layouts/grid/density-picker.js +25 -56
  95. package/build-module/dataviews-layouts/grid/density-picker.js.map +1 -1
  96. package/build-module/dataviews-layouts/grid/index.js +1 -1
  97. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  98. package/build-module/dataviews-layouts/index.js +45 -1
  99. package/build-module/dataviews-layouts/index.js.map +1 -1
  100. package/build-module/dataviews-layouts/list/index.js +125 -80
  101. package/build-module/dataviews-layouts/list/index.js.map +1 -1
  102. package/build-module/dataviews-layouts/table/column-header-menu.js +52 -57
  103. package/build-module/dataviews-layouts/table/column-header-menu.js.map +1 -1
  104. package/build-module/dataviews-layouts/table/index.js +9 -37
  105. package/build-module/dataviews-layouts/table/index.js.map +1 -1
  106. package/build-module/field-types/datetime.js +24 -0
  107. package/build-module/field-types/datetime.js.map +1 -0
  108. package/build-module/field-types/index.js +4 -0
  109. package/build-module/field-types/index.js.map +1 -1
  110. package/build-module/field-types/integer.js +2 -60
  111. package/build-module/field-types/integer.js.map +1 -1
  112. package/build-module/field-types/text.js +2 -60
  113. package/build-module/field-types/text.js.map +1 -1
  114. package/build-module/normalize-fields.js +11 -9
  115. package/build-module/normalize-fields.js.map +1 -1
  116. package/build-module/types.js.map +1 -1
  117. package/build-style/style-rtl.css +93 -80
  118. package/build-style/style.css +93 -80
  119. package/build-types/components/dataform/stories/index.story.d.ts.map +1 -1
  120. package/build-types/components/dataviews/index.d.ts.map +1 -1
  121. package/build-types/components/dataviews/stories/fixtures.d.ts +28 -113
  122. package/build-types/components/dataviews/stories/fixtures.d.ts.map +1 -1
  123. package/build-types/components/dataviews/stories/index.story.d.ts +12 -44
  124. package/build-types/components/dataviews/stories/index.story.d.ts.map +1 -1
  125. package/build-types/components/dataviews-bulk-actions/index.d.ts +11 -1
  126. package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
  127. package/build-types/components/dataviews-filters/add-filter.d.ts.map +1 -1
  128. package/build-types/components/dataviews-filters/index.d.ts +1 -1
  129. package/build-types/components/dataviews-filters/index.d.ts.map +1 -1
  130. package/build-types/components/dataviews-filters/search-widget.d.ts.map +1 -1
  131. package/build-types/components/dataviews-footer/index.d.ts +2 -0
  132. package/build-types/components/dataviews-footer/index.d.ts.map +1 -0
  133. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  134. package/build-types/components/dataviews-pagination/index.d.ts.map +1 -1
  135. package/build-types/components/dataviews-view-config/index.d.ts +4 -3
  136. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  137. package/build-types/dataform-controls/datetime.d.ts +6 -0
  138. package/build-types/dataform-controls/datetime.d.ts.map +1 -0
  139. package/build-types/dataform-controls/index.d.ts +11 -0
  140. package/build-types/dataform-controls/index.d.ts.map +1 -0
  141. package/build-types/dataform-controls/integer.d.ts +6 -0
  142. package/build-types/dataform-controls/integer.d.ts.map +1 -0
  143. package/build-types/dataform-controls/radio.d.ts +6 -0
  144. package/build-types/dataform-controls/radio.d.ts.map +1 -0
  145. package/build-types/dataform-controls/select.d.ts +6 -0
  146. package/build-types/dataform-controls/select.d.ts.map +1 -0
  147. package/build-types/dataform-controls/text.d.ts +6 -0
  148. package/build-types/dataform-controls/text.d.ts.map +1 -0
  149. package/build-types/dataforms-layouts/panel/index.d.ts.map +1 -1
  150. package/build-types/dataforms-layouts/regular/index.d.ts.map +1 -1
  151. package/build-types/dataviews-layouts/grid/density-picker.d.ts.map +1 -1
  152. package/build-types/dataviews-layouts/index.d.ts +4 -2
  153. package/build-types/dataviews-layouts/index.d.ts.map +1 -1
  154. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  155. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  156. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  157. package/build-types/field-types/datetime.d.ts +13 -0
  158. package/build-types/field-types/datetime.d.ts.map +1 -0
  159. package/build-types/field-types/index.d.ts +1 -1
  160. package/build-types/field-types/index.d.ts.map +1 -1
  161. package/build-types/field-types/integer.d.ts +2 -3
  162. package/build-types/field-types/integer.d.ts.map +1 -1
  163. package/build-types/field-types/text.d.ts +2 -3
  164. package/build-types/field-types/text.d.ts.map +1 -1
  165. package/build-types/normalize-fields.d.ts.map +1 -1
  166. package/build-types/types.d.ts +43 -21
  167. package/build-types/types.d.ts.map +1 -1
  168. package/package.json +12 -12
  169. package/src/components/dataform/stories/index.story.tsx +43 -2
  170. package/src/components/dataviews/index.tsx +14 -18
  171. package/src/components/dataviews/stories/fixtures.tsx +690 -0
  172. package/src/components/dataviews/stories/index.story.tsx +164 -0
  173. package/src/components/dataviews/style.scss +2 -12
  174. package/src/components/dataviews-bulk-actions/index.tsx +264 -213
  175. package/src/components/dataviews-bulk-actions/style.scss +9 -4
  176. package/src/components/dataviews-filters/add-filter.tsx +7 -11
  177. package/src/components/dataviews-filters/index.tsx +3 -0
  178. package/src/components/dataviews-filters/search-widget.tsx +46 -25
  179. package/src/components/dataviews-filters/style.scss +13 -3
  180. package/src/components/dataviews-footer/index.tsx +50 -0
  181. package/src/components/dataviews-footer/style.scss +40 -0
  182. package/src/components/dataviews-item-actions/index.tsx +8 -14
  183. package/src/components/dataviews-pagination/index.tsx +40 -21
  184. package/src/components/dataviews-pagination/style.scss +7 -21
  185. package/src/components/dataviews-view-config/index.tsx +297 -69
  186. package/src/components/dataviews-view-config/style.scss +25 -0
  187. package/src/dataform-controls/datetime.tsx +43 -0
  188. package/src/dataform-controls/index.tsx +61 -0
  189. package/src/dataform-controls/integer.tsx +38 -0
  190. package/src/dataform-controls/radio.tsx +42 -0
  191. package/src/dataform-controls/select.tsx +52 -0
  192. package/src/dataform-controls/style.scss +4 -0
  193. package/src/dataform-controls/text.tsx +40 -0
  194. package/src/dataforms-layouts/panel/index.tsx +8 -2
  195. package/src/dataforms-layouts/regular/index.tsx +6 -2
  196. package/src/dataviews-layouts/grid/density-picker.tsx +33 -67
  197. package/src/dataviews-layouts/grid/index.tsx +1 -1
  198. package/src/dataviews-layouts/grid/style.scss +1 -5
  199. package/src/dataviews-layouts/index.ts +63 -2
  200. package/src/dataviews-layouts/list/index.tsx +199 -123
  201. package/src/dataviews-layouts/list/style.scss +10 -4
  202. package/src/dataviews-layouts/table/column-header-menu.tsx +86 -90
  203. package/src/dataviews-layouts/table/index.tsx +8 -65
  204. package/src/dataviews-layouts/table/style.scss +0 -5
  205. package/src/field-types/datetime.tsx +28 -0
  206. package/src/field-types/index.tsx +5 -0
  207. package/src/field-types/integer.tsx +2 -71
  208. package/src/field-types/text.tsx +2 -70
  209. package/src/normalize-fields.ts +10 -10
  210. package/src/style.scss +2 -1
  211. package/src/test/filter-and-sort-data-view.js +28 -0
  212. package/src/types.ts +56 -32
  213. package/tsconfig.tsbuildinfo +1 -1
  214. package/build/components/dataviews-bulk-actions-toolbar/index.js +0 -207
  215. package/build/components/dataviews-bulk-actions-toolbar/index.js.map +0 -1
  216. package/build-module/components/dataviews-bulk-actions-toolbar/index.js +0 -201
  217. package/build-module/components/dataviews-bulk-actions-toolbar/index.js.map +0 -1
  218. package/build-types/components/dataviews-bulk-actions-toolbar/index.d.ts +0 -2
  219. package/build-types/components/dataviews-bulk-actions-toolbar/index.d.ts.map +0 -1
  220. package/src/components/dataviews/stories/fixtures.js +0 -222
  221. package/src/components/dataviews/stories/index.story.js +0 -65
  222. package/src/components/dataviews-bulk-actions-toolbar/index.tsx +0 -288
  223. package/src/components/dataviews-bulk-actions-toolbar/style.scss +0 -45
@@ -2,14 +2,11 @@
2
2
  * External dependencies
3
3
  */
4
4
  import clsx from 'clsx';
5
- // Import CompositeStore type, which is not exported from @wordpress/components.
6
- // eslint-disable-next-line no-restricted-imports
7
- import type { CompositeStore } from '@ariakit/react';
8
5
 
9
6
  /**
10
7
  * WordPress dependencies
11
8
  */
12
- import { useInstanceId } from '@wordpress/compose';
9
+ import { useInstanceId, usePrevious } from '@wordpress/compose';
13
10
  import {
14
11
  __experimentalHStack as HStack,
15
12
  __experimentalVStack as VStack,
@@ -41,39 +38,115 @@ import type { Action, NormalizedField, ViewListProps } from '../../types';
41
38
 
42
39
  interface ListViewItemProps< Item > {
43
40
  actions: Action< Item >[];
44
- id?: string;
41
+ idPrefix: string;
45
42
  isSelected: boolean;
46
43
  item: Item;
47
44
  mediaField?: NormalizedField< Item >;
48
45
  onSelect: ( item: Item ) => void;
49
46
  primaryField?: NormalizedField< Item >;
50
- store: CompositeStore;
51
47
  visibleFields: NormalizedField< Item >[];
48
+ onDropdownTriggerKeyDown: React.KeyboardEventHandler< HTMLButtonElement >;
52
49
  }
53
50
 
54
51
  const {
55
- useCompositeStoreV2: useCompositeStore,
56
52
  CompositeV2: Composite,
57
53
  CompositeItemV2: CompositeItem,
58
54
  CompositeRowV2: CompositeRow,
59
55
  DropdownMenuV2: DropdownMenu,
60
56
  } = unlock( componentsPrivateApis );
61
57
 
58
+ function generateItemWrapperCompositeId( idPrefix: string ) {
59
+ return `${ idPrefix }-item-wrapper`;
60
+ }
61
+ function generatePrimaryActionCompositeId(
62
+ idPrefix: string,
63
+ primaryActionId: string
64
+ ) {
65
+ return `${ idPrefix }-primary-action-${ primaryActionId }`;
66
+ }
67
+ function generateDropdownTriggerCompositeId( idPrefix: string ) {
68
+ return `${ idPrefix }-dropdown`;
69
+ }
70
+
71
+ function PrimaryActionGridCell< Item >( {
72
+ idPrefix,
73
+ primaryAction,
74
+ item,
75
+ }: {
76
+ idPrefix: string;
77
+ primaryAction: Action< Item >;
78
+ item: Item;
79
+ } ) {
80
+ const registry = useRegistry();
81
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
82
+
83
+ const compositeItemId = generatePrimaryActionCompositeId(
84
+ idPrefix,
85
+ primaryAction.id
86
+ );
87
+
88
+ const label =
89
+ typeof primaryAction.label === 'string'
90
+ ? primaryAction.label
91
+ : primaryAction.label( [ item ] );
92
+
93
+ return 'RenderModal' in primaryAction ? (
94
+ <div role="gridcell" key={ primaryAction.id }>
95
+ <CompositeItem
96
+ id={ compositeItemId }
97
+ render={
98
+ <Button
99
+ label={ label }
100
+ icon={ primaryAction.icon }
101
+ isDestructive={ primaryAction.isDestructive }
102
+ size="small"
103
+ onClick={ () => setIsModalOpen( true ) }
104
+ />
105
+ }
106
+ >
107
+ { isModalOpen && (
108
+ <ActionModal< Item >
109
+ action={ primaryAction }
110
+ items={ [ item ] }
111
+ closeModal={ () => setIsModalOpen( false ) }
112
+ />
113
+ ) }
114
+ </CompositeItem>
115
+ </div>
116
+ ) : (
117
+ <div role="gridcell" key={ primaryAction.id }>
118
+ <CompositeItem
119
+ id={ compositeItemId }
120
+ render={
121
+ <Button
122
+ label={ label }
123
+ icon={ primaryAction.icon }
124
+ isDestructive={ primaryAction.isDestructive }
125
+ size="small"
126
+ onClick={ () => {
127
+ primaryAction.callback( [ item ], { registry } );
128
+ } }
129
+ />
130
+ }
131
+ />
132
+ </div>
133
+ );
134
+ }
135
+
62
136
  function ListItem< Item >( {
63
137
  actions,
64
- id,
138
+ idPrefix,
65
139
  isSelected,
66
140
  item,
67
141
  mediaField,
68
142
  onSelect,
69
143
  primaryField,
70
- store,
71
144
  visibleFields,
145
+ onDropdownTriggerKeyDown,
72
146
  }: ListViewItemProps< Item > ) {
73
- const registry = useRegistry();
74
147
  const itemRef = useRef< HTMLElement >( null );
75
- const labelId = `${ id }-label`;
76
- const descriptionId = `${ id }-description`;
148
+ const labelId = `${ idPrefix }-label`;
149
+ const descriptionId = `${ idPrefix }-description`;
77
150
 
78
151
  const [ isHovered, setIsHovered ] = useState( false );
79
152
  const handleMouseEnter = () => {
@@ -108,13 +181,6 @@ function ListItem< Item >( {
108
181
  };
109
182
  }, [ actions, item ] );
110
183
 
111
- const [ isModalOpen, setIsModalOpen ] = useState( false );
112
- const primaryActionLabel =
113
- primaryAction &&
114
- ( typeof primaryAction.label === 'string'
115
- ? primaryAction.label
116
- : primaryAction.label( [ item ] ) );
117
-
118
184
  const renderedMediaField = mediaField?.render ? (
119
185
  <mediaField.render item={ item } />
120
186
  ) : (
@@ -144,10 +210,9 @@ function ListItem< Item >( {
144
210
  >
145
211
  <div role="gridcell">
146
212
  <CompositeItem
147
- store={ store }
148
213
  render={ <div /> }
149
214
  role="button"
150
- id={ id }
215
+ id={ generateItemWrapperCompositeId( idPrefix ) }
151
216
  aria-pressed={ isSelected }
152
217
  aria-labelledby={ labelId }
153
218
  aria-describedby={ descriptionId }
@@ -207,65 +272,20 @@ function ListItem< Item >( {
207
272
  width: 'auto',
208
273
  } }
209
274
  >
210
- { primaryAction && 'RenderModal' in primaryAction && (
211
- <div role="gridcell">
212
- <CompositeItem
213
- store={ store }
214
- render={
215
- <Button
216
- label={ primaryActionLabel }
217
- icon={ primaryAction.icon }
218
- isDestructive={
219
- primaryAction.isDestructive
220
- }
221
- size="small"
222
- onClick={ () =>
223
- setIsModalOpen( true )
224
- }
225
- />
226
- }
227
- >
228
- { isModalOpen && (
229
- <ActionModal< Item >
230
- action={ primaryAction }
231
- items={ [ item ] }
232
- closeModal={ () =>
233
- setIsModalOpen( false )
234
- }
235
- />
236
- ) }
237
- </CompositeItem>
238
- </div>
275
+ { primaryAction && (
276
+ <PrimaryActionGridCell
277
+ idPrefix={ idPrefix }
278
+ primaryAction={ primaryAction }
279
+ item={ item }
280
+ />
239
281
  ) }
240
- { primaryAction &&
241
- ! ( 'RenderModal' in primaryAction ) && (
242
- <div role="gridcell" key={ primaryAction.id }>
243
- <CompositeItem
244
- store={ store }
245
- render={
246
- <Button
247
- label={ primaryActionLabel }
248
- icon={ primaryAction.icon }
249
- isDestructive={
250
- primaryAction.isDestructive
251
- }
252
- size="small"
253
- onClick={ () => {
254
- primaryAction.callback(
255
- [ item ],
256
- { registry }
257
- );
258
- } }
259
- />
260
- }
261
- />
262
- </div>
263
- ) }
264
282
  <div role="gridcell">
265
283
  <DropdownMenu
266
284
  trigger={
267
285
  <CompositeItem
268
- store={ store }
286
+ id={ generateDropdownTriggerCompositeId(
287
+ idPrefix
288
+ ) }
269
289
  render={
270
290
  <Button
271
291
  size="small"
@@ -273,30 +293,9 @@ function ListItem< Item >( {
273
293
  label={ __( 'Actions' ) }
274
294
  accessibleWhenDisabled
275
295
  disabled={ ! actions.length }
276
- onKeyDown={ ( event: {
277
- key: string;
278
- preventDefault: () => void;
279
- } ) => {
280
- if (
281
- event.key ===
282
- 'ArrowDown'
283
- ) {
284
- // Prevent the default behaviour (open dropdown menu) and go down.
285
- event.preventDefault();
286
- store.move(
287
- store.down()
288
- );
289
- }
290
- if (
291
- event.key === 'ArrowUp'
292
- ) {
293
- // Prevent the default behavior (open dropdown menu) and go up.
294
- event.preventDefault();
295
- store.move(
296
- store.up()
297
- );
298
- }
299
- } }
296
+ onKeyDown={
297
+ onDropdownTriggerKeyDown
298
+ }
300
299
  />
301
300
  }
302
301
  />
@@ -328,6 +327,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
328
327
  view,
329
328
  } = props;
330
329
  const baseId = useInstanceId( ViewList, 'view-list' );
330
+
331
331
  const selectedItem = data?.findLast( ( item ) =>
332
332
  selection.includes( getItemId( item ) )
333
333
  );
@@ -350,33 +350,108 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
350
350
  const onSelect = ( item: Item ) =>
351
351
  onChangeSelection( [ getItemId( item ) ] );
352
352
 
353
- const getItemDomId = useCallback(
354
- ( item?: Item ) =>
355
- item ? `${ baseId }-${ getItemId( item ) }` : undefined,
353
+ const generateCompositeItemIdPrefix = useCallback(
354
+ ( item: Item ) => `${ baseId }-${ getItemId( item ) }`,
356
355
  [ baseId, getItemId ]
357
356
  );
358
357
 
359
- const store = useCompositeStore( {
360
- defaultActiveId: getItemDomId( selectedItem ),
361
- } );
358
+ const isActiveCompositeItem = useCallback(
359
+ ( item: Item, idToCheck: string ) => {
360
+ // All composite items use the same prefix in their IDs.
361
+ return idToCheck.startsWith(
362
+ generateCompositeItemIdPrefix( item )
363
+ );
364
+ },
365
+ [ generateCompositeItemIdPrefix ]
366
+ );
362
367
 
363
- // Manage focused item, when the active one is removed from the list.
364
- const isActiveIdInList = store.useState(
365
- ( state: { items: any[]; activeId: any } ) =>
366
- state.items.some(
367
- ( item: { id: any } ) => item.id === state.activeId
368
- )
368
+ // Controlled state for the active composite item.
369
+ const [ activeCompositeId, setActiveCompositeId ] = useState<
370
+ string | null | undefined
371
+ >( undefined );
372
+
373
+ // Update the active composite item when the selected item changes.
374
+ useEffect( () => {
375
+ if ( selectedItem ) {
376
+ setActiveCompositeId(
377
+ generateItemWrapperCompositeId(
378
+ generateCompositeItemIdPrefix( selectedItem )
379
+ )
380
+ );
381
+ }
382
+ }, [ selectedItem, generateCompositeItemIdPrefix ] );
383
+
384
+ const activeItemIndex = data.findIndex( ( item ) =>
385
+ isActiveCompositeItem( item, activeCompositeId ?? '' )
369
386
  );
387
+ const previousActiveItemIndex = usePrevious( activeItemIndex );
388
+ const isActiveIdInList = activeItemIndex !== -1;
389
+
390
+ const selectCompositeItem = useCallback(
391
+ (
392
+ targetIndex: number,
393
+ // Allows invokers to specify a custom function to generate the
394
+ // target composite item ID
395
+ generateCompositeId: ( idPrefix: string ) => string
396
+ ) => {
397
+ // Clamping between 0 and data.length - 1 to avoid out of bounds.
398
+ const clampedIndex = Math.min(
399
+ data.length - 1,
400
+ Math.max( 0, targetIndex )
401
+ );
402
+ const itemIdPrefix = generateCompositeItemIdPrefix(
403
+ data[ clampedIndex ]
404
+ );
405
+ const targetCompositeItemId = generateCompositeId( itemIdPrefix );
406
+
407
+ setActiveCompositeId( targetCompositeItemId );
408
+ document.getElementById( targetCompositeItemId )?.focus();
409
+ },
410
+ [ data, generateCompositeItemIdPrefix ]
411
+ );
412
+
413
+ // Select a new active composite item when the current active item
414
+ // is removed from the list.
370
415
  useEffect( () => {
371
- if ( ! isActiveIdInList ) {
372
- // Prefer going down, except if there is no item below (last item), then go up (last item in list).
373
- if ( store.down() ) {
374
- store.move( store.down() );
375
- } else if ( store.up() ) {
376
- store.move( store.up() );
377
- }
416
+ const wasActiveIdInList =
417
+ previousActiveItemIndex !== undefined &&
418
+ previousActiveItemIndex !== -1;
419
+ if ( ! isActiveIdInList && wasActiveIdInList ) {
420
+ // By picking `previousActiveItemIndex` as the next item index, we are
421
+ // basically picking the item that would have been after the deleted one.
422
+ // If the previously active (and removed) item was the last of the list,
423
+ // we will select the item before it — which is the new last item.
424
+ selectCompositeItem(
425
+ previousActiveItemIndex,
426
+ generateItemWrapperCompositeId
427
+ );
378
428
  }
379
- }, [ isActiveIdInList ] );
429
+ }, [ isActiveIdInList, selectCompositeItem, previousActiveItemIndex ] );
430
+
431
+ // Prevent the default behavior (open dropdown menu) and instead select the
432
+ // dropdown menu trigger on the previous/next row.
433
+ // https://github.com/ariakit/ariakit/issues/3768
434
+ const onDropdownTriggerKeyDown = useCallback(
435
+ ( event: React.KeyboardEvent< HTMLButtonElement > ) => {
436
+ if ( event.key === 'ArrowDown' ) {
437
+ // Select the dropdown menu trigger item in the next row.
438
+ event.preventDefault();
439
+ selectCompositeItem(
440
+ activeItemIndex + 1,
441
+ generateDropdownTriggerCompositeId
442
+ );
443
+ }
444
+ if ( event.key === 'ArrowUp' ) {
445
+ // Select the dropdown menu trigger item in the previous row.
446
+ event.preventDefault();
447
+ selectCompositeItem(
448
+ activeItemIndex - 1,
449
+ generateDropdownTriggerCompositeId
450
+ );
451
+ }
452
+ },
453
+ [ selectCompositeItem, activeItemIndex ]
454
+ );
380
455
 
381
456
  const hasData = data?.length;
382
457
  if ( ! hasData ) {
@@ -400,22 +475,23 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
400
475
  render={ <ul /> }
401
476
  className="dataviews-view-list"
402
477
  role="grid"
403
- store={ store }
478
+ activeId={ activeCompositeId }
479
+ setActiveId={ setActiveCompositeId }
404
480
  >
405
481
  { data.map( ( item ) => {
406
- const id = getItemDomId( item );
482
+ const id = generateCompositeItemIdPrefix( item );
407
483
  return (
408
484
  <ListItem
409
485
  key={ id }
410
- id={ id }
486
+ idPrefix={ id }
411
487
  actions={ actions }
412
488
  item={ item }
413
489
  isSelected={ item === selectedItem }
414
490
  onSelect={ onSelect }
415
491
  mediaField={ mediaField }
416
492
  primaryField={ primaryField }
417
- store={ store }
418
493
  visibleFields={ visibleFields }
494
+ onDropdownTriggerKeyDown={ onDropdownTriggerKeyDown }
419
495
  />
420
496
  );
421
497
  } ) }
@@ -1,3 +1,7 @@
1
+ ul.dataviews-view-list {
2
+ list-style-type: none;
3
+ }
4
+
1
5
  .dataviews-view-list {
2
6
  margin: 0 0 auto;
3
7
 
@@ -73,6 +77,7 @@
73
77
  color: $gray-900;
74
78
  }
75
79
  &:hover,
80
+ &.is-hovered,
76
81
  &:focus-within {
77
82
  color: var(--wp-admin-theme-color);
78
83
  background-color: #f8f8f8;
@@ -100,6 +105,7 @@
100
105
  }
101
106
 
102
107
  .dataviews-view-list__item {
108
+ box-sizing: border-box;
103
109
  padding: $grid-unit-20 $grid-unit-30;
104
110
  width: 100%;
105
111
  scroll-margin: $grid-unit-10 0;
@@ -108,12 +114,12 @@
108
114
  &::before {
109
115
  position: absolute;
110
116
  content: "";
111
- top: calc(var(--wp-admin-border-width-focus) + 1px);
117
+ top: var(--wp-admin-border-width-focus);
112
118
  right: var(--wp-admin-border-width-focus);
113
119
  bottom: var(--wp-admin-border-width-focus);
114
120
  left: var(--wp-admin-border-width-focus);
115
121
  box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
116
- border-radius: $radius-block-ui;
122
+ border-radius: $radius-small;
117
123
  }
118
124
  }
119
125
  .dataviews-view-list__primary-field {
@@ -151,8 +157,8 @@
151
157
  }
152
158
 
153
159
  .dataviews-view-list__media-placeholder {
154
- min-width: $grid-unit-40;
155
- height: $grid-unit-40;
160
+ width: $grid-unit-05 * 13;
161
+ height: $grid-unit-05 * 13;
156
162
  background-color: $gray-200;
157
163
  }
158
164