@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.
Files changed (161) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +19 -0
  3. package/build/actions.cjs +25 -31
  4. package/build/actions.cjs.map +2 -2
  5. package/build/awareness/post-editor-awareness.cjs +34 -1
  6. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  7. package/build/awareness/types.cjs.map +1 -1
  8. package/build/entities.cjs +3 -2
  9. package/build/entities.cjs.map +2 -2
  10. package/build/entity-provider.cjs +15 -6
  11. package/build/entity-provider.cjs.map +2 -2
  12. package/build/hooks/use-entity-prop.cjs +33 -2
  13. package/build/hooks/use-entity-prop.cjs.map +2 -2
  14. package/build/hooks/use-post-editor-awareness-state.cjs +83 -2
  15. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  16. package/build/index.cjs +3 -0
  17. package/build/index.cjs.map +2 -2
  18. package/build/private-actions.cjs +1 -1
  19. package/build/private-actions.cjs.map +2 -2
  20. package/build/private-apis.cjs +3 -1
  21. package/build/private-apis.cjs.map +2 -2
  22. package/build/queried-data/actions.cjs +1 -1
  23. package/build/queried-data/actions.cjs.map +2 -2
  24. package/build/queried-data/reducer.cjs +19 -13
  25. package/build/queried-data/reducer.cjs.map +2 -2
  26. package/build/queried-data/selectors.cjs +7 -4
  27. package/build/queried-data/selectors.cjs.map +2 -2
  28. package/build/reducer.cjs +2 -1
  29. package/build/reducer.cjs.map +2 -2
  30. package/build/resolvers.cjs +114 -76
  31. package/build/resolvers.cjs.map +2 -2
  32. package/build/selectors.cjs +29 -0
  33. package/build/selectors.cjs.map +2 -2
  34. package/build/sync.cjs +3 -0
  35. package/build/sync.cjs.map +2 -2
  36. package/build/types.cjs +16 -0
  37. package/build/types.cjs.map +3 -3
  38. package/build/utils/crdt-blocks.cjs +22 -26
  39. package/build/utils/crdt-blocks.cjs.map +2 -2
  40. package/build/utils/crdt-user-selections.cjs +8 -5
  41. package/build/utils/crdt-user-selections.cjs.map +2 -2
  42. package/build/utils/crdt.cjs +1 -3
  43. package/build/utils/crdt.cjs.map +2 -2
  44. package/build/utils/index.cjs +3 -0
  45. package/build/utils/index.cjs.map +2 -2
  46. package/build/utils/normalize-query-for-resolution.cjs +35 -0
  47. package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
  48. package/build/utils/user-permissions.cjs +1 -4
  49. package/build/utils/user-permissions.cjs.map +2 -2
  50. package/build-module/actions.mjs +30 -32
  51. package/build-module/actions.mjs.map +2 -2
  52. package/build-module/awareness/post-editor-awareness.mjs +34 -1
  53. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  54. package/build-module/entities.mjs +3 -2
  55. package/build-module/entities.mjs.map +2 -2
  56. package/build-module/entity-provider.mjs +15 -6
  57. package/build-module/entity-provider.mjs.map +2 -2
  58. package/build-module/hooks/use-entity-prop.mjs +34 -3
  59. package/build-module/hooks/use-entity-prop.mjs.map +2 -2
  60. package/build-module/hooks/use-post-editor-awareness-state.mjs +80 -1
  61. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  62. package/build-module/index.mjs +2 -0
  63. package/build-module/index.mjs.map +2 -2
  64. package/build-module/private-actions.mjs +1 -1
  65. package/build-module/private-actions.mjs.map +2 -2
  66. package/build-module/private-apis.mjs +6 -2
  67. package/build-module/private-apis.mjs.map +2 -2
  68. package/build-module/queried-data/actions.mjs +1 -1
  69. package/build-module/queried-data/actions.mjs.map +2 -2
  70. package/build-module/queried-data/reducer.mjs +19 -13
  71. package/build-module/queried-data/reducer.mjs.map +2 -2
  72. package/build-module/queried-data/selectors.mjs +7 -4
  73. package/build-module/queried-data/selectors.mjs.map +2 -2
  74. package/build-module/reducer.mjs +2 -1
  75. package/build-module/reducer.mjs.map +2 -2
  76. package/build-module/resolvers.mjs +116 -77
  77. package/build-module/resolvers.mjs.map +2 -2
  78. package/build-module/selectors.mjs +28 -0
  79. package/build-module/selectors.mjs.map +2 -2
  80. package/build-module/sync.mjs +2 -0
  81. package/build-module/sync.mjs.map +2 -2
  82. package/build-module/types.mjs +9 -0
  83. package/build-module/types.mjs.map +4 -4
  84. package/build-module/utils/crdt-blocks.mjs +22 -26
  85. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  86. package/build-module/utils/crdt-user-selections.mjs +8 -5
  87. package/build-module/utils/crdt-user-selections.mjs.map +2 -2
  88. package/build-module/utils/crdt.mjs +1 -3
  89. package/build-module/utils/crdt.mjs.map +2 -2
  90. package/build-module/utils/index.mjs +2 -0
  91. package/build-module/utils/index.mjs.map +2 -2
  92. package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
  93. package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
  94. package/build-module/utils/user-permissions.mjs +1 -4
  95. package/build-module/utils/user-permissions.mjs.map +2 -2
  96. package/build-types/actions.d.ts.map +1 -1
  97. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  98. package/build-types/awareness/types.d.ts +1 -1
  99. package/build-types/awareness/types.d.ts.map +1 -1
  100. package/build-types/entities.d.ts +1 -1
  101. package/build-types/entities.d.ts.map +1 -1
  102. package/build-types/entity-provider.d.ts +11 -6
  103. package/build-types/entity-provider.d.ts.map +1 -1
  104. package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
  105. package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
  106. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  107. package/build-types/index.d.ts +2 -0
  108. package/build-types/index.d.ts.map +1 -1
  109. package/build-types/private-apis.d.ts.map +1 -1
  110. package/build-types/queried-data/reducer.d.ts.map +1 -1
  111. package/build-types/queried-data/selectors.d.ts.map +1 -1
  112. package/build-types/reducer.d.ts.map +1 -1
  113. package/build-types/resolvers.d.ts +2 -1
  114. package/build-types/resolvers.d.ts.map +1 -1
  115. package/build-types/selectors.d.ts +17 -0
  116. package/build-types/selectors.d.ts.map +1 -1
  117. package/build-types/sync.d.ts +2 -2
  118. package/build-types/sync.d.ts.map +1 -1
  119. package/build-types/types.d.ts +15 -0
  120. package/build-types/types.d.ts.map +1 -1
  121. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  122. package/build-types/utils/crdt-user-selections.d.ts +10 -5
  123. package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
  124. package/build-types/utils/crdt.d.ts.map +1 -1
  125. package/build-types/utils/index.d.ts +1 -0
  126. package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
  127. package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
  128. package/build-types/utils/user-permissions.d.ts.map +1 -1
  129. package/package.json +18 -18
  130. package/src/actions.js +49 -50
  131. package/src/awareness/post-editor-awareness.ts +93 -1
  132. package/src/awareness/test/post-editor-awareness.ts +35 -0
  133. package/src/awareness/types.ts +1 -1
  134. package/src/entities.js +2 -1
  135. package/src/entity-provider.js +24 -11
  136. package/src/hooks/test/use-post-editor-awareness-state.ts +443 -0
  137. package/src/hooks/use-entity-prop.js +43 -3
  138. package/src/hooks/use-post-editor-awareness-state.ts +159 -7
  139. package/src/index.js +1 -0
  140. package/src/private-actions.js +1 -1
  141. package/src/private-apis.js +6 -2
  142. package/src/queried-data/actions.js +1 -1
  143. package/src/queried-data/reducer.js +26 -14
  144. package/src/queried-data/selectors.js +12 -5
  145. package/src/queried-data/test/selectors.js +25 -0
  146. package/src/reducer.js +4 -1
  147. package/src/resolvers.js +141 -91
  148. package/src/selectors.ts +56 -0
  149. package/src/sync.ts +2 -0
  150. package/src/test/private-actions.js +1 -1
  151. package/src/test/resolvers.js +88 -14
  152. package/src/test/selectors.js +150 -0
  153. package/src/test/store.js +182 -0
  154. package/src/types.ts +19 -0
  155. package/src/utils/crdt-blocks.ts +47 -54
  156. package/src/utils/crdt-user-selections.ts +28 -16
  157. package/src/utils/crdt.ts +2 -7
  158. package/src/utils/index.js +1 -0
  159. package/src/utils/normalize-query-for-resolution.js +23 -0
  160. package/src/utils/test/crdt-blocks.ts +42 -24
  161. 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
- export function useLastPostSave(
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';
@@ -103,7 +103,7 @@ export const editMediaEntity =
103
103
  dispatch.receiveEntityRecords(
104
104
  kind,
105
105
  name,
106
- [ newRecord ],
106
+ newRecord,
107
107
  undefined,
108
108
  true,
109
109
  undefined,
@@ -6,7 +6,9 @@ import { RECEIVE_INTERMEDIATE_RESULTS } from './utils';
6
6
  import {
7
7
  useActiveCollaborators,
8
8
  useResolvedSelection,
9
- useLastPostSave,
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
- useLastPostSave,
23
+ useOnCollaboratorJoin,
24
+ useOnCollaboratorLeave,
25
+ useOnPostSave,
22
26
  } );
@@ -10,7 +10,7 @@
10
10
  export function receiveItems( items, edits, meta ) {
11
11
  return {
12
12
  type: 'RECEIVE_ITEMS',
13
- items: Array.isArray( items ) ? items : [ items ],
13
+ items,
14
14
  persistedEdits: edits,
15
15
  meta,
16
16
  };
@@ -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
- ...action.items.reduce( ( accumulator, value ) => {
113
- const itemId = value?.[ key ];
114
-
115
- accumulator[ itemId ] = conservativeMapItem(
116
- state?.[ context ]?.[ itemId ],
117
- value
118
- );
119
- return accumulator;
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
- ...action.items.reduce( ( result, item ) => {
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
- const { type, page, perPage, key = DEFAULT_ENTITY_KEY } = action;
233
+ if ( action.type !== 'RECEIVE_ITEMS' ) {
234
+ return state;
235
+ }
228
236
 
229
- if ( type !== 'RECEIVE_ITEMS' ) {
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 action.items ) {
221
+ for ( const record of itemsList ) {
219
222
  const recordId = record?.[ action.key ];
220
223
  const edits = nextState[ recordId ];
221
224
  if ( ! edits ) {