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