@wordpress/core-data 7.41.0 → 7.41.2-next.v.202603161435.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 +6 -0
- package/README.md +19 -0
- package/build/actions.cjs +25 -31
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +34 -1
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +3 -2
- package/build/entities.cjs.map +2 -2
- package/build/entity-provider.cjs +15 -6
- package/build/entity-provider.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +33 -2
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +83 -2
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +3 -0
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +1 -1
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +3 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/queried-data/actions.cjs +1 -1
- package/build/queried-data/actions.cjs.map +2 -2
- package/build/queried-data/reducer.cjs +19 -13
- package/build/queried-data/reducer.cjs.map +2 -2
- package/build/queried-data/selectors.cjs +7 -4
- package/build/queried-data/selectors.cjs.map +2 -2
- package/build/reducer.cjs +2 -1
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +114 -76
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +29 -0
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +3 -0
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs +16 -0
- package/build/types.cjs.map +3 -3
- package/build/utils/crdt-blocks.cjs +22 -26
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +8 -5
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build/utils/crdt.cjs +1 -3
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +3 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/normalize-query-for-resolution.cjs +35 -0
- package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
- package/build/utils/user-permissions.cjs +1 -4
- package/build/utils/user-permissions.cjs.map +2 -2
- package/build-module/actions.mjs +30 -32
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +34 -1
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +3 -2
- package/build-module/entities.mjs.map +2 -2
- package/build-module/entity-provider.mjs +15 -6
- package/build-module/entity-provider.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +34 -3
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +80 -1
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +2 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +1 -1
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/queried-data/actions.mjs +1 -1
- package/build-module/queried-data/actions.mjs.map +2 -2
- package/build-module/queried-data/reducer.mjs +19 -13
- package/build-module/queried-data/reducer.mjs.map +2 -2
- package/build-module/queried-data/selectors.mjs +7 -4
- package/build-module/queried-data/selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +2 -1
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +116 -77
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +28 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +2 -0
- package/build-module/sync.mjs.map +2 -2
- package/build-module/types.mjs +9 -0
- package/build-module/types.mjs.map +4 -4
- package/build-module/utils/crdt-blocks.mjs +22 -26
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +8 -5
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +1 -3
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +2 -0
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
- package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
- package/build-module/utils/user-permissions.mjs +1 -4
- package/build-module/utils/user-permissions.mjs.map +2 -2
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +1 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +11 -6
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +2 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/reducer.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +2 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +2 -2
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +15 -0
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +10 -5
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +1 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
- package/build-types/utils/user-permissions.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/actions.js +49 -50
- package/src/awareness/post-editor-awareness.ts +93 -1
- package/src/awareness/test/post-editor-awareness.ts +35 -0
- package/src/awareness/types.ts +1 -1
- package/src/entities.js +2 -1
- package/src/entity-provider.js +24 -11
- package/src/hooks/test/use-post-editor-awareness-state.ts +443 -0
- package/src/hooks/use-entity-prop.js +43 -3
- package/src/hooks/use-post-editor-awareness-state.ts +159 -7
- package/src/index.js +1 -0
- package/src/private-actions.js +1 -1
- package/src/private-apis.js +6 -2
- package/src/queried-data/actions.js +1 -1
- package/src/queried-data/reducer.js +26 -14
- package/src/queried-data/selectors.js +12 -5
- package/src/queried-data/test/selectors.js +25 -0
- package/src/reducer.js +4 -1
- package/src/resolvers.js +141 -91
- package/src/selectors.ts +56 -0
- package/src/sync.ts +2 -0
- package/src/test/private-actions.js +1 -1
- package/src/test/resolvers.js +88 -14
- package/src/test/selectors.js +150 -0
- package/src/test/store.js +182 -0
- package/src/types.ts +19 -0
- package/src/utils/crdt-blocks.ts +47 -54
- package/src/utils/crdt-user-selections.ts +28 -16
- package/src/utils/crdt.ts +2 -7
- package/src/utils/index.js +1 -0
- package/src/utils/normalize-query-for-resolution.js +23 -0
- package/src/utils/test/crdt-blocks.ts +42 -24
- package/src/utils/user-permissions.js +4 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
+
import { usePrevious } from '@wordpress/compose';
|
|
4
5
|
import { useEffect, useState } from '@wordpress/element';
|
|
5
6
|
import type { Y } from '@wordpress/sync';
|
|
6
7
|
|
|
@@ -13,14 +14,9 @@ import type {
|
|
|
13
14
|
PostSaveEvent,
|
|
14
15
|
YDocDebugData,
|
|
15
16
|
} from '../awareness/types';
|
|
16
|
-
import type { SelectionState } from '../types';
|
|
17
|
+
import type { SelectionState, ResolvedSelection } from '../types';
|
|
17
18
|
import type { PostEditorAwareness } from '../awareness/post-editor-awareness';
|
|
18
19
|
|
|
19
|
-
interface ResolvedSelection {
|
|
20
|
-
textIndex: number | null;
|
|
21
|
-
localClientId: string | null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
interface AwarenessState {
|
|
25
21
|
activeCollaborators: ActiveCollaborator[];
|
|
26
22
|
resolveSelection: ( selection: SelectionState ) => ResolvedSelection;
|
|
@@ -167,7 +163,7 @@ export function useIsDisconnected(
|
|
|
167
163
|
* @param postId The ID of the post.
|
|
168
164
|
* @param postType The type of the post.
|
|
169
165
|
*/
|
|
170
|
-
|
|
166
|
+
function useLastPostSave(
|
|
171
167
|
postId: number | null,
|
|
172
168
|
postType: string | null
|
|
173
169
|
): PostSaveEvent | null {
|
|
@@ -226,3 +222,159 @@ export function useLastPostSave(
|
|
|
226
222
|
|
|
227
223
|
return lastSave;
|
|
228
224
|
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Hook that fires a callback when a new collaborator joins the post.
|
|
228
|
+
* Handles initial hydration and state diffing internally—consumers
|
|
229
|
+
* only receive "join" events for collaborators that appear after the
|
|
230
|
+
* initial state has loaded.
|
|
231
|
+
*
|
|
232
|
+
* The callback receives the joining collaborator and, when available,
|
|
233
|
+
* the current user's state (useful for comparing `enteredAt` timestamps).
|
|
234
|
+
*
|
|
235
|
+
* @param postId The ID of the post.
|
|
236
|
+
* @param postType The type of the post.
|
|
237
|
+
* @param callback Invoked for each collaborator that joins.
|
|
238
|
+
*/
|
|
239
|
+
export function useOnCollaboratorJoin(
|
|
240
|
+
postId: number | null,
|
|
241
|
+
postType: string | null,
|
|
242
|
+
callback: (
|
|
243
|
+
collaborator: ActiveCollaborator,
|
|
244
|
+
me?: ActiveCollaborator
|
|
245
|
+
) => void
|
|
246
|
+
): void {
|
|
247
|
+
const { activeCollaborators } = usePostEditorAwarenessState(
|
|
248
|
+
postId,
|
|
249
|
+
postType
|
|
250
|
+
);
|
|
251
|
+
const prevCollaborators = usePrevious( activeCollaborators );
|
|
252
|
+
|
|
253
|
+
useEffect( () => {
|
|
254
|
+
/*
|
|
255
|
+
* On first render usePrevious returns undefined. On subsequent
|
|
256
|
+
* renders the list may still be empty while the store hydrates.
|
|
257
|
+
* In both cases, skip to avoid spurious "joined" callbacks for
|
|
258
|
+
* users already present.
|
|
259
|
+
*/
|
|
260
|
+
if ( ! prevCollaborators || prevCollaborators.length === 0 ) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const prevMap = new Map< number, ActiveCollaborator >(
|
|
265
|
+
prevCollaborators.map( ( collaborator ) => [
|
|
266
|
+
collaborator.clientId,
|
|
267
|
+
collaborator,
|
|
268
|
+
] )
|
|
269
|
+
);
|
|
270
|
+
const me = activeCollaborators.find(
|
|
271
|
+
( collaborator ) => collaborator.isMe
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
for ( const collaborator of activeCollaborators ) {
|
|
275
|
+
if (
|
|
276
|
+
! prevMap.has( collaborator.clientId ) &&
|
|
277
|
+
! collaborator.isMe
|
|
278
|
+
) {
|
|
279
|
+
callback( collaborator, me );
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}, [ activeCollaborators, prevCollaborators, callback ] );
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Hook that fires a callback when a collaborator leaves the post.
|
|
287
|
+
* A "leave" is detected when a previously-connected collaborator either
|
|
288
|
+
* transitions to `isConnected = false` or disappears from the list
|
|
289
|
+
* entirely while still connected. Already-disconnected collaborators
|
|
290
|
+
* that are later removed from the list are silently ignored.
|
|
291
|
+
*
|
|
292
|
+
* @param postId The ID of the post.
|
|
293
|
+
* @param postType The type of the post.
|
|
294
|
+
* @param callback Invoked for each collaborator that leaves.
|
|
295
|
+
*/
|
|
296
|
+
export function useOnCollaboratorLeave(
|
|
297
|
+
postId: number | null,
|
|
298
|
+
postType: string | null,
|
|
299
|
+
callback: ( collaborator: ActiveCollaborator ) => void
|
|
300
|
+
): void {
|
|
301
|
+
const { activeCollaborators } = usePostEditorAwarenessState(
|
|
302
|
+
postId,
|
|
303
|
+
postType
|
|
304
|
+
);
|
|
305
|
+
const prevCollaborators = usePrevious( activeCollaborators );
|
|
306
|
+
|
|
307
|
+
useEffect( () => {
|
|
308
|
+
if ( ! prevCollaborators || prevCollaborators.length === 0 ) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const newMap = new Map< number, ActiveCollaborator >(
|
|
313
|
+
activeCollaborators.map( ( collaborator ) => [
|
|
314
|
+
collaborator.clientId,
|
|
315
|
+
collaborator,
|
|
316
|
+
] )
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
for ( const prevCollab of prevCollaborators ) {
|
|
320
|
+
if ( prevCollab.isMe || ! prevCollab.isConnected ) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const newCollab = newMap.get( prevCollab.clientId );
|
|
325
|
+
if ( ! newCollab?.isConnected ) {
|
|
326
|
+
callback( prevCollab );
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}, [ activeCollaborators, prevCollaborators, callback ] );
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Hook that fires a callback when a remote collaborator saves the post.
|
|
334
|
+
* Only fires for saves by other collaborators (not the current user).
|
|
335
|
+
* Deduplicates by `savedAt` timestamp so the same save event is never
|
|
336
|
+
* reported twice.
|
|
337
|
+
*
|
|
338
|
+
* @param postId The ID of the post.
|
|
339
|
+
* @param postType The type of the post.
|
|
340
|
+
* @param callback Invoked with the save event, the collaborator who saved,
|
|
341
|
+
* and the previous save event (if any) for transition detection.
|
|
342
|
+
*/
|
|
343
|
+
export function useOnPostSave(
|
|
344
|
+
postId: number | null,
|
|
345
|
+
postType: string | null,
|
|
346
|
+
callback: (
|
|
347
|
+
event: PostSaveEvent,
|
|
348
|
+
saver: ActiveCollaborator,
|
|
349
|
+
prevEvent: PostSaveEvent | null
|
|
350
|
+
) => void
|
|
351
|
+
): void {
|
|
352
|
+
const { activeCollaborators } = usePostEditorAwarenessState(
|
|
353
|
+
postId,
|
|
354
|
+
postType
|
|
355
|
+
);
|
|
356
|
+
const lastPostSave = useLastPostSave( postId, postType );
|
|
357
|
+
const prevPostSave = usePrevious( lastPostSave );
|
|
358
|
+
|
|
359
|
+
useEffect( () => {
|
|
360
|
+
if ( ! lastPostSave ) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if ( prevPostSave && lastPostSave.savedAt === prevPostSave.savedAt ) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const saver = activeCollaborators.find(
|
|
369
|
+
( collaborator ) =>
|
|
370
|
+
collaborator.clientId === lastPostSave.savedByClientId &&
|
|
371
|
+
! collaborator.isMe
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if ( ! saver ) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
callback( lastPostSave, saver, prevPostSave ?? null );
|
|
379
|
+
}, [ lastPostSave, prevPostSave, activeCollaborators, callback ] );
|
|
380
|
+
}
|
package/src/index.js
CHANGED
|
@@ -138,6 +138,7 @@ register( store ); // Register store after unlocking private selectors to allow
|
|
|
138
138
|
* based on their values (they blur to string type).
|
|
139
139
|
*/
|
|
140
140
|
export { SelectionType } from './utils/crdt-user-selections';
|
|
141
|
+
export { SelectionDirection } from './types';
|
|
141
142
|
|
|
142
143
|
export { default as EntityProvider } from './entity-provider';
|
|
143
144
|
export * from './entity-provider';
|
package/src/private-actions.js
CHANGED
package/src/private-apis.js
CHANGED
|
@@ -6,7 +6,9 @@ import { RECEIVE_INTERMEDIATE_RESULTS } from './utils';
|
|
|
6
6
|
import {
|
|
7
7
|
useActiveCollaborators,
|
|
8
8
|
useResolvedSelection,
|
|
9
|
-
|
|
9
|
+
useOnCollaboratorJoin,
|
|
10
|
+
useOnCollaboratorLeave,
|
|
11
|
+
useOnPostSave,
|
|
10
12
|
} from './hooks/use-post-editor-awareness-state';
|
|
11
13
|
import { lock } from './lock-unlock';
|
|
12
14
|
import { retrySyncConnection } from './sync';
|
|
@@ -18,5 +20,7 @@ lock( privateApis, {
|
|
|
18
20
|
retrySyncConnection,
|
|
19
21
|
useActiveCollaborators,
|
|
20
22
|
useResolvedSelection,
|
|
21
|
-
|
|
23
|
+
useOnCollaboratorJoin,
|
|
24
|
+
useOnCollaboratorLeave,
|
|
25
|
+
useOnPostSave,
|
|
22
26
|
} );
|
|
@@ -105,19 +105,22 @@ export function items( state = {}, action ) {
|
|
|
105
105
|
case 'RECEIVE_ITEMS': {
|
|
106
106
|
const context = getContextFromAction( action );
|
|
107
107
|
const key = action.key || DEFAULT_ENTITY_KEY;
|
|
108
|
+
const itemsList = Array.isArray( action.items )
|
|
109
|
+
? action.items
|
|
110
|
+
: [ action.items ];
|
|
108
111
|
return {
|
|
109
112
|
...state,
|
|
110
113
|
[ context ]: {
|
|
111
114
|
...state[ context ],
|
|
112
|
-
...
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
...Object.fromEntries(
|
|
116
|
+
itemsList.map( ( item ) => [
|
|
117
|
+
item?.[ key ],
|
|
118
|
+
conservativeMapItem(
|
|
119
|
+
state?.[ context ]?.[ item?.[ key ] ],
|
|
120
|
+
item
|
|
121
|
+
),
|
|
122
|
+
] )
|
|
123
|
+
),
|
|
121
124
|
},
|
|
122
125
|
};
|
|
123
126
|
}
|
|
@@ -149,6 +152,9 @@ export function itemIsComplete( state = {}, action ) {
|
|
|
149
152
|
case 'RECEIVE_ITEMS': {
|
|
150
153
|
const context = getContextFromAction( action );
|
|
151
154
|
const { query, key = DEFAULT_ENTITY_KEY } = action;
|
|
155
|
+
const itemsList = Array.isArray( action.items )
|
|
156
|
+
? action.items
|
|
157
|
+
: [ action.items ];
|
|
152
158
|
|
|
153
159
|
// An item is considered complete if it is received without an associated
|
|
154
160
|
// fields query. Ideally, this would be implemented in such a way where the
|
|
@@ -164,7 +170,7 @@ export function itemIsComplete( state = {}, action ) {
|
|
|
164
170
|
...state,
|
|
165
171
|
[ context ]: {
|
|
166
172
|
...state[ context ],
|
|
167
|
-
...
|
|
173
|
+
...itemsList.reduce( ( result, item ) => {
|
|
168
174
|
const itemId = item?.[ key ];
|
|
169
175
|
|
|
170
176
|
// Defer to completeness if already assigned. Technically the
|
|
@@ -224,18 +230,24 @@ const receiveQueries = compose( [
|
|
|
224
230
|
// reducer tracks only a single query object.
|
|
225
231
|
onSubKey( 'stableKey' ),
|
|
226
232
|
] )( ( state = {}, action ) => {
|
|
227
|
-
|
|
233
|
+
if ( action.type !== 'RECEIVE_ITEMS' ) {
|
|
234
|
+
return state;
|
|
235
|
+
}
|
|
228
236
|
|
|
229
|
-
|
|
237
|
+
// Single items don't have page or total count metadata
|
|
238
|
+
// (only collection query responses do), so skip updating itemIds.
|
|
239
|
+
if ( ! Array.isArray( action.items ) ) {
|
|
230
240
|
return state;
|
|
231
241
|
}
|
|
232
242
|
|
|
243
|
+
const key = action.key ?? DEFAULT_ENTITY_KEY;
|
|
244
|
+
|
|
233
245
|
return {
|
|
234
246
|
itemIds: getMergedItemIds(
|
|
235
247
|
state?.itemIds || [],
|
|
236
248
|
action.items.map( ( item ) => item?.[ key ] ).filter( Boolean ),
|
|
237
|
-
page,
|
|
238
|
-
perPage
|
|
249
|
+
action.page,
|
|
250
|
+
action.perPage
|
|
239
251
|
),
|
|
240
252
|
meta: action.meta,
|
|
241
253
|
};
|
|
@@ -34,12 +34,8 @@ const queriedItemsCacheByState = new WeakMap();
|
|
|
34
34
|
function getQueriedItemsUncached( state, query ) {
|
|
35
35
|
const { stableKey, page, perPage, include, fields, context } =
|
|
36
36
|
getQueryParts( query );
|
|
37
|
-
let itemIds;
|
|
38
|
-
|
|
39
|
-
if ( state.queries?.[ context ]?.[ stableKey ] ) {
|
|
40
|
-
itemIds = state.queries[ context ][ stableKey ].itemIds;
|
|
41
|
-
}
|
|
42
37
|
|
|
38
|
+
const itemIds = state.queries?.[ context ]?.[ stableKey ]?.itemIds;
|
|
43
39
|
if ( ! itemIds ) {
|
|
44
40
|
return null;
|
|
45
41
|
}
|
|
@@ -50,6 +46,17 @@ function getQueriedItemsUncached( state, query ) {
|
|
|
50
46
|
? itemIds.length
|
|
51
47
|
: Math.min( startOffset + perPage, itemIds.length );
|
|
52
48
|
|
|
49
|
+
// If the requested page range exceeds the stored itemIds, the data for
|
|
50
|
+
// this specific pagination window may not have been fetched yet. Return
|
|
51
|
+
// null unless totalItems confirms we already have all available items.
|
|
52
|
+
if ( perPage !== -1 && itemIds.length < startOffset + perPage ) {
|
|
53
|
+
const totalItems =
|
|
54
|
+
state.queries[ context ][ stableKey ].meta?.totalItems;
|
|
55
|
+
if ( Number.isFinite( totalItems ) && itemIds.length < totalItems ) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
const items = [];
|
|
54
61
|
for ( let i = startOffset; i < endOffset; i++ ) {
|
|
55
62
|
const itemId = itemIds[ i ];
|
|
@@ -239,4 +239,29 @@ describe( 'getQueriedItems', () => {
|
|
|
239
239
|
|
|
240
240
|
expect( result ).toBe( null );
|
|
241
241
|
} );
|
|
242
|
+
|
|
243
|
+
it( 'should return null when per_page exceeds stored itemIds and more items exist', () => {
|
|
244
|
+
const state = {
|
|
245
|
+
items: {
|
|
246
|
+
default: {
|
|
247
|
+
1: { id: 1 },
|
|
248
|
+
2: { id: 2 },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
itemIsComplete: {
|
|
252
|
+
default: { 1: true, 2: true },
|
|
253
|
+
},
|
|
254
|
+
queries: {
|
|
255
|
+
default: {
|
|
256
|
+
'': {
|
|
257
|
+
itemIds: [ 1, 2 ],
|
|
258
|
+
meta: { totalItems: 5 },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = getQueriedItems( state, { per_page: 3 } );
|
|
265
|
+
expect( result ).toBe( null );
|
|
266
|
+
} );
|
|
242
267
|
} );
|
package/src/reducer.js
CHANGED
|
@@ -214,8 +214,11 @@ function entity( entityConfig ) {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const nextState = { ...state };
|
|
217
|
+
const itemsList = Array.isArray( action.items )
|
|
218
|
+
? action.items
|
|
219
|
+
: [ action.items ];
|
|
217
220
|
|
|
218
|
-
for ( const record of
|
|
221
|
+
for ( const record of itemsList ) {
|
|
219
222
|
const recordId = record?.[ action.key ];
|
|
220
223
|
const edits = nextState[ recordId ];
|
|
221
224
|
if ( ! edits ) {
|