@wordpress/dataviews 12.1.1-next.v.202602241322.0 → 13.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 (132) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +132 -14
  3. package/build/components/dataform-layouts/card/index.cjs +12 -7
  4. package/build/components/dataform-layouts/card/index.cjs.map +2 -2
  5. package/build/components/dataform-layouts/panel/utils/get-label-content.cjs +2 -2
  6. package/build/components/dataform-layouts/panel/utils/get-label-content.cjs.map +2 -2
  7. package/build/components/dataviews-context/index.cjs +1 -0
  8. package/build/components/dataviews-context/index.cjs.map +2 -2
  9. package/build/components/dataviews-filters/toggle.cjs +6 -2
  10. package/build/components/dataviews-filters/toggle.cjs.map +2 -2
  11. package/build/components/dataviews-footer/index.cjs +28 -12
  12. package/build/components/dataviews-footer/index.cjs.map +3 -3
  13. package/build/components/dataviews-item-actions/index.cjs +0 -1
  14. package/build/components/dataviews-item-actions/index.cjs.map +2 -2
  15. package/build/components/dataviews-layout/index.cjs +4 -0
  16. package/build/components/dataviews-layout/index.cjs.map +2 -2
  17. package/build/components/dataviews-layouts/activity/index.cjs +41 -26
  18. package/build/components/dataviews-layouts/activity/index.cjs.map +3 -3
  19. package/build/components/dataviews-layouts/grid/composite-grid.cjs +2 -0
  20. package/build/components/dataviews-layouts/grid/composite-grid.cjs.map +2 -2
  21. package/build/components/dataviews-layouts/grid/index.cjs +19 -14
  22. package/build/components/dataviews-layouts/grid/index.cjs.map +3 -3
  23. package/build/components/dataviews-layouts/list/index.cjs +25 -12
  24. package/build/components/dataviews-layouts/list/index.cjs.map +2 -2
  25. package/build/components/dataviews-layouts/table/index.cjs +40 -19
  26. package/build/components/dataviews-layouts/table/index.cjs.map +2 -2
  27. package/build/dataviews/index.cjs +9 -2
  28. package/build/dataviews/index.cjs.map +3 -3
  29. package/build/dataviews-picker/index.cjs +1 -0
  30. package/build/dataviews-picker/index.cjs.map +2 -2
  31. package/build/hooks/use-data.cjs +46 -0
  32. package/build/hooks/use-data.cjs.map +7 -0
  33. package/build/hooks/use-delayed-loading.cjs +47 -0
  34. package/build/hooks/use-delayed-loading.cjs.map +7 -0
  35. package/build-module/components/dataform-layouts/card/index.mjs +12 -7
  36. package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
  37. package/build-module/components/dataform-layouts/panel/utils/get-label-content.mjs +3 -3
  38. package/build-module/components/dataform-layouts/panel/utils/get-label-content.mjs.map +2 -2
  39. package/build-module/components/dataviews-context/index.mjs +1 -0
  40. package/build-module/components/dataviews-context/index.mjs.map +2 -2
  41. package/build-module/components/dataviews-filters/toggle.mjs +6 -2
  42. package/build-module/components/dataviews-filters/toggle.mjs.map +2 -2
  43. package/build-module/components/dataviews-footer/index.mjs +28 -12
  44. package/build-module/components/dataviews-footer/index.mjs.map +2 -2
  45. package/build-module/components/dataviews-item-actions/index.mjs +0 -1
  46. package/build-module/components/dataviews-item-actions/index.mjs.map +2 -2
  47. package/build-module/components/dataviews-layout/index.mjs +4 -0
  48. package/build-module/components/dataviews-layout/index.mjs.map +2 -2
  49. package/build-module/components/dataviews-layouts/activity/index.mjs +41 -26
  50. package/build-module/components/dataviews-layouts/activity/index.mjs.map +2 -2
  51. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs +2 -0
  52. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs.map +2 -2
  53. package/build-module/components/dataviews-layouts/grid/index.mjs +19 -14
  54. package/build-module/components/dataviews-layouts/grid/index.mjs.map +2 -2
  55. package/build-module/components/dataviews-layouts/list/index.mjs +25 -12
  56. package/build-module/components/dataviews-layouts/list/index.mjs.map +2 -2
  57. package/build-module/components/dataviews-layouts/table/index.mjs +40 -19
  58. package/build-module/components/dataviews-layouts/table/index.mjs.map +2 -2
  59. package/build-module/dataviews/index.mjs +9 -2
  60. package/build-module/dataviews/index.mjs.map +2 -2
  61. package/build-module/dataviews-picker/index.mjs +1 -0
  62. package/build-module/dataviews-picker/index.mjs.map +2 -2
  63. package/build-module/hooks/use-data.mjs +25 -0
  64. package/build-module/hooks/use-data.mjs.map +7 -0
  65. package/build-module/hooks/use-delayed-loading.mjs +22 -0
  66. package/build-module/hooks/use-delayed-loading.mjs.map +7 -0
  67. package/build-style/style-rtl.css +91 -19
  68. package/build-style/style.css +91 -19
  69. package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
  70. package/build-types/components/dataform-layouts/panel/utils/get-label-content.d.ts +1 -1
  71. package/build-types/components/dataform-layouts/panel/utils/get-label-content.d.ts.map +1 -1
  72. package/build-types/components/dataviews-context/index.d.ts +1 -0
  73. package/build-types/components/dataviews-context/index.d.ts.map +1 -1
  74. package/build-types/components/dataviews-filters/toggle.d.ts.map +1 -1
  75. package/build-types/components/dataviews-footer/index.d.ts +1 -1
  76. package/build-types/components/dataviews-footer/index.d.ts.map +1 -1
  77. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  78. package/build-types/components/dataviews-layout/index.d.ts +1 -1
  79. package/build-types/components/dataviews-layout/index.d.ts.map +1 -1
  80. package/build-types/components/dataviews-layouts/activity/index.d.ts.map +1 -1
  81. package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts +2 -1
  82. package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts.map +1 -1
  83. package/build-types/components/dataviews-layouts/grid/index.d.ts.map +1 -1
  84. package/build-types/components/dataviews-layouts/list/index.d.ts.map +1 -1
  85. package/build-types/components/dataviews-layouts/table/index.d.ts.map +1 -1
  86. package/build-types/dataform/stories/layout-panel.d.ts.map +1 -1
  87. package/build-types/dataviews/index.d.ts.map +1 -1
  88. package/build-types/dataviews/stories/empty.d.ts.map +1 -1
  89. package/build-types/dataviews/stories/free-composition.d.ts.map +1 -1
  90. package/build-types/dataviews/stories/layout-table.d.ts.map +1 -1
  91. package/build-types/dataviews/stories/minimal-ui.d.ts.map +1 -1
  92. package/build-types/dataviews/stories/with-card.d.ts.map +1 -1
  93. package/build-types/dataviews-picker/index.d.ts.map +1 -1
  94. package/build-types/hooks/use-data.d.ts +12 -0
  95. package/build-types/hooks/use-data.d.ts.map +1 -0
  96. package/build-types/hooks/use-delayed-loading.d.ts +4 -0
  97. package/build-types/hooks/use-delayed-loading.d.ts.map +1 -0
  98. package/build-wp/index.js +263 -135
  99. package/package.json +16 -19
  100. package/src/components/dataform-layouts/card/index.tsx +8 -9
  101. package/src/components/dataform-layouts/card/style.scss +1 -0
  102. package/src/components/dataform-layouts/panel/style.scss +2 -0
  103. package/src/components/dataform-layouts/panel/utils/get-label-content.tsx +3 -5
  104. package/src/components/dataform-layouts/regular/style.scss +3 -2
  105. package/src/components/dataviews-context/index.ts +2 -0
  106. package/src/components/dataviews-filters/toggle.tsx +9 -2
  107. package/src/components/dataviews-footer/index.tsx +39 -12
  108. package/src/components/dataviews-footer/style.scss +6 -1
  109. package/src/components/dataviews-item-actions/index.tsx +0 -3
  110. package/src/components/dataviews-item-actions/style.scss +7 -0
  111. package/src/components/dataviews-layout/index.tsx +5 -0
  112. package/src/components/dataviews-layouts/activity/index.tsx +29 -22
  113. package/src/components/dataviews-layouts/activity/style.scss +5 -0
  114. package/src/components/dataviews-layouts/grid/composite-grid.tsx +4 -0
  115. package/src/components/dataviews-layouts/grid/index.tsx +19 -22
  116. package/src/components/dataviews-layouts/grid/style.scss +5 -0
  117. package/src/components/dataviews-layouts/list/index.tsx +39 -21
  118. package/src/components/dataviews-layouts/list/style.scss +5 -9
  119. package/src/components/dataviews-layouts/table/index.tsx +48 -22
  120. package/src/components/dataviews-layouts/table/style.scss +6 -0
  121. package/src/dataform/stories/layout-card.tsx +2 -2
  122. package/src/dataform/stories/layout-panel.tsx +5 -1
  123. package/src/dataviews/index.tsx +10 -2
  124. package/src/dataviews/stories/empty.tsx +1 -7
  125. package/src/dataviews/stories/free-composition.tsx +0 -5
  126. package/src/dataviews/stories/layout-table.tsx +1 -7
  127. package/src/dataviews/stories/minimal-ui.tsx +0 -5
  128. package/src/dataviews/stories/with-card.tsx +1 -7
  129. package/src/dataviews/style.scss +25 -0
  130. package/src/dataviews-picker/index.tsx +1 -0
  131. package/src/hooks/use-data.ts +45 -0
  132. package/src/hooks/use-delayed-loading.ts +21 -0
@@ -33,6 +33,7 @@ import { Stack } from '@wordpress/ui';
33
33
  import { unlock } from '../../../lock-unlock';
34
34
  import { ActionsMenuGroup, ActionModal } from '../../dataviews-item-actions';
35
35
  import DataViewsContext from '../../dataviews-context';
36
+ import { useDelayedLoading } from '../../../hooks/use-delayed-loading';
36
37
  import type {
37
38
  Action,
38
39
  NormalizedField,
@@ -216,6 +217,10 @@ function ListItem< Item >( {
216
217
  <titleField.render item={ item } field={ titleField } />
217
218
  ) : null;
218
219
 
220
+ const renderDescription = showDescription && descriptionField?.render;
221
+ // When we have only the media and title fields, we want to center them vertically in the list item.
222
+ const hasOnlyMediaAndTitle =
223
+ !! renderedMediaField && ! renderDescription && ! otherFields.length;
219
224
  const usedActions = eligibleActions?.length > 0 && (
220
225
  <Stack
221
226
  direction="row"
@@ -314,7 +319,7 @@ function ListItem< Item >( {
314
319
  direction="row"
315
320
  gap="md"
316
321
  justify="start"
317
- align="flex-start"
322
+ align={ hasOnlyMediaAndTitle ? 'center' : 'flex-start' }
318
323
  style={ { flex: 1, minWidth: 0 } }
319
324
  >
320
325
  { renderedMediaField }
@@ -332,7 +337,7 @@ function ListItem< Item >( {
332
337
  </div>
333
338
  { usedActions }
334
339
  </Stack>
335
- { showDescription && descriptionField?.render && (
340
+ { renderDescription && (
336
341
  <div className="dataviews-view-list__field">
337
342
  <descriptionField.render
338
343
  item={ item }
@@ -389,6 +394,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
389
394
  empty,
390
395
  } = props;
391
396
  const baseId = useInstanceId( ViewList, 'view-list' );
397
+ const isDelayedLoading = useDelayedLoading( !! isLoading );
392
398
 
393
399
  const selectedItem = data?.findLast( ( item ) =>
394
400
  selection.includes( getItemId( item ) )
@@ -425,6 +431,8 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
425
431
  string | null | undefined
426
432
  >( undefined );
427
433
 
434
+ const compositeRef = useRef< HTMLDivElement >( null );
435
+
428
436
  // Update the active composite item when the selected item changes.
429
437
  useEffect( () => {
430
438
  if ( selectedItem ) {
@@ -463,7 +471,17 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
463
471
  const targetCompositeItemId = generateCompositeId( itemIdPrefix );
464
472
 
465
473
  setActiveCompositeId( targetCompositeItemId );
466
- document.getElementById( targetCompositeItemId )?.focus();
474
+ // The active composite item is controlled state that
475
+ // can update without needing a focus move (e.g., searching
476
+ // can trigger an active ID update). Only move DOM focus
477
+ // when it's already within the list.
478
+ if (
479
+ compositeRef.current?.contains(
480
+ compositeRef.current.ownerDocument.activeElement
481
+ )
482
+ ) {
483
+ document.getElementById( targetCompositeItemId )?.focus();
484
+ }
467
485
  },
468
486
  [ data, generateCompositeItemIdPrefix ]
469
487
  );
@@ -511,36 +529,30 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
511
529
  [ selectCompositeItem, activeItemIndex ]
512
530
  );
513
531
 
514
- const hasData = data?.length;
532
+ const hasData = !! data?.length;
533
+ const groupField = view.groupBy?.field
534
+ ? fields.find( ( field ) => field.id === view.groupBy?.field )
535
+ : null;
536
+ const dataByGroup =
537
+ hasData && groupField ? getDataByGroup( data, groupField ) : null;
538
+ const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
515
539
  if ( ! hasData ) {
516
540
  return (
517
541
  <div
518
- className={ clsx( {
519
- 'dataviews-loading': isLoading,
520
- 'dataviews-no-results': ! hasData && ! isLoading,
542
+ className={ clsx( 'dataviews-no-results', {
543
+ 'is-refreshing': isDelayedLoading,
521
544
  } ) }
522
545
  >
523
- { ! hasData &&
524
- ( isLoading ? (
525
- <p>
526
- <Spinner />
527
- </p>
528
- ) : (
529
- empty
530
- ) ) }
546
+ { empty }
531
547
  </div>
532
548
  );
533
549
  }
534
550
 
535
- const groupField = view.groupBy?.field
536
- ? fields.find( ( field ) => field.id === view.groupBy?.field )
537
- : null;
538
- const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
539
-
540
551
  // Render data grouped by field
541
552
  if ( hasData && groupField && dataByGroup ) {
542
553
  return (
543
554
  <Composite
555
+ ref={ compositeRef }
544
556
  id={ `${ baseId }` }
545
557
  render={ <div /> }
546
558
  className="dataviews-view-list__group"
@@ -606,6 +618,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
606
618
  return (
607
619
  <>
608
620
  <Composite
621
+ ref={ compositeRef }
609
622
  id={ baseId }
610
623
  render={ <div /> }
611
624
  className={ clsx( 'dataviews-view-list', className, {
@@ -614,10 +627,15 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
614
627
  [ 'compact', 'comfortable' ].includes(
615
628
  view.layout.density
616
629
  ),
630
+ 'is-refreshing': ! isInfiniteScroll && isDelayedLoading,
617
631
  } ) }
618
632
  role={ view.infiniteScrollEnabled ? 'feed' : 'grid' }
619
633
  activeId={ activeCompositeId }
620
634
  setActiveId={ setActiveCompositeId }
635
+ // @ts-ignore
636
+ inert={
637
+ ! isInfiniteScroll && !! isLoading ? 'true' : undefined
638
+ }
621
639
  >
622
640
  { data.map( ( item, index ) => {
623
641
  const id = generateCompositeItemIdPrefix( item );
@@ -646,7 +664,7 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) {
646
664
  );
647
665
  } ) }
648
666
  </Composite>
649
- { hasData && isLoading && (
667
+ { isInfiniteScroll && isLoading && (
650
668
  <p className="dataviews-loading-more">
651
669
  <Spinner />
652
670
  </p>
@@ -1,5 +1,6 @@
1
1
  @use "@wordpress/base-styles/colors" as *;
2
2
  @use "@wordpress/base-styles/variables" as *;
3
+ @use "../../../dataviews/style" as *;
3
4
 
4
5
  div.dataviews-view-list {
5
6
  list-style-type: none;
@@ -169,7 +170,6 @@ div.dataviews-view-list {
169
170
  }
170
171
 
171
172
  .dataviews-view-list__field-wrapper {
172
- min-height: $grid-unit-05 * 13; // Ensures title is centrally aligned when all fields are hidden
173
173
  flex-grow: 1;
174
174
  min-width: 0;
175
175
  }
@@ -224,10 +224,6 @@ div.dataviews-view-list {
224
224
  height: $grid-unit-05 * 8;
225
225
  }
226
226
 
227
- .dataviews-view-list__field-wrapper {
228
- min-height: $grid-unit-05 * 8;
229
- }
230
-
231
227
  .dataviews-view-list__fields {
232
228
  gap: $grid-unit-10;
233
229
  row-gap: $grid-unit-05;
@@ -256,10 +252,6 @@ div.dataviews-view-list {
256
252
  height: $grid-unit-05 * 16;
257
253
  }
258
254
 
259
- .dataviews-view-list__field-wrapper {
260
- min-height: $grid-unit-05 * 16;
261
- }
262
-
263
255
  .dataviews-view-list__fields {
264
256
  gap: $grid-unit-20;
265
257
  row-gap: $grid-unit-10;
@@ -271,6 +263,10 @@ div.dataviews-view-list {
271
263
  }
272
264
  }
273
265
  }
266
+
267
+ &.is-refreshing {
268
+ @include dataviews-refreshing();
269
+ }
274
270
  }
275
271
 
276
272
  .dataviews-view-list__group-header {
@@ -42,6 +42,20 @@ import ColumnPrimary from './column-primary';
42
42
  import { useIsHorizontalScrollEnd } from './use-is-horizontal-scroll-end';
43
43
  import getDataByGroup from '../utils/get-data-by-group';
44
44
  import { PropertiesSection } from '../../dataviews-view-config/properties-section';
45
+ import { useDelayedLoading } from '../../../hooks/use-delayed-loading';
46
+
47
+ function getEffectiveAlign(
48
+ explicitAlign: 'start' | 'center' | 'end' | undefined,
49
+ fieldType: string | undefined
50
+ ): 'start' | 'center' | 'end' | undefined {
51
+ if ( explicitAlign ) {
52
+ return explicitAlign;
53
+ }
54
+ if ( fieldType === 'integer' || fieldType === 'number' ) {
55
+ return 'end';
56
+ }
57
+ return undefined;
58
+ }
45
59
 
46
60
  interface TableColumnFieldProps< Item > {
47
61
  fields: NormalizedField< Item >[];
@@ -225,6 +239,8 @@ function TableRow< Item >( {
225
239
  // Explicit picks the supported styles.
226
240
  const { width, maxWidth, minWidth, align } =
227
241
  view.layout?.styles?.[ column ] ?? {};
242
+ const field = fields.find( ( f ) => f.id === column );
243
+ const effectiveAlign = getEffectiveAlign( align, field?.type );
228
244
 
229
245
  return (
230
246
  <td
@@ -239,7 +255,7 @@ function TableRow< Item >( {
239
255
  fields={ fields }
240
256
  item={ item }
241
257
  column={ column }
242
- align={ align }
258
+ align={ effectiveAlign }
243
259
  />
244
260
  </td>
245
261
  );
@@ -287,13 +303,13 @@ function ViewTable< Item >( {
287
303
  empty,
288
304
  }: ViewTableProps< Item > ) {
289
305
  const { containerRef } = useContext( DataViewsContext );
306
+ const isDelayedLoading = useDelayedLoading( isLoading );
290
307
  const headerMenuRefs = useRef<
291
308
  Map< string, { node: HTMLButtonElement; fallback: string } >
292
309
  >( new Map() );
293
310
  const headerMenuToFocusRef = useRef< HTMLButtonElement >( undefined );
294
311
  const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] =
295
312
  useState< HTMLButtonElement >();
296
- const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
297
313
  const [ contextMenuAnchor, setContextMenuAnchor ] = useState< {
298
314
  getBoundingClientRect: () => DOMRect;
299
315
  } | null >( null );
@@ -312,6 +328,8 @@ function ViewTable< Item >( {
312
328
  enabled: !! actions?.length,
313
329
  } );
314
330
 
331
+ const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
332
+
315
333
  if ( nextHeaderMenuToFocus ) {
316
334
  // If we need to force focus, we short-circuit rendering here
317
335
  // to prevent any additional work while we handle that.
@@ -382,6 +400,18 @@ function ViewTable< Item >( {
382
400
  };
383
401
  const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
384
402
  const isRtl = isRTL();
403
+ if ( ! hasData ) {
404
+ return (
405
+ <div
406
+ className={ clsx( 'dataviews-no-results', {
407
+ 'is-refreshing': isDelayedLoading,
408
+ } ) }
409
+ id={ tableNoticeId }
410
+ >
411
+ { empty }
412
+ </div>
413
+ );
414
+ }
385
415
 
386
416
  return (
387
417
  <>
@@ -393,10 +423,13 @@ function ViewTable< Item >( {
393
423
  view.layout.density
394
424
  ),
395
425
  'has-bulk-actions': hasBulkActions,
426
+ 'is-refreshing': ! isInfiniteScroll && isDelayedLoading,
396
427
  } ) }
397
428
  aria-busy={ isLoading }
398
429
  aria-describedby={ tableNoticeId }
399
430
  role={ isInfiniteScroll ? 'feed' : undefined }
431
+ // @ts-ignore Reason: inert is a recent HTML attribute
432
+ inert={ ! isInfiniteScroll && isLoading ? 'true' : undefined }
400
433
  >
401
434
  <colgroup>
402
435
  { hasBulkActions && (
@@ -411,7 +444,7 @@ function ViewTable< Item >( {
411
444
  className={ clsx(
412
445
  `dataviews-view-table__col-${ column }`,
413
446
  {
414
- 'dataviews-view-table__col-expand':
447
+ 'dataviews-view-table__col-first-expand':
415
448
  ! hasPrimaryColumn &&
416
449
  index === columns.length - 1,
417
450
  }
@@ -483,6 +516,13 @@ function ViewTable< Item >( {
483
516
  // Explicit picks the supported styles.
484
517
  const { width, maxWidth, minWidth, align } =
485
518
  view.layout?.styles?.[ column ] ?? {};
519
+ const field = fields.find(
520
+ ( f ) => f.id === column
521
+ );
522
+ const effectiveAlign = getEffectiveAlign(
523
+ align,
524
+ field?.type
525
+ );
486
526
  const canInsertOrMove =
487
527
  view.layout?.enableMoving ?? true;
488
528
  return (
@@ -492,7 +532,7 @@ function ViewTable< Item >( {
492
532
  width,
493
533
  maxWidth,
494
534
  minWidth,
495
- textAlign: align,
535
+ textAlign: effectiveAlign,
496
536
  } }
497
537
  aria-sort={
498
538
  view.sort?.direction &&
@@ -634,27 +674,13 @@ function ViewTable< Item >( {
634
674
  </tbody>
635
675
  ) }
636
676
  </table>
637
- <div
638
- className={ clsx( {
639
- 'dataviews-loading': isLoading,
640
- 'dataviews-no-results': ! hasData && ! isLoading,
641
- } ) }
642
- id={ tableNoticeId }
643
- >
644
- { ! hasData &&
645
- ( isLoading ? (
646
- <p>
647
- <Spinner />
648
- </p>
649
- ) : (
650
- empty
651
- ) ) }
652
- { hasData && isLoading && (
677
+ { isInfiniteScroll && isLoading && (
678
+ <div className="dataviews-loading" id={ tableNoticeId }>
653
679
  <p className="dataviews-loading-more">
654
680
  <Spinner />
655
681
  </p>
656
- ) }
657
- </div>
682
+ </div>
683
+ ) }
658
684
  </>
659
685
  );
660
686
  }
@@ -2,6 +2,7 @@
2
2
  @use "@wordpress/base-styles/mixins" as *;
3
3
  @use "@wordpress/base-styles/variables" as *;
4
4
  @use "@wordpress/base-styles/z-index" as *;
5
+ @use "../../../dataviews/style" as *;
5
6
 
6
7
  .dataviews-view-table {
7
8
  width: 100%;
@@ -176,6 +177,7 @@
176
177
 
177
178
  &.dataviews-view-table__cell-align-end {
178
179
  justify-content: flex-end;
180
+ font-variant-numeric: tabular-nums;
179
181
  }
180
182
 
181
183
  &.dataviews-view-table__cell-align-center {
@@ -309,3 +311,7 @@
309
311
  .dataviews-view-table col[class^="dataviews-view-table__col-"]:not(.dataviews-view-table__col-first-data):not(.dataviews-view-table__col-expand) {
310
312
  width: 1%;
311
313
  }
314
+
315
+ .dataviews-view-table.is-refreshing {
316
+ @include dataviews-refreshing();
317
+ }
@@ -152,7 +152,7 @@ const LayoutCardComponent = ( {
152
152
  isCollapsible: collapsible,
153
153
  isOpened: opened,
154
154
  }: {
155
- summary?: string | { id: string; visibility: 'always' }[];
155
+ summary?: string | string[] | { id: string; visibility: 'always' }[];
156
156
  withSummary?: boolean;
157
157
  withHeader?: boolean;
158
158
  isCollapsible?: boolean;
@@ -178,7 +178,7 @@ const LayoutCardComponent = ( {
178
178
  {
179
179
  id: 'customerCard',
180
180
  layout: getCardLayoutFromStoryArgs( {
181
- summary: 'plan-summary',
181
+ summary: [ 'name', 'plan-summary' ],
182
182
  withHeader: withHeader ?? true,
183
183
  withSummary,
184
184
  isCollapsible,
@@ -75,6 +75,10 @@ const fields: Field< SamplePost >[] = [
75
75
  { value: 2, label: 'John' },
76
76
  { value: 3, label: 'Alice' },
77
77
  { value: 4, label: 'Bob' },
78
+ {
79
+ value: 5,
80
+ label: 'Superadministratoraccountwithalongunhyphenatedusername',
81
+ },
78
82
  ],
79
83
  setValue: ( { value } ) => ( {
80
84
  author: Number( value ),
@@ -310,7 +314,7 @@ const LayoutPanelComponent = ( {
310
314
  const [ post, setPost ] = useState< SamplePost >( {
311
315
  title: 'Hello, World!',
312
316
  order: 2,
313
- author: 1,
317
+ author: 5,
314
318
  status: 'draft',
315
319
  reviewer: 'fulano',
316
320
  date: '2021-01-01T12:00:00',
@@ -31,6 +31,7 @@ import DataViewsViewConfig, {
31
31
  ViewTypeMenu,
32
32
  } from '../components/dataviews-view-config';
33
33
  import normalizeFields from '../field-types';
34
+ import useData from '../hooks/use-data';
34
35
  import type { Action, Field, View, SupportedLayouts } from '../types';
35
36
  import type { SelectionOrUpdater } from '../types/private';
36
37
  type ItemWithId = { id: string };
@@ -236,6 +237,12 @@ function DataViews< Item >( {
236
237
  [ defaultLayoutsProperty ]
237
238
  );
238
239
 
240
+ const {
241
+ data: displayData,
242
+ paginationInfo: displayPaginationInfo,
243
+ hasInitiallyLoaded,
244
+ } = useData( data, isLoading, paginationInfo );
245
+
239
246
  if ( ! defaultLayouts[ view.type ] ) {
240
247
  return null;
241
248
  }
@@ -247,9 +254,9 @@ function DataViews< Item >( {
247
254
  onChangeView,
248
255
  fields: _fields,
249
256
  actions,
250
- data,
257
+ data: displayData,
251
258
  isLoading,
252
- paginationInfo,
259
+ paginationInfo: displayPaginationInfo,
253
260
  selection: _selection,
254
261
  onChangeSelection: setSelectionWithChange,
255
262
  openedFilter,
@@ -268,6 +275,7 @@ function DataViews< Item >( {
268
275
  setIsShowingFilter,
269
276
  config,
270
277
  empty,
278
+ hasInitiallyLoaded,
271
279
  hasInfiniteScrollHandler: !! infiniteScrollHandler,
272
280
  onReset,
273
281
  } }
@@ -59,13 +59,7 @@ const EmptyComponent = ( {
59
59
  search: '',
60
60
  page: 1,
61
61
  perPage: 10,
62
- layout: {
63
- styles: {
64
- satellites: {
65
- align: 'end' as const,
66
- },
67
- },
68
- },
62
+ layout: {},
69
63
  filters: [],
70
64
  fields: [ 'title', 'description', 'categories' ],
71
65
  } );
@@ -134,11 +134,6 @@ export const FreeCompositionComponent = () => {
134
134
  page: 1,
135
135
  perPage: 10,
136
136
  layout: {
137
- styles: {
138
- satellites: {
139
- align: 'end' as const,
140
- },
141
- },
142
137
  enableMoving: false,
143
138
  },
144
139
  filters: [],
@@ -32,13 +32,7 @@ export const LayoutTableComponent = ( {
32
32
  search: '',
33
33
  page: 1,
34
34
  perPage: 10,
35
- layout: {
36
- styles: {
37
- satellites: {
38
- align: 'end' as const,
39
- },
40
- },
41
- },
35
+ layout: {},
42
36
  filters: [],
43
37
  fields: [ 'categories' ],
44
38
  titleField: 'title',
@@ -23,11 +23,6 @@ const MinimalUIComponent = ( {
23
23
  page: 1,
24
24
  perPage: 10,
25
25
  layout: {
26
- styles: {
27
- satellites: {
28
- align: 'end' as const,
29
- },
30
- },
31
26
  enableMoving: false,
32
27
  },
33
28
  filters: [],
@@ -24,13 +24,7 @@ const WithCardComponent = () => {
24
24
  search: '',
25
25
  page: 1,
26
26
  perPage: 10,
27
- layout: {
28
- styles: {
29
- satellites: {
30
- align: 'end' as const,
31
- },
32
- },
33
- },
27
+ layout: {},
34
28
  filters: [],
35
29
  fields: [ 'categories' ],
36
30
  titleField: 'title',
@@ -2,6 +2,16 @@
2
2
  @use "@wordpress/base-styles/variables" as *;
3
3
  @use "@wordpress/base-styles/mixins" as *;
4
4
 
5
+ @mixin dataviews-refreshing {
6
+ opacity: 0.5;
7
+ pointer-events: none;
8
+
9
+ @media not (prefers-reduced-motion) {
10
+ opacity: 1;
11
+ animation: dataviews-pulse 1s ease-in-out infinite;
12
+ }
13
+ }
14
+
5
15
  .dataviews-wrapper,
6
16
  .dataviews-picker-wrapper {
7
17
  height: 100%;
@@ -44,6 +54,21 @@
44
54
  }
45
55
  }
46
56
 
57
+ .dataviews-no-results.is-refreshing {
58
+ @include dataviews-refreshing();
59
+ }
60
+
61
+ @keyframes dataviews-pulse {
62
+ 0%,
63
+ 100% {
64
+ opacity: 1;
65
+ }
66
+
67
+ 50% {
68
+ opacity: 0.5;
69
+ }
70
+ }
71
+
47
72
  .dataviews-loading-more {
48
73
  text-align: center;
49
74
  }
@@ -237,6 +237,7 @@ function DataViewsPicker< Item >( {
237
237
  config,
238
238
  itemListLabel,
239
239
  empty,
240
+ hasInitiallyLoaded: true,
240
241
  hasInfiniteScrollHandler: !! infiniteScrollHandler,
241
242
  } }
242
243
  >
@@ -0,0 +1,45 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useEffect, useRef, useState } from '@wordpress/element';
5
+
6
+ type PaginationInfo = {
7
+ totalItems: number;
8
+ totalPages: number;
9
+ infiniteScrollHandler?: () => void;
10
+ };
11
+
12
+ export default function useData< Item >(
13
+ data: Item[],
14
+ isLoading: boolean | undefined,
15
+ paginationInfo: PaginationInfo
16
+ ): {
17
+ data: Item[];
18
+ paginationInfo: PaginationInfo;
19
+ hasInitiallyLoaded: boolean;
20
+ } {
21
+ const previousDataRef = useRef< Item[] >( data );
22
+ const previousPaginationInfoRef =
23
+ useRef< PaginationInfo >( paginationInfo );
24
+ const [ hasInitiallyLoaded, setHasInitiallyLoaded ] = useState(
25
+ ! isLoading
26
+ );
27
+ useEffect( () => {
28
+ if ( ! isLoading ) {
29
+ previousDataRef.current = data;
30
+ previousPaginationInfoRef.current = paginationInfo;
31
+ setHasInitiallyLoaded( true );
32
+ }
33
+ }, [ data, isLoading, paginationInfo ] );
34
+ return {
35
+ data:
36
+ isLoading && previousDataRef.current?.length
37
+ ? previousDataRef.current
38
+ : data,
39
+ paginationInfo:
40
+ isLoading && previousDataRef.current?.length
41
+ ? previousPaginationInfoRef.current
42
+ : paginationInfo,
43
+ hasInitiallyLoaded,
44
+ };
45
+ }
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from '@wordpress/element';
2
+
3
+ export function useDelayedLoading(
4
+ isLoading: boolean,
5
+ options: { delay: number } = { delay: 400 }
6
+ ): boolean {
7
+ const [ showLoader, setShowLoader ] = useState( false );
8
+ useEffect( () => {
9
+ if ( ! isLoading ) {
10
+ return;
11
+ }
12
+ const timeout = setTimeout( () => {
13
+ setShowLoader( true );
14
+ }, options.delay );
15
+ return () => {
16
+ clearTimeout( timeout );
17
+ setShowLoader( false );
18
+ };
19
+ }, [ isLoading, options.delay ] );
20
+ return showLoader;
21
+ }