@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.
- package/CHANGELOG.md +15 -6
- package/README.md +17 -2
- package/build/components/dataform-controls/datetime.cjs +8 -4
- package/build/components/dataform-controls/datetime.cjs.map +2 -2
- package/build/components/dataform-layouts/card/index.cjs +132 -128
- package/build/components/dataform-layouts/card/index.cjs.map +3 -3
- package/build/components/dataviews-bulk-actions/index.cjs +28 -5
- package/build/components/dataviews-bulk-actions/index.cjs.map +2 -2
- package/build/components/dataviews-context/index.cjs +2 -2
- package/build/components/dataviews-context/index.cjs.map +2 -2
- package/build/components/dataviews-footer/index.cjs +2 -3
- package/build/components/dataviews-footer/index.cjs.map +2 -2
- package/build/components/dataviews-layouts/grid/composite-grid.cjs +378 -249
- package/build/components/dataviews-layouts/grid/composite-grid.cjs.map +2 -2
- package/build/components/dataviews-layouts/picker-grid/index.cjs +63 -30
- package/build/components/dataviews-layouts/picker-grid/index.cjs.map +2 -2
- package/build/components/dataviews-layouts/picker-table/index.cjs +34 -22
- package/build/components/dataviews-layouts/picker-table/index.cjs.map +2 -2
- package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs +62 -0
- package/build/components/dataviews-layouts/utils/use-infinite-scroll.cjs.map +7 -0
- package/build/components/dataviews-picker-footer/index.cjs +23 -4
- package/build/components/dataviews-picker-footer/index.cjs.map +2 -2
- package/build/components/dataviews-search/index.cjs +2 -1
- package/build/components/dataviews-search/index.cjs.map +2 -2
- package/build/components/dataviews-selection-checkbox/index.cjs +3 -2
- package/build/components/dataviews-selection-checkbox/index.cjs.map +2 -2
- package/build/components/dataviews-view-config/index.cjs +0 -2
- package/build/components/dataviews-view-config/index.cjs.map +3 -3
- package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs +0 -3
- package/build/components/dataviews-view-config/infinite-scroll-toggle.cjs.map +2 -2
- package/build/dataviews/index.cjs +37 -37
- package/build/dataviews/index.cjs.map +3 -3
- package/build/dataviews-picker/index.cjs +25 -24
- package/build/dataviews-picker/index.cjs.map +3 -3
- package/build/hooks/index.cjs +11 -2
- package/build/hooks/index.cjs.map +2 -2
- package/build/hooks/use-data.cjs +146 -9
- package/build/hooks/use-data.cjs.map +2 -2
- package/build/hooks/use-infinite-scroll.cjs +208 -0
- package/build/hooks/use-infinite-scroll.cjs.map +7 -0
- package/build/hooks/use-selected-items.cjs +57 -0
- package/build/hooks/use-selected-items.cjs.map +7 -0
- package/build/types/dataviews.cjs.map +1 -1
- package/build/types/field-api.cjs.map +1 -1
- package/build/utils/filter-sort-and-paginate.cjs +5 -1
- package/build/utils/filter-sort-and-paginate.cjs.map +2 -2
- package/build/utils/get-footer-message.cjs +8 -8
- package/build/utils/get-footer-message.cjs.map +2 -2
- package/build-module/components/dataform-controls/datetime.mjs +8 -4
- package/build-module/components/dataform-controls/datetime.mjs.map +2 -2
- package/build-module/components/dataform-layouts/card/index.mjs +132 -133
- package/build-module/components/dataform-layouts/card/index.mjs.map +2 -2
- package/build-module/components/dataviews-bulk-actions/index.mjs +28 -5
- package/build-module/components/dataviews-bulk-actions/index.mjs.map +2 -2
- package/build-module/components/dataviews-context/index.mjs +2 -2
- package/build-module/components/dataviews-context/index.mjs.map +2 -2
- package/build-module/components/dataviews-footer/index.mjs +2 -3
- package/build-module/components/dataviews-footer/index.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/grid/composite-grid.mjs +387 -250
- package/build-module/components/dataviews-layouts/grid/composite-grid.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/picker-grid/index.mjs +67 -31
- package/build-module/components/dataviews-layouts/picker-grid/index.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/picker-table/index.mjs +34 -22
- package/build-module/components/dataviews-layouts/picker-table/index.mjs.map +2 -2
- package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs +26 -0
- package/build-module/components/dataviews-layouts/utils/use-infinite-scroll.mjs.map +7 -0
- package/build-module/components/dataviews-picker-footer/index.mjs +23 -4
- package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
- package/build-module/components/dataviews-search/index.mjs +2 -1
- package/build-module/components/dataviews-search/index.mjs.map +2 -2
- package/build-module/components/dataviews-selection-checkbox/index.mjs +3 -2
- package/build-module/components/dataviews-selection-checkbox/index.mjs.map +2 -2
- package/build-module/components/dataviews-view-config/index.mjs +0 -2
- package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
- package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs +0 -3
- package/build-module/components/dataviews-view-config/infinite-scroll-toggle.mjs.map +2 -2
- package/build-module/dataviews/index.mjs +45 -39
- package/build-module/dataviews/index.mjs.map +2 -2
- package/build-module/dataviews-picker/index.mjs +33 -26
- package/build-module/dataviews-picker/index.mjs.map +2 -2
- package/build-module/hooks/index.mjs +7 -1
- package/build-module/hooks/index.mjs.map +2 -2
- package/build-module/hooks/use-data.mjs +147 -10
- package/build-module/hooks/use-data.mjs.map +2 -2
- package/build-module/hooks/use-infinite-scroll.mjs +188 -0
- package/build-module/hooks/use-infinite-scroll.mjs.map +7 -0
- package/build-module/hooks/use-selected-items.mjs +36 -0
- package/build-module/hooks/use-selected-items.mjs.map +7 -0
- package/build-module/utils/filter-sort-and-paginate.mjs +5 -1
- package/build-module/utils/filter-sort-and-paginate.mjs.map +2 -2
- package/build-module/utils/get-footer-message.mjs +8 -8
- package/build-module/utils/get-footer-message.mjs.map +2 -2
- package/build-style/style-rtl.css +61 -37
- package/build-style/style.css +61 -37
- package/build-types/components/dataform-controls/datetime.d.ts +1 -1
- package/build-types/components/dataform-controls/datetime.d.ts.map +1 -1
- package/build-types/components/dataform-layouts/card/index.d.ts.map +1 -1
- package/build-types/components/dataviews-bulk-actions/index.d.ts +2 -1
- package/build-types/components/dataviews-bulk-actions/index.d.ts.map +1 -1
- package/build-types/components/dataviews-context/index.d.ts +1 -1
- package/build-types/components/dataviews-context/index.d.ts.map +1 -1
- package/build-types/components/dataviews-footer/index.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/picker-grid/index.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/picker-table/index.d.ts.map +1 -1
- package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts +22 -0
- package/build-types/components/dataviews-layouts/utils/use-infinite-scroll.d.ts.map +1 -0
- package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
- package/build-types/components/dataviews-search/index.d.ts.map +1 -1
- package/build-types/components/dataviews-selection-checkbox/index.d.ts.map +1 -1
- package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
- package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts +1 -1
- package/build-types/components/dataviews-view-config/infinite-scroll-toggle.d.ts.map +1 -1
- package/build-types/dataviews/index.d.ts +0 -1
- package/build-types/dataviews/index.d.ts.map +1 -1
- package/build-types/dataviews/stories/fixtures.d.ts.map +1 -1
- package/build-types/dataviews/stories/free-composition.d.ts.map +1 -1
- package/build-types/dataviews/stories/index.story.d.ts +11 -0
- package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
- package/build-types/dataviews/stories/infinite-scroll.d.ts.map +1 -1
- package/build-types/dataviews/stories/with-card.d.ts.map +1 -1
- package/build-types/dataviews-picker/index.d.ts +0 -1
- package/build-types/dataviews-picker/index.d.ts.map +1 -1
- package/build-types/dataviews-picker/stories/fixtures.d.ts.map +1 -1
- package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
- package/build-types/field-types/stories/index.story.d.ts.map +1 -1
- package/build-types/hooks/index.d.ts +3 -0
- package/build-types/hooks/index.d.ts.map +1 -1
- package/build-types/hooks/test/use-data.d.ts +2 -0
- package/build-types/hooks/test/use-data.d.ts.map +1 -0
- package/build-types/hooks/use-data.d.ts +41 -3
- package/build-types/hooks/use-data.d.ts.map +1 -1
- package/build-types/hooks/use-infinite-scroll.d.ts +21 -0
- package/build-types/hooks/use-infinite-scroll.d.ts.map +1 -0
- package/build-types/hooks/use-selected-items.d.ts +19 -0
- package/build-types/hooks/use-selected-items.d.ts.map +1 -0
- package/build-types/types/dataviews.d.ts +7 -1
- package/build-types/types/dataviews.d.ts.map +1 -1
- package/build-types/types/field-api.d.ts +15 -4
- package/build-types/types/field-api.d.ts.map +1 -1
- package/build-types/utils/filter-sort-and-paginate.d.ts.map +1 -1
- package/build-types/utils/get-footer-message.d.ts +3 -2
- package/build-types/utils/get-footer-message.d.ts.map +1 -1
- package/build-wp/index.js +3013 -2613
- package/package.json +19 -19
- package/src/components/dataform-controls/datetime.tsx +19 -11
- package/src/components/dataform-layouts/card/index.tsx +171 -146
- package/src/components/dataform-layouts/card/style.scss +8 -5
- package/src/components/dataviews-bulk-actions/index.tsx +28 -1
- package/src/components/dataviews-context/index.ts +2 -2
- package/src/components/dataviews-footer/index.tsx +1 -6
- package/src/components/dataviews-layouts/grid/composite-grid.tsx +433 -284
- package/src/components/dataviews-layouts/grid/style.scss +4 -0
- package/src/components/dataviews-layouts/picker-grid/index.tsx +53 -15
- package/src/components/dataviews-layouts/picker-table/index.tsx +42 -22
- package/src/components/dataviews-layouts/utils/use-infinite-scroll.ts +64 -0
- package/src/components/dataviews-picker-footer/index.tsx +21 -1
- package/src/components/dataviews-search/index.tsx +2 -1
- package/src/components/dataviews-selection-checkbox/index.tsx +4 -2
- package/src/components/dataviews-view-config/index.tsx +0 -2
- package/src/components/dataviews-view-config/infinite-scroll-toggle.tsx +0 -5
- package/src/dataviews/index.tsx +57 -52
- package/src/dataviews/stories/fixtures.tsx +288 -0
- package/src/dataviews/stories/free-composition.tsx +12 -11
- package/src/dataviews/stories/index.story.tsx +19 -2
- package/src/dataviews/stories/infinite-scroll.tsx +12 -92
- package/src/dataviews/stories/with-card.tsx +30 -23
- package/src/dataviews/style.scss +5 -7
- package/src/dataviews/test/dataviews.tsx +21 -9
- package/src/dataviews-picker/index.tsx +40 -34
- package/src/dataviews-picker/stories/fixtures.tsx +270 -0
- package/src/dataviews-picker/stories/index.story.tsx +62 -129
- package/src/field-types/stories/index.story.tsx +12 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/test/use-data.ts +791 -0
- package/src/hooks/use-data.ts +288 -21
- package/src/hooks/use-infinite-scroll.ts +304 -0
- package/src/hooks/use-selected-items.ts +72 -0
- package/src/types/dataviews.ts +8 -1
- package/src/types/field-api.ts +16 -3
- package/src/utils/filter-sort-and-paginate.ts +13 -1
- package/src/utils/get-footer-message.ts +12 -9
- package/src/utils/test/filter-sort-and-paginate.js +78 -54
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from '@wordpress/components';
|
|
17
17
|
import { __, sprintf } from '@wordpress/i18n';
|
|
18
18
|
import { useInstanceId } from '@wordpress/compose';
|
|
19
|
-
import { useContext } from '@wordpress/element';
|
|
19
|
+
import { useContext, useRef } from '@wordpress/element';
|
|
20
20
|
import { Stack } from '@wordpress/ui';
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -35,6 +35,11 @@ import type { SetSelection } from '../../../types/private';
|
|
|
35
35
|
import { GridItems } from '../utils/grid-items';
|
|
36
36
|
const { Badge } = unlock( componentsPrivateApis );
|
|
37
37
|
import getDataByGroup from '../utils/get-data-by-group';
|
|
38
|
+
import { useGridColumns } from '../grid/preview-size-picker';
|
|
39
|
+
import {
|
|
40
|
+
useIntersectionObserver,
|
|
41
|
+
usePlaceholdersNeeded,
|
|
42
|
+
} from '../utils/use-infinite-scroll';
|
|
38
43
|
|
|
39
44
|
interface GridItemProps< Item > {
|
|
40
45
|
view: ViewPickerGridType;
|
|
@@ -73,7 +78,16 @@ function GridItem< Item >( {
|
|
|
73
78
|
}: GridItemProps< Item > ) {
|
|
74
79
|
const { showTitle = true, showMedia = true, showDescription = true } = view;
|
|
75
80
|
const id = getItemId( item );
|
|
81
|
+
const elementRef = useRef< HTMLElement | null >( null );
|
|
82
|
+
|
|
76
83
|
const isSelected = selection.includes( id );
|
|
84
|
+
|
|
85
|
+
const setElementRef = ( element: HTMLElement | null ) => {
|
|
86
|
+
elementRef.current = element;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
useIntersectionObserver( elementRef, posinset );
|
|
90
|
+
|
|
77
91
|
const renderedMediaField = mediaField?.render ? (
|
|
78
92
|
<mediaField.render
|
|
79
93
|
item={ item }
|
|
@@ -88,6 +102,7 @@ function GridItem< Item >( {
|
|
|
88
102
|
|
|
89
103
|
return (
|
|
90
104
|
<Composite.Item
|
|
105
|
+
ref={ setElementRef }
|
|
91
106
|
aria-label={
|
|
92
107
|
titleField
|
|
93
108
|
? titleField.getValue( { item } ) || __( '(no title)' )
|
|
@@ -105,6 +120,7 @@ function GridItem< Item >( {
|
|
|
105
120
|
} ) }
|
|
106
121
|
aria-selected={ isSelected }
|
|
107
122
|
onClick={ () => {
|
|
123
|
+
// Toggle in/out of selection array
|
|
108
124
|
if ( isSelected ) {
|
|
109
125
|
onChangeSelection(
|
|
110
126
|
selection.filter( ( itemId ) => id !== itemId )
|
|
@@ -318,12 +334,21 @@ function ViewPickerGrid< Item >( {
|
|
|
318
334
|
: null;
|
|
319
335
|
const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
|
|
320
336
|
|
|
321
|
-
const isInfiniteScroll =
|
|
337
|
+
const isInfiniteScroll =
|
|
338
|
+
( view.infiniteScrollEnabled && ! dataByGroup ) ?? false;
|
|
322
339
|
|
|
323
340
|
const currentPage = view?.page ?? 1;
|
|
324
341
|
const perPage = view?.perPage ?? 0;
|
|
325
342
|
const setSize = isInfiniteScroll ? paginationInfo?.totalItems : undefined;
|
|
326
343
|
|
|
344
|
+
// Calculate placeholders needed for infinite scroll
|
|
345
|
+
const gridColumns = useGridColumns();
|
|
346
|
+
const placeholdersNeeded = usePlaceholdersNeeded(
|
|
347
|
+
data,
|
|
348
|
+
isInfiniteScroll,
|
|
349
|
+
gridColumns
|
|
350
|
+
);
|
|
351
|
+
|
|
327
352
|
return (
|
|
328
353
|
<>
|
|
329
354
|
{
|
|
@@ -378,10 +403,12 @@ function ViewPickerGrid< Item >( {
|
|
|
378
403
|
}
|
|
379
404
|
>
|
|
380
405
|
{ groupItems.map( ( item ) => {
|
|
406
|
+
// Use position from item if available (infinite scroll), otherwise calculate.
|
|
381
407
|
const posInSet =
|
|
408
|
+
( item as any ).position ??
|
|
382
409
|
( currentPage - 1 ) * perPage +
|
|
383
|
-
|
|
384
|
-
|
|
410
|
+
data.indexOf( item ) +
|
|
411
|
+
1;
|
|
385
412
|
return (
|
|
386
413
|
<GridItem
|
|
387
414
|
key={ getItemId( item ) }
|
|
@@ -451,17 +478,28 @@ function ViewPickerGrid< Item >( {
|
|
|
451
478
|
aria-multiselectable={ isMultiselect }
|
|
452
479
|
aria-label={ itemListLabel }
|
|
453
480
|
>
|
|
454
|
-
{
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
481
|
+
{ /* Render placeholders for unloaded items in first row */ }
|
|
482
|
+
{ Array.from( { length: placeholdersNeeded } ).map(
|
|
483
|
+
( _, index ) => (
|
|
484
|
+
<Composite.Item
|
|
485
|
+
key={ `placeholder-${ index }` }
|
|
486
|
+
render={ ( { children, ...props } ) => (
|
|
487
|
+
<Stack
|
|
488
|
+
direction="column"
|
|
489
|
+
children={ children }
|
|
490
|
+
{ ...props }
|
|
491
|
+
/>
|
|
492
|
+
) }
|
|
493
|
+
role="option"
|
|
494
|
+
aria-hidden
|
|
495
|
+
tabIndex={ -1 }
|
|
496
|
+
className="dataviews-view-picker-grid__card dataviews-view-picker-grid__placeholder"
|
|
497
|
+
/>
|
|
498
|
+
)
|
|
499
|
+
) }
|
|
500
|
+
{ data.map( ( item ) => {
|
|
501
|
+
// Use position from item for accessibility in infinite scroll mode.
|
|
502
|
+
const posinset = ( item as any ).position;
|
|
465
503
|
|
|
466
504
|
return (
|
|
467
505
|
<GridItem
|
|
@@ -33,6 +33,7 @@ import type { SetSelection } from '../../../types/private';
|
|
|
33
33
|
import ColumnHeaderMenu from '../table/column-header-menu';
|
|
34
34
|
import ColumnPrimary from '../table/column-primary';
|
|
35
35
|
import getDataByGroup from '../utils/get-data-by-group';
|
|
36
|
+
import { useIntersectionObserver } from '../utils/use-infinite-scroll';
|
|
36
37
|
|
|
37
38
|
interface TableColumnFieldProps< Item > {
|
|
38
39
|
fields: NormalizedField< Item >[];
|
|
@@ -95,8 +96,17 @@ function TableRow< Item >( {
|
|
|
95
96
|
posinset,
|
|
96
97
|
}: TableRowProps< Item > ) {
|
|
97
98
|
const { paginationInfo } = useContext( DataViewsContext );
|
|
99
|
+
|
|
98
100
|
const isSelected = selection.includes( id );
|
|
101
|
+
|
|
99
102
|
const [ isHovered, setIsHovered ] = useState( false );
|
|
103
|
+
const elementRef = useRef< HTMLElement | null >( null );
|
|
104
|
+
|
|
105
|
+
const setElementRef = ( element: HTMLElement | null ) => {
|
|
106
|
+
elementRef.current = element;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
useIntersectionObserver( elementRef, posinset );
|
|
100
110
|
const {
|
|
101
111
|
showTitle = true,
|
|
102
112
|
showMedia = true,
|
|
@@ -119,6 +129,7 @@ function TableRow< Item >( {
|
|
|
119
129
|
return (
|
|
120
130
|
<Composite.Item
|
|
121
131
|
key={ id }
|
|
132
|
+
ref={ setElementRef }
|
|
122
133
|
render={ ( { children, ...props } ) => (
|
|
123
134
|
<tr
|
|
124
135
|
className={ clsx( 'dataviews-view-table__row', {
|
|
@@ -136,6 +147,7 @@ function TableRow< Item >( {
|
|
|
136
147
|
aria-posinset={ posinset }
|
|
137
148
|
role={ infiniteScrollEnabled ? 'article' : 'option' }
|
|
138
149
|
onClick={ () => {
|
|
150
|
+
// Toggle in/out of selection array
|
|
139
151
|
if ( isSelected ) {
|
|
140
152
|
onChangeSelection(
|
|
141
153
|
selection.filter( ( itemId ) => id !== itemId )
|
|
@@ -236,6 +248,12 @@ function ViewPickerTable< Item >( {
|
|
|
236
248
|
}
|
|
237
249
|
} );
|
|
238
250
|
|
|
251
|
+
const groupField = view.groupBy?.field
|
|
252
|
+
? fields.find( ( f ) => f.id === view.groupBy?.field )
|
|
253
|
+
: null;
|
|
254
|
+
const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
|
|
255
|
+
const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
|
|
256
|
+
|
|
239
257
|
const tableNoticeId = useId();
|
|
240
258
|
|
|
241
259
|
if ( nextHeaderMenuToFocus ) {
|
|
@@ -264,10 +282,6 @@ function ViewPickerTable< Item >( {
|
|
|
264
282
|
( field ) => field.id === view.descriptionField
|
|
265
283
|
);
|
|
266
284
|
|
|
267
|
-
const groupField = view.groupBy?.field
|
|
268
|
-
? fields.find( ( f ) => f.id === view.groupBy?.field )
|
|
269
|
-
: null;
|
|
270
|
-
const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
|
|
271
285
|
const { showTitle = true, showMedia = true, showDescription = true } = view;
|
|
272
286
|
const hasPrimaryColumn =
|
|
273
287
|
( titleField && showTitle ) ||
|
|
@@ -285,7 +299,6 @@ function ViewPickerTable< Item >( {
|
|
|
285
299
|
headerMenuRefs.current.delete( column );
|
|
286
300
|
}
|
|
287
301
|
};
|
|
288
|
-
const isInfiniteScroll = view.infiniteScrollEnabled && ! dataByGroup;
|
|
289
302
|
|
|
290
303
|
return (
|
|
291
304
|
<>
|
|
@@ -319,6 +332,7 @@ function ViewPickerTable< Item >( {
|
|
|
319
332
|
data={ data }
|
|
320
333
|
actions={ actions }
|
|
321
334
|
getItemId={ getItemId }
|
|
335
|
+
disableSelectAll={ isInfiniteScroll }
|
|
322
336
|
/>
|
|
323
337
|
) }
|
|
324
338
|
</th>
|
|
@@ -441,23 +455,29 @@ function ViewPickerTable< Item >( {
|
|
|
441
455
|
orientation="vertical"
|
|
442
456
|
>
|
|
443
457
|
{ hasData &&
|
|
444
|
-
data.map( ( item, index ) =>
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
458
|
+
data.map( ( item, index ) => {
|
|
459
|
+
const itemId = getItemId( item );
|
|
460
|
+
// Use position from item for accessibility in infinite scroll mode.
|
|
461
|
+
const posinset = ( item as any ).position;
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<TableRow
|
|
465
|
+
key={ itemId }
|
|
466
|
+
item={ item }
|
|
467
|
+
fields={ fields }
|
|
468
|
+
id={ itemId || index.toString() }
|
|
469
|
+
view={ view }
|
|
470
|
+
titleField={ titleField }
|
|
471
|
+
mediaField={ mediaField }
|
|
472
|
+
descriptionField={ descriptionField }
|
|
473
|
+
selection={ selection }
|
|
474
|
+
getItemId={ getItemId }
|
|
475
|
+
onChangeSelection={ onChangeSelection }
|
|
476
|
+
multiselect={ isMultiselect }
|
|
477
|
+
posinset={ posinset }
|
|
478
|
+
/>
|
|
479
|
+
);
|
|
480
|
+
} ) }
|
|
461
481
|
</Composite>
|
|
462
482
|
) }
|
|
463
483
|
</table>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useContext, useEffect } from '@wordpress/element';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import DataViewsContext from '../../dataviews-context';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook to set up an IntersectionObserver for infinite scroll items.
|
|
13
|
+
* Observes the element and triggers the callback when the item becomes visible.
|
|
14
|
+
*
|
|
15
|
+
* @param elementRef - Ref to the DOM element to observe.
|
|
16
|
+
* @param posinset - The position of the item in the set (1-based index).
|
|
17
|
+
*/
|
|
18
|
+
export function useIntersectionObserver(
|
|
19
|
+
elementRef: React.RefObject< HTMLElement | null >,
|
|
20
|
+
posinset: number | undefined
|
|
21
|
+
) {
|
|
22
|
+
const { intersectionObserver } = useContext( DataViewsContext );
|
|
23
|
+
|
|
24
|
+
useEffect( () => {
|
|
25
|
+
const element = elementRef.current;
|
|
26
|
+
if ( ! element || posinset === undefined || ! intersectionObserver ) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
intersectionObserver.observe( element );
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
intersectionObserver.unobserve( element );
|
|
34
|
+
};
|
|
35
|
+
}, [ elementRef, intersectionObserver, posinset ] );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook to calculate the number of placeholder items needed for the first row
|
|
40
|
+
* in an infinite scroll grid layout.
|
|
41
|
+
*
|
|
42
|
+
* When items are loaded starting from a position other than 1, placeholders
|
|
43
|
+
* are needed to maintain proper grid alignment.
|
|
44
|
+
*
|
|
45
|
+
* @param data - The array of data items.
|
|
46
|
+
* @param isInfiniteScroll - Whether infinite scroll is enabled.
|
|
47
|
+
* @param gridColumns - The number of columns in the grid.
|
|
48
|
+
* @return The number of placeholder items needed.
|
|
49
|
+
*/
|
|
50
|
+
export function usePlaceholdersNeeded< Item >(
|
|
51
|
+
data: Item[],
|
|
52
|
+
isInfiniteScroll: boolean,
|
|
53
|
+
gridColumns: number
|
|
54
|
+
): number {
|
|
55
|
+
const hasData = !! data?.length;
|
|
56
|
+
const firstItemPosition =
|
|
57
|
+
hasData && isInfiniteScroll
|
|
58
|
+
? ( data[ 0 ] as { position?: number } ).position
|
|
59
|
+
: undefined;
|
|
60
|
+
|
|
61
|
+
return firstItemPosition && gridColumns
|
|
62
|
+
? ( firstItemPosition - 1 ) % gridColumns
|
|
63
|
+
: 0;
|
|
64
|
+
}
|
|
@@ -32,15 +32,32 @@ function BulkSelectionCheckbox< Item >( {
|
|
|
32
32
|
onChangeSelection,
|
|
33
33
|
data,
|
|
34
34
|
getItemId,
|
|
35
|
+
disableSelectAll = false,
|
|
35
36
|
}: {
|
|
36
37
|
selection: string[];
|
|
37
38
|
selectedItems: Item[];
|
|
38
39
|
onChangeSelection: SetSelection;
|
|
39
40
|
data: Item[];
|
|
40
41
|
getItemId: ( item: Item ) => string;
|
|
42
|
+
disableSelectAll?: boolean;
|
|
41
43
|
} ) {
|
|
44
|
+
const hasSelection = selection.length > 0;
|
|
42
45
|
const areAllSelected = selectedItems.length === data.length;
|
|
43
46
|
|
|
47
|
+
if ( disableSelectAll ) {
|
|
48
|
+
return (
|
|
49
|
+
<CheckboxControl
|
|
50
|
+
className="dataviews-view-table-selection-checkbox"
|
|
51
|
+
checked={ hasSelection }
|
|
52
|
+
disabled={ ! hasSelection }
|
|
53
|
+
onChange={ () => {
|
|
54
|
+
onChangeSelection( [] );
|
|
55
|
+
} }
|
|
56
|
+
aria-label={ __( 'Deselect all' ) }
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
return (
|
|
45
62
|
<CheckboxControl
|
|
46
63
|
className="dataviews-view-table-selection-checkbox"
|
|
@@ -136,6 +153,7 @@ export function DataViewsPickerFooter() {
|
|
|
136
153
|
getItemId,
|
|
137
154
|
actions = EMPTY_ARRAY,
|
|
138
155
|
paginationInfo,
|
|
156
|
+
view,
|
|
139
157
|
} = useContext( DataViewsContext );
|
|
140
158
|
|
|
141
159
|
const isMultiselect = useIsMultiselectPicker( actions );
|
|
@@ -143,7 +161,8 @@ export function DataViewsPickerFooter() {
|
|
|
143
161
|
const message = getFooterMessage(
|
|
144
162
|
selection.length,
|
|
145
163
|
data.length,
|
|
146
|
-
paginationInfo.totalItems
|
|
164
|
+
paginationInfo.totalItems,
|
|
165
|
+
!! view.infiniteScrollEnabled
|
|
147
166
|
);
|
|
148
167
|
|
|
149
168
|
const selectedItems = useMemo(
|
|
@@ -173,6 +192,7 @@ export function DataViewsPickerFooter() {
|
|
|
173
192
|
onChangeSelection={ onChangeSelection }
|
|
174
193
|
data={ data }
|
|
175
194
|
getItemId={ getItemId }
|
|
195
|
+
disableSelectAll={ !! view.infiniteScrollEnabled }
|
|
176
196
|
/>
|
|
177
197
|
) }
|
|
178
198
|
<span className="dataviews-bulk-actions-footer__item-count">
|
|
@@ -35,7 +35,8 @@ const DataViewsSearch = memo( function Search( { label }: SearchProps ) {
|
|
|
35
35
|
if ( debouncedSearch !== viewRef.current?.search ) {
|
|
36
36
|
onChangeViewRef.current( {
|
|
37
37
|
...viewRef.current,
|
|
38
|
-
page: 1,
|
|
38
|
+
page: view.page ? 1 : undefined,
|
|
39
|
+
startPosition: view.startPosition ? 1 : undefined,
|
|
39
40
|
search: debouncedSearch,
|
|
40
41
|
} );
|
|
41
42
|
}
|
|
@@ -30,7 +30,8 @@ export default function DataViewsSelectionCheckbox< Item >( {
|
|
|
30
30
|
...extraProps
|
|
31
31
|
}: DataViewsSelectionCheckboxProps< Item > ) {
|
|
32
32
|
const id = getItemId( item );
|
|
33
|
-
const
|
|
33
|
+
const isInSelectionArray = selection.includes( id );
|
|
34
|
+
const checked = ! disabled && isInSelectionArray;
|
|
34
35
|
|
|
35
36
|
// Fallback label to ensure accessibility
|
|
36
37
|
const selectionLabel =
|
|
@@ -47,8 +48,9 @@ export default function DataViewsSelectionCheckbox< Item >( {
|
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// Toggle in/out of selection array
|
|
50
52
|
onChangeSelection(
|
|
51
|
-
|
|
53
|
+
isInSelectionArray
|
|
52
54
|
? selection.filter( ( itemId ) => id !== itemId )
|
|
53
55
|
: [ ...selection, id ]
|
|
54
56
|
);
|
|
@@ -31,7 +31,6 @@ import { SORTING_DIRECTIONS, sortIcons, sortLabels } from '../../constants';
|
|
|
31
31
|
import { VIEW_LAYOUTS } from '../dataviews-layouts';
|
|
32
32
|
import type { View } from '../../types';
|
|
33
33
|
import DataViewsContext from '../dataviews-context';
|
|
34
|
-
import InfiniteScrollToggle from './infinite-scroll-toggle';
|
|
35
34
|
import { PropertiesSection } from './properties-section';
|
|
36
35
|
import { unlock } from '../../lock-unlock';
|
|
37
36
|
|
|
@@ -346,7 +345,6 @@ export function DataviewsViewConfigDropdown() {
|
|
|
346
345
|
{ !! activeLayout?.viewConfigOptions && (
|
|
347
346
|
<activeLayout.viewConfigOptions />
|
|
348
347
|
) }
|
|
349
|
-
<InfiniteScrollToggle />
|
|
350
348
|
<ItemsPerPageControl />
|
|
351
349
|
<PropertiesSection />
|
|
352
350
|
</Stack>
|
|
@@ -15,11 +15,6 @@ export default function InfiniteScrollToggle() {
|
|
|
15
15
|
const { view, onChangeView } = context;
|
|
16
16
|
const infiniteScrollEnabled = view.infiniteScrollEnabled ?? false;
|
|
17
17
|
|
|
18
|
-
// Only render the toggle if an infinite scroll handler is available
|
|
19
|
-
if ( ! context.hasInfiniteScrollHandler ) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
18
|
return (
|
|
24
19
|
<ToggleControl
|
|
25
20
|
label={ __( 'Enable infinite scroll' ) }
|
package/src/dataviews/index.tsx
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import type { ReactNode, ComponentProps, ReactElement } from 'react';
|
|
5
|
+
import clsx from 'clsx';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* WordPress dependencies
|
|
8
9
|
*/
|
|
9
|
-
import {
|
|
10
|
-
|
|
10
|
+
import {
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useState,
|
|
16
|
+
} from '@wordpress/element';
|
|
17
|
+
import { useResizeObserver } from '@wordpress/compose';
|
|
11
18
|
import { Stack } from '@wordpress/ui';
|
|
12
19
|
|
|
13
20
|
/**
|
|
@@ -32,6 +39,7 @@ import DataViewsViewConfig, {
|
|
|
32
39
|
} from '../components/dataviews-view-config';
|
|
33
40
|
import normalizeFields from '../field-types';
|
|
34
41
|
import useData from '../hooks/use-data';
|
|
42
|
+
import { useInfiniteScroll } from '../hooks/use-infinite-scroll';
|
|
35
43
|
import type { Action, Field, View, SupportedLayouts } from '../types';
|
|
36
44
|
import type { SelectionOrUpdater } from '../types/private';
|
|
37
45
|
type ItemWithId = { id: string };
|
|
@@ -48,7 +56,6 @@ type DataViewsProps< Item > = {
|
|
|
48
56
|
paginationInfo: {
|
|
49
57
|
totalItems: number;
|
|
50
58
|
totalPages: number;
|
|
51
|
-
infiniteScrollHandler?: () => void;
|
|
52
59
|
};
|
|
53
60
|
defaultLayouts: SupportedLayouts;
|
|
54
61
|
selection?: string[];
|
|
@@ -90,13 +97,18 @@ function DefaultUI( {
|
|
|
90
97
|
search = true,
|
|
91
98
|
searchLabel = undefined,
|
|
92
99
|
}: DefaultUIProps ) {
|
|
100
|
+
const { view } = useContext( DataViewsContext );
|
|
101
|
+
const isInfiniteScroll = view.infiniteScrollEnabled;
|
|
93
102
|
return (
|
|
94
103
|
<>
|
|
95
104
|
<Stack
|
|
96
105
|
direction="row"
|
|
97
106
|
align="top"
|
|
98
107
|
justify="space-between"
|
|
99
|
-
className=
|
|
108
|
+
className={ clsx( 'dataviews__view-actions', {
|
|
109
|
+
'dataviews__view-actions--infinite-scroll':
|
|
110
|
+
isInfiniteScroll,
|
|
111
|
+
} ) }
|
|
100
112
|
gap="xs"
|
|
101
113
|
>
|
|
102
114
|
<Stack
|
|
@@ -144,7 +156,31 @@ function DataViews< Item >( {
|
|
|
144
156
|
empty,
|
|
145
157
|
onReset,
|
|
146
158
|
}: DataViewsProps< Item > ) {
|
|
147
|
-
const
|
|
159
|
+
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
|
|
160
|
+
const isUncontrolled =
|
|
161
|
+
selectionProperty === undefined || onChangeSelection === undefined;
|
|
162
|
+
const selection = isUncontrolled ? selectionState : selectionProperty;
|
|
163
|
+
|
|
164
|
+
// useData handles both infinite scroll and standard pagination paths,
|
|
165
|
+
// preserving previous data while loading and tracking initial load state.
|
|
166
|
+
const {
|
|
167
|
+
data: displayData,
|
|
168
|
+
paginationInfo: displayPaginationInfo,
|
|
169
|
+
hasInitiallyLoaded,
|
|
170
|
+
setVisibleEntries,
|
|
171
|
+
} = useData( {
|
|
172
|
+
view,
|
|
173
|
+
data: data as any,
|
|
174
|
+
getItemId: getItemId as any,
|
|
175
|
+
isLoading,
|
|
176
|
+
selection,
|
|
177
|
+
paginationInfo,
|
|
178
|
+
} ) as {
|
|
179
|
+
data: ( Item & { position?: number } )[];
|
|
180
|
+
paginationInfo: { totalItems: number; totalPages: number };
|
|
181
|
+
hasInitiallyLoaded: boolean;
|
|
182
|
+
setVisibleEntries?: React.Dispatch< React.SetStateAction< number[] > >;
|
|
183
|
+
};
|
|
148
184
|
const containerRef = useRef< HTMLDivElement >( null );
|
|
149
185
|
const [ containerWidth, setContainerWidth ] = useState( 0 );
|
|
150
186
|
const resizeObserverRef = useResizeObserver(
|
|
@@ -155,10 +191,6 @@ function DataViews< Item >( {
|
|
|
155
191
|
},
|
|
156
192
|
{ box: 'border-box' }
|
|
157
193
|
);
|
|
158
|
-
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
|
|
159
|
-
const isUncontrolled =
|
|
160
|
-
selectionProperty === undefined || onChangeSelection === undefined;
|
|
161
|
-
const selection = isUncontrolled ? selectionState : selectionProperty;
|
|
162
194
|
const [ openedFilter, setOpenedFilter ] = useState< string | null >( null );
|
|
163
195
|
function setSelectionWithChange( value: SelectionOrUpdater ) {
|
|
164
196
|
const newValue =
|
|
@@ -171,11 +203,16 @@ function DataViews< Item >( {
|
|
|
171
203
|
}
|
|
172
204
|
}
|
|
173
205
|
const _fields = useMemo( () => normalizeFields( fields ), [ fields ] );
|
|
206
|
+
// When infinite scroll is enabled, don't filter selection by current data
|
|
207
|
+
// because items may be scrolled out of view but still selected.
|
|
174
208
|
const _selection = useMemo( () => {
|
|
209
|
+
if ( view.infiniteScrollEnabled ) {
|
|
210
|
+
return selection;
|
|
211
|
+
}
|
|
175
212
|
return selection.filter( ( id ) =>
|
|
176
213
|
data.some( ( item ) => getItemId( item ) === id )
|
|
177
214
|
);
|
|
178
|
-
}, [ selection, data, getItemId ] );
|
|
215
|
+
}, [ selection, data, getItemId, view.infiniteScrollEnabled ] );
|
|
179
216
|
|
|
180
217
|
const filters = useFilters( _fields, view );
|
|
181
218
|
const hasPrimaryOrLockedFilters = useMemo(
|
|
@@ -189,53 +226,21 @@ function DataViews< Item >( {
|
|
|
189
226
|
hasPrimaryOrLockedFilters
|
|
190
227
|
);
|
|
191
228
|
|
|
229
|
+
const { intersectionObserver } = useInfiniteScroll( {
|
|
230
|
+
view,
|
|
231
|
+
onChangeView,
|
|
232
|
+
isLoading,
|
|
233
|
+
paginationInfo,
|
|
234
|
+
containerRef,
|
|
235
|
+
setVisibleEntries,
|
|
236
|
+
} );
|
|
237
|
+
|
|
192
238
|
useEffect( () => {
|
|
193
239
|
if ( hasPrimaryOrLockedFilters && ! isShowingFilter ) {
|
|
194
240
|
setIsShowingFilter( true );
|
|
195
241
|
}
|
|
196
242
|
}, [ hasPrimaryOrLockedFilters, isShowingFilter ] );
|
|
197
243
|
|
|
198
|
-
const {
|
|
199
|
-
data: displayData,
|
|
200
|
-
paginationInfo: displayPaginationInfo,
|
|
201
|
-
hasInitiallyLoaded,
|
|
202
|
-
} = useData( data, isLoading, paginationInfo );
|
|
203
|
-
|
|
204
|
-
// Attach scroll event listener for infinite scroll
|
|
205
|
-
useEffect( () => {
|
|
206
|
-
if (
|
|
207
|
-
! hasInitiallyLoaded ||
|
|
208
|
-
! view.infiniteScrollEnabled ||
|
|
209
|
-
! containerRef.current
|
|
210
|
-
) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const handleScroll = throttle( ( event: unknown ) => {
|
|
215
|
-
const target = ( event as Event ).target as HTMLElement;
|
|
216
|
-
const scrollTop = target.scrollTop;
|
|
217
|
-
const scrollHeight = target.scrollHeight;
|
|
218
|
-
const clientHeight = target.clientHeight;
|
|
219
|
-
|
|
220
|
-
// Check if user has scrolled near the bottom
|
|
221
|
-
if ( scrollTop + clientHeight >= scrollHeight - 100 ) {
|
|
222
|
-
infiniteScrollHandler?.();
|
|
223
|
-
}
|
|
224
|
-
}, 100 ); // Throttle to 100ms
|
|
225
|
-
|
|
226
|
-
const container = containerRef.current;
|
|
227
|
-
container.addEventListener( 'scroll', handleScroll );
|
|
228
|
-
|
|
229
|
-
return () => {
|
|
230
|
-
container.removeEventListener( 'scroll', handleScroll );
|
|
231
|
-
handleScroll.cancel(); // Cancel any pending throttled calls
|
|
232
|
-
};
|
|
233
|
-
}, [
|
|
234
|
-
hasInitiallyLoaded,
|
|
235
|
-
infiniteScrollHandler,
|
|
236
|
-
view.infiniteScrollEnabled,
|
|
237
|
-
] );
|
|
238
|
-
|
|
239
244
|
// Filter out DataViewsPicker layouts.
|
|
240
245
|
const defaultLayouts = useMemo(
|
|
241
246
|
() =>
|
|
@@ -284,8 +289,8 @@ function DataViews< Item >( {
|
|
|
284
289
|
config,
|
|
285
290
|
empty,
|
|
286
291
|
hasInitiallyLoaded,
|
|
287
|
-
hasInfiniteScrollHandler: !! infiniteScrollHandler,
|
|
288
292
|
onReset,
|
|
293
|
+
intersectionObserver,
|
|
289
294
|
} }
|
|
290
295
|
>
|
|
291
296
|
<div className="dataviews-wrapper">
|