@wordpress/core-data 7.42.0 → 7.43.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 (97) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/private-actions.cjs +10 -0
  3. package/build/private-actions.cjs.map +2 -2
  4. package/build/private-selectors.cjs +20 -3
  5. package/build/private-selectors.cjs.map +2 -2
  6. package/build/queried-data/get-query-parts.cjs +8 -7
  7. package/build/queried-data/get-query-parts.cjs.map +2 -2
  8. package/build/queried-data/reducer.cjs +15 -9
  9. package/build/queried-data/reducer.cjs.map +2 -2
  10. package/build/queried-data/selectors.cjs +3 -6
  11. package/build/queried-data/selectors.cjs.map +2 -2
  12. package/build/reducer.cjs +16 -3
  13. package/build/reducer.cjs.map +2 -2
  14. package/build/resolvers.cjs +16 -8
  15. package/build/resolvers.cjs.map +2 -2
  16. package/build/selectors.cjs +16 -8
  17. package/build/selectors.cjs.map +2 -2
  18. package/build/utils/crdt-blocks.cjs +49 -2
  19. package/build/utils/crdt-blocks.cjs.map +2 -2
  20. package/build/utils/crdt-text.cjs +52 -0
  21. package/build/utils/crdt-text.cjs.map +7 -0
  22. package/build/utils/crdt.cjs +5 -0
  23. package/build/utils/crdt.cjs.map +2 -2
  24. package/build/utils/index.cjs +0 -3
  25. package/build/utils/index.cjs.map +2 -2
  26. package/build-module/private-actions.mjs +9 -0
  27. package/build-module/private-actions.mjs.map +2 -2
  28. package/build-module/private-selectors.mjs +19 -3
  29. package/build-module/private-selectors.mjs.map +2 -2
  30. package/build-module/queried-data/get-query-parts.mjs +8 -7
  31. package/build-module/queried-data/get-query-parts.mjs.map +2 -2
  32. package/build-module/queried-data/reducer.mjs +15 -9
  33. package/build-module/queried-data/reducer.mjs.map +2 -2
  34. package/build-module/queried-data/selectors.mjs +3 -6
  35. package/build-module/queried-data/selectors.mjs.map +2 -2
  36. package/build-module/reducer.mjs +14 -2
  37. package/build-module/reducer.mjs.map +2 -2
  38. package/build-module/resolvers.mjs +14 -7
  39. package/build-module/resolvers.mjs.map +2 -2
  40. package/build-module/selectors.mjs +16 -9
  41. package/build-module/selectors.mjs.map +2 -2
  42. package/build-module/utils/crdt-blocks.mjs +48 -2
  43. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  44. package/build-module/utils/crdt-text.mjs +26 -0
  45. package/build-module/utils/crdt-text.mjs.map +7 -0
  46. package/build-module/utils/crdt.mjs +6 -0
  47. package/build-module/utils/crdt.mjs.map +2 -2
  48. package/build-module/utils/index.mjs +8 -10
  49. package/build-module/utils/index.mjs.map +2 -2
  50. package/build-types/index.d.ts.map +1 -1
  51. package/build-types/private-actions.d.ts +10 -0
  52. package/build-types/private-actions.d.ts.map +1 -1
  53. package/build-types/private-selectors.d.ts +11 -4
  54. package/build-types/private-selectors.d.ts.map +1 -1
  55. package/build-types/queried-data/get-query-parts.d.ts +11 -19
  56. package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
  57. package/build-types/queried-data/reducer.d.ts +11 -5
  58. package/build-types/queried-data/reducer.d.ts.map +1 -1
  59. package/build-types/queried-data/selectors.d.ts.map +1 -1
  60. package/build-types/reducer.d.ts +11 -0
  61. package/build-types/reducer.d.ts.map +1 -1
  62. package/build-types/resolvers.d.ts +3 -0
  63. package/build-types/resolvers.d.ts.map +1 -1
  64. package/build-types/selectors.d.ts +1 -0
  65. package/build-types/selectors.d.ts.map +1 -1
  66. package/build-types/utils/crdt-blocks.d.ts +11 -0
  67. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  68. package/build-types/utils/crdt-text.d.ts +16 -0
  69. package/build-types/utils/crdt-text.d.ts.map +1 -0
  70. package/build-types/utils/crdt.d.ts.map +1 -1
  71. package/build-types/utils/index.d.ts +0 -1
  72. package/package.json +18 -18
  73. package/src/private-actions.js +18 -0
  74. package/src/private-selectors.ts +37 -4
  75. package/src/queried-data/get-query-parts.js +14 -20
  76. package/src/queried-data/reducer.js +28 -15
  77. package/src/queried-data/selectors.js +4 -14
  78. package/src/queried-data/test/get-query-parts.js +11 -11
  79. package/src/queried-data/test/reducer.js +78 -8
  80. package/src/queried-data/test/selectors.js +22 -9
  81. package/src/reducer.js +20 -0
  82. package/src/resolvers.js +23 -7
  83. package/src/selectors.ts +20 -19
  84. package/src/utils/crdt-blocks.ts +83 -2
  85. package/src/utils/crdt-text.ts +43 -0
  86. package/src/utils/crdt.ts +10 -0
  87. package/src/utils/index.js +0 -1
  88. package/src/utils/test/crdt-blocks.ts +53 -0
  89. package/src/utils/test/crdt.ts +109 -1
  90. package/build/utils/is-raw-attribute.cjs +0 -29
  91. package/build/utils/is-raw-attribute.cjs.map +0 -7
  92. package/build-module/utils/is-raw-attribute.mjs +0 -8
  93. package/build-module/utils/is-raw-attribute.mjs.map +0 -7
  94. package/build-types/utils/is-raw-attribute.d.ts +0 -10
  95. package/build-types/utils/is-raw-attribute.d.ts.map +0 -1
  96. package/src/utils/is-raw-attribute.js +0 -11
  97. package/src/utils/test/is-raw-attribute.js +0 -22
@@ -14,6 +14,8 @@ import logEntityDeprecation from './utils/log-entity-deprecation';
14
14
 
15
15
  type EntityRecordKey = string | number;
16
16
 
17
+ const EMPTY_OBJECT = {};
18
+
17
19
  /**
18
20
  * Returns the previous edit from the current undo offset
19
21
  * for the entity records edits history, if any.
@@ -170,11 +172,18 @@ export const getHomePage = createRegistrySelector( ( select ) =>
170
172
  ).getDefaultTemplateId( {
171
173
  slug: 'front-page',
172
174
  } );
173
- // Still resolving getDefaultTemplateId.
174
- if ( ! frontPageTemplateId ) {
175
- return null;
175
+ if ( frontPageTemplateId ) {
176
+ return {
177
+ postType: 'wp_template',
178
+ postId: frontPageTemplateId,
179
+ };
176
180
  }
177
- return { postType: 'wp_template', postId: frontPageTemplateId };
181
+ // Resolution is finished and no front-page template exists.
182
+ if ( frontPageTemplateId === '' ) {
183
+ return EMPTY_OBJECT;
184
+ }
185
+ // Still resolving getDefaultTemplateId.
186
+ return null;
178
187
  },
179
188
  ( state ) => [
180
189
  // Even though getDefaultTemplateId.shouldInvalidate returns true when root/site changes,
@@ -314,3 +323,27 @@ export function getEditorAssets( state: State ): Record< string, any > | null {
314
323
  export function isCollaborationSupported( state: State ): boolean {
315
324
  return state.collaborationSupported;
316
325
  }
326
+
327
+ /**
328
+ * Returns the view configuration for the given entity type.
329
+ *
330
+ * @param state Data state.
331
+ * @param kind Entity kind.
332
+ * @param name Entity name.
333
+ *
334
+ * @return The view configuration or undefined if not loaded.
335
+ */
336
+ export function getViewConfig(
337
+ state: State,
338
+ kind: string,
339
+ name: string
340
+ ): Record< string, any > | undefined {
341
+ return (
342
+ state.viewConfigs?.[ `${ kind }/${ name }` ] ?? {
343
+ default_view: undefined,
344
+ default_layouts: undefined,
345
+ view_list: undefined,
346
+ form: undefined,
347
+ }
348
+ );
349
+ }
@@ -15,15 +15,11 @@ import { withWeakMapCache, getNormalizedCommaSeparable } from '../utils';
15
15
  *
16
16
  * @property {number} page The query page (1-based index, default 1).
17
17
  * @property {number} perPage Items per page for query (default 10).
18
- * @property {number} offset Absolute item offset (default undefined).
19
- * When present, also encoded into stableKey.
20
- * @property {string} stableKey An encoded stable string of all non-
21
- * pagination, non-fields query parameters.
22
- * @property {?(string[])} fields Target subset of fields to derive from
23
- * item objects.
24
- * @property {?(number[])} include Specific item IDs to include.
25
- * @property {string} context Scope under which the request is made;
26
- * determines returned fields in response.
18
+ * @property {?number} offset Absolute item offset (default null).
19
+ * @property {string} stableKey An encoded stable string of all non-pagination, non-fields query parameters.
20
+ * @property {?(string[])} fields Target subset of fields to derive from item objects (default null).
21
+ * @property {?(number[])} include Specific item IDs to include (default null).
22
+ * @property {string} context Scope under which the request is made; determines returned fields in response.
27
23
  */
28
24
 
29
25
  /**
@@ -43,7 +39,7 @@ export function getQueryParts( query ) {
43
39
  stableKey: '',
44
40
  page: 1,
45
41
  perPage: 10,
46
- offset: undefined,
42
+ offset: null,
47
43
  fields: null,
48
44
  include: null,
49
45
  context: 'default',
@@ -65,21 +61,19 @@ export function getQueryParts( query ) {
65
61
  parts.perPage = Number( value );
66
62
  break;
67
63
 
64
+ case 'offset': {
65
+ const numericOffset = Number( value );
66
+ if ( Number.isFinite( numericOffset ) ) {
67
+ parts.offset = numericOffset;
68
+ }
69
+ break;
70
+ }
71
+
68
72
  case 'context':
69
73
  parts.context = value;
70
74
  break;
71
75
 
72
76
  default:
73
- // Extract offset for use in pagination calculations while
74
- // still including it in the stableKey (different offsets
75
- // produce different result sets).
76
- if ( key === 'offset' ) {
77
- const numericOffset = Number( value );
78
- if ( Number.isFinite( numericOffset ) ) {
79
- parts.offset = numericOffset;
80
- }
81
- }
82
-
83
77
  // While in theory, we could exclude "_fields" from the stableKey
84
78
  // because two request with different fields have the same results
85
79
  // We're not able to ensure that because the server can decide to omit
@@ -30,24 +30,32 @@ function getContextFromAction( action ) {
30
30
  * Returns a merged array of item IDs, given details of the received paginated
31
31
  * items. The array is sparse-like with `undefined` entries where holes exist.
32
32
  *
33
- * @param {?Array<number>} itemIds Original item IDs (default empty array).
34
- * @param {number[]} nextItemIds Item IDs to merge.
35
- * @param {number} page Page of items merged.
36
- * @param {number} perPage Number of items per page.
33
+ * @param {number[]|undefined} itemIds Original item IDs (default empty array).
34
+ * @param {number[]} nextItemIds Item IDs to merge.
35
+ * @param {Object} options Options object.
36
+ * @param {number} [options.page] Page of items merged.
37
+ * @param {number} [options.offset] Offset of items merged.
38
+ * @param {number} options.perPage Number of items per page.
37
39
  *
38
40
  * @return {number[]} Merged array of item IDs.
39
41
  */
40
- export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) {
41
- const receivedAllIds = page === 1 && perPage === -1;
42
- if ( receivedAllIds ) {
42
+ export function getMergedItemIds(
43
+ itemIds = [],
44
+ nextItemIds,
45
+ // The defaults for `page` and `perPage` are the same as in `getQueryParts`.
46
+ { page = 1, offset, perPage = 10 } = {}
47
+ ) {
48
+ // If the query is unbounded, then `nextItemIds` is a complete replacement.
49
+ if ( perPage === -1 ) {
43
50
  return nextItemIds;
44
51
  }
45
- const nextItemIdsStartIndex = ( page - 1 ) * perPage;
52
+
53
+ const nextItemIdsStartIndex = offset ?? ( page - 1 ) * perPage;
46
54
 
47
55
  // If later page has already been received, default to the larger known
48
56
  // size of the existing array, else calculate as extending the existing.
49
57
  const size = Math.max(
50
- itemIds?.length ?? 0,
58
+ itemIds.length,
51
59
  nextItemIdsStartIndex + nextItemIds.length
52
60
  );
53
61
 
@@ -60,9 +68,11 @@ export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) {
60
68
  // a page could receive fewer than what was previously stored.
61
69
  const isInNextItemsRange =
62
70
  i >= nextItemIdsStartIndex && i < nextItemIdsStartIndex + perPage;
63
- mergedItemIds[ i ] = isInNextItemsRange
64
- ? nextItemIds[ i - nextItemIdsStartIndex ]
65
- : itemIds?.[ i ];
71
+ if ( isInNextItemsRange ) {
72
+ mergedItemIds[ i ] = nextItemIds[ i - nextItemIdsStartIndex ];
73
+ } else {
74
+ mergedItemIds[ i ] = itemIds[ i ];
75
+ }
66
76
  }
67
77
 
68
78
  return mergedItemIds;
@@ -244,10 +254,13 @@ const receiveQueries = compose( [
244
254
 
245
255
  return {
246
256
  itemIds: getMergedItemIds(
247
- state?.itemIds || [],
257
+ state.itemIds,
248
258
  action.items.map( ( item ) => item?.[ key ] ).filter( Boolean ),
249
- action.page,
250
- action.perPage
259
+ {
260
+ page: action.page,
261
+ offset: action.offset,
262
+ perPage: action.perPage,
263
+ }
251
264
  ),
252
265
  meta: action.meta,
253
266
  };
@@ -47,7 +47,8 @@ function getQueriedItemsUncached( state, query ) {
47
47
  return null;
48
48
  }
49
49
 
50
- const startOffset = perPage === -1 ? 0 : ( page - 1 ) * perPage;
50
+ const startOffset =
51
+ perPage === -1 ? 0 : queryOffset ?? ( page - 1 ) * perPage;
51
52
  const endOffset =
52
53
  perPage === -1
53
54
  ? itemIds.length
@@ -59,19 +60,8 @@ function getQueriedItemsUncached( state, query ) {
59
60
  if ( perPage !== -1 && itemIds.length < startOffset + perPage ) {
60
61
  const totalItems =
61
62
  state.queries[ context ][ stableKey ].meta?.totalItems;
62
- if ( Number.isFinite( totalItems ) ) {
63
- // For offset-based queries, totalItems (from X-WP-Total)
64
- // reflects the global count of all matching items, not the
65
- // count remaining after the offset. The number of items
66
- // available for this query is (totalItems - offset), so a
67
- // partial last page is expected and valid.
68
- const effectiveTotal =
69
- queryOffset !== undefined
70
- ? totalItems - queryOffset
71
- : totalItems;
72
- if ( itemIds.length < effectiveTotal ) {
73
- return null;
74
- }
63
+ if ( Number.isFinite( totalItems ) && itemIds.length < totalItems ) {
64
+ return null;
75
65
  }
76
66
  }
77
67
 
@@ -11,7 +11,7 @@ describe( 'getQueryParts', () => {
11
11
  context: 'default',
12
12
  page: 2,
13
13
  perPage: 2,
14
- offset: undefined,
14
+ offset: null,
15
15
  stableKey: '',
16
16
  fields: null,
17
17
  include: null,
@@ -29,7 +29,7 @@ describe( 'getQueryParts', () => {
29
29
  context: 'default',
30
30
  page: 1,
31
31
  perPage: 10,
32
- offset: undefined,
32
+ offset: null,
33
33
  stableKey: 'include=1',
34
34
  fields: null,
35
35
  include: [ 1 ],
@@ -45,7 +45,7 @@ describe( 'getQueryParts', () => {
45
45
  context: 'default',
46
46
  page: 1,
47
47
  perPage: 10,
48
- offset: undefined,
48
+ offset: null,
49
49
  stableKey: '%3F=%26&b=2',
50
50
  fields: null,
51
51
  include: null,
@@ -59,7 +59,7 @@ describe( 'getQueryParts', () => {
59
59
  context: 'default',
60
60
  page: 1,
61
61
  perPage: 10,
62
- offset: undefined,
62
+ offset: null,
63
63
  stableKey: 'a%5B0%5D=1&a%5B1%5D=2',
64
64
  fields: null,
65
65
  include: null,
@@ -75,7 +75,7 @@ describe( 'getQueryParts', () => {
75
75
  context: 'default',
76
76
  page: 1,
77
77
  perPage: 10,
78
- offset: undefined,
78
+ offset: null,
79
79
  stableKey: 'b=2',
80
80
  fields: null,
81
81
  include: null,
@@ -89,7 +89,7 @@ describe( 'getQueryParts', () => {
89
89
  context: 'default',
90
90
  page: 1,
91
91
  perPage: -1,
92
- offset: undefined,
92
+ offset: null,
93
93
  stableKey: 'b=2',
94
94
  fields: null,
95
95
  include: null,
@@ -103,7 +103,7 @@ describe( 'getQueryParts', () => {
103
103
  context: 'default',
104
104
  page: 1,
105
105
  perPage: 10,
106
- offset: undefined,
106
+ offset: null,
107
107
  stableKey: '_fields=id%2Ctitle',
108
108
  fields: [ 'id', 'title' ],
109
109
  include: null,
@@ -116,7 +116,7 @@ describe( 'getQueryParts', () => {
116
116
  expect( parts ).toEqual( {
117
117
  page: 1,
118
118
  perPage: 10,
119
- offset: undefined,
119
+ offset: null,
120
120
  stableKey: '',
121
121
  include: null,
122
122
  fields: null,
@@ -124,7 +124,7 @@ describe( 'getQueryParts', () => {
124
124
  } );
125
125
  } );
126
126
 
127
- it( 'extracts offset and includes it in stableKey', () => {
127
+ it( 'extracts offset and excludes it from stableKey', () => {
128
128
  const parts = getQueryParts( {
129
129
  per_page: 50,
130
130
  offset: 100,
@@ -135,7 +135,7 @@ describe( 'getQueryParts', () => {
135
135
  page: 1,
136
136
  perPage: 50,
137
137
  offset: 100,
138
- stableKey: 'offset=100',
138
+ stableKey: '',
139
139
  fields: null,
140
140
  include: null,
141
141
  } );
@@ -147,6 +147,6 @@ describe( 'getQueryParts', () => {
147
147
  offset: 'abc',
148
148
  } );
149
149
 
150
- expect( parts.offset ).toBeUndefined();
150
+ expect( parts.offset ).toBeNull();
151
151
  } );
152
152
  } );
@@ -11,7 +11,10 @@ import { removeItems } from '../actions';
11
11
 
12
12
  describe( 'getMergedItemIds', () => {
13
13
  it( 'should receive a page', () => {
14
- const result = getMergedItemIds( [], [ 4, 5, 6 ], 2, 3 );
14
+ const result = getMergedItemIds( [], [ 4, 5, 6 ], {
15
+ page: 2,
16
+ perPage: 3,
17
+ } );
15
18
 
16
19
  expect( result ).toEqual( [
17
20
  undefined,
@@ -32,46 +35,68 @@ describe( 'getMergedItemIds', () => {
32
35
  5,
33
36
  6,
34
37
  ] );
35
- const result = getMergedItemIds( original, [ 1, 2, 3 ], 1, 3 );
38
+ const result = getMergedItemIds( original, [ 1, 2, 3 ], {
39
+ page: 1,
40
+ perPage: 3,
41
+ } );
36
42
 
37
43
  expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6 ] );
38
44
  } );
39
45
 
40
46
  it( 'should replace with new page', () => {
41
47
  const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
42
- const result = getMergedItemIds( original, [ 'replaced', 5, 6 ], 2, 3 );
48
+ const result = getMergedItemIds( original, [ 'replaced', 5, 6 ], {
49
+ page: 2,
50
+ perPage: 3,
51
+ } );
43
52
 
44
53
  expect( result ).toEqual( [ 1, 2, 3, 'replaced', 5, 6 ] );
45
54
  } );
46
55
 
47
56
  it( 'should append a new partial page', () => {
48
57
  const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
49
- const result = getMergedItemIds( original, [ 7 ], 3, 3 );
58
+ const result = getMergedItemIds( original, [ 7 ], {
59
+ page: 3,
60
+ perPage: 3,
61
+ } );
50
62
 
51
63
  expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6, 7 ] );
52
64
  } );
53
65
 
54
66
  it( 'should return a copy of nextItemIds if it represents all ids (single id removed) (page=1 and perPage=-1)', () => {
55
67
  const original = deepFreeze( [ 1, 2, 3 ] );
56
- const result = getMergedItemIds( original, [ 1, 3 ], 1, -1 );
68
+ const result = getMergedItemIds( original, [ 1, 3 ], {
69
+ page: 1,
70
+ perPage: -1,
71
+ } );
57
72
 
58
73
  expect( result ).toEqual( [ 1, 3 ] );
59
74
  } );
60
75
 
61
76
  it( 'should return a copy of nextItemIds if it represents all ids (single id removed and another one added) (page=1 and perPage=-1)', () => {
62
77
  const original = deepFreeze( [ 1, 2, 3 ] );
63
- const result = getMergedItemIds( original, [ 1, 3, 4 ], 1, -1 );
78
+ const result = getMergedItemIds( original, [ 1, 3, 4 ], {
79
+ page: 1,
80
+ perPage: -1,
81
+ } );
64
82
 
65
83
  expect( result ).toEqual( [ 1, 3, 4 ] );
66
84
  } );
85
+
67
86
  it( 'should update a page properly if less items are provided than previously stored', () => {
68
87
  let original = deepFreeze( [ 1, 2, 3 ] );
69
- let result = getMergedItemIds( original, [ 1, 2 ], 1, 3 );
88
+ let result = getMergedItemIds( original, [ 1, 2 ], {
89
+ page: 1,
90
+ perPage: 3,
91
+ } );
70
92
 
71
93
  expect( result ).toEqual( [ 1, 2 ] );
72
94
 
73
95
  original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
74
- result = getMergedItemIds( original, [ 9 ], 2, 2 );
96
+ result = getMergedItemIds( original, [ 9 ], {
97
+ page: 2,
98
+ perPage: 2,
99
+ } );
75
100
 
76
101
  expect( result ).toEqual( [ 1, 2, 9, undefined, 5, 6 ] );
77
102
  } );
@@ -197,6 +222,51 @@ describe( 'reducer', () => {
197
222
  } );
198
223
  } );
199
224
 
225
+ it( 'receives a sparse subset of items at given offsets', () => {
226
+ const original = deepFreeze( {
227
+ items: { default: {} },
228
+ queries: {},
229
+ itemIsComplete: { default: {} },
230
+ } );
231
+ const state = [
232
+ {
233
+ type: 'RECEIVE_ITEMS',
234
+ query: { offset: 1, per_page: 2 },
235
+ items: [
236
+ { id: 2, name: 'def' },
237
+ { id: 3, name: 'ghi' },
238
+ ],
239
+ },
240
+ {
241
+ type: 'RECEIVE_ITEMS',
242
+ query: { offset: 4, per_page: 2 },
243
+ items: [
244
+ { id: 5, name: 'mno' },
245
+ { id: 6, name: 'pqr' },
246
+ ],
247
+ },
248
+ ].reduce( reducer, original );
249
+
250
+ expect( state ).toEqual( {
251
+ items: {
252
+ default: {
253
+ 2: { id: 2, name: 'def' },
254
+ 3: { id: 3, name: 'ghi' },
255
+ 5: { id: 5, name: 'mno' },
256
+ 6: { id: 6, name: 'pqr' },
257
+ },
258
+ },
259
+ itemIsComplete: {
260
+ default: { 2: true, 3: true, 5: true, 6: true },
261
+ },
262
+ queries: {
263
+ default: {
264
+ '': { itemIds: [ undefined, 2, 3, undefined, 5, 6 ] },
265
+ },
266
+ },
267
+ } );
268
+ } );
269
+
200
270
  it( 'deletes an item', () => {
201
271
  const kind = 'root';
202
272
  const name = 'menu';
@@ -2,6 +2,7 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import { getQueriedItems } from '../selectors';
5
+ import { getMergedItemIds } from '../reducer';
5
6
 
6
7
  describe( 'getQueriedItems', () => {
7
8
  it( 'should return null if requesting but no item IDs', () => {
@@ -284,8 +285,11 @@ describe( 'getQueriedItems', () => {
284
285
  },
285
286
  queries: {
286
287
  default: {
287
- 'offset=100': {
288
- itemIds: [ 101, 102, 103 ],
288
+ '': {
289
+ itemIds: getMergedItemIds( [], [ 101, 102, 103 ], {
290
+ offset: 100,
291
+ perPage: 50,
292
+ } ),
289
293
  meta: { totalItems: 103 },
290
294
  },
291
295
  },
@@ -314,8 +318,11 @@ describe( 'getQueriedItems', () => {
314
318
  },
315
319
  queries: {
316
320
  default: {
317
- 'offset=50': {
318
- itemIds: [ 51, 52 ],
321
+ '': {
322
+ itemIds: getMergedItemIds( [], [ 51, 52 ], {
323
+ offset: 50,
324
+ perPage: 50,
325
+ } ),
319
326
  meta: { totalItems: 200 },
320
327
  },
321
328
  },
@@ -348,8 +355,11 @@ describe( 'getQueriedItems', () => {
348
355
  },
349
356
  queries: {
350
357
  default: {
351
- 'offset=3': {
352
- itemIds: [ 4, 5, 6, 7, 8 ],
358
+ '': {
359
+ itemIds: getMergedItemIds( [], [ 4, 5, 6, 7, 8 ], {
360
+ offset: 3,
361
+ perPage: 10,
362
+ } ),
353
363
  meta: { totalItems: 50 },
354
364
  },
355
365
  },
@@ -379,7 +389,7 @@ describe( 'getQueriedItems', () => {
379
389
  },
380
390
  queries: {
381
391
  default: {
382
- 'offset=0': {
392
+ '': {
383
393
  itemIds: [ 1, 2 ],
384
394
  meta: { totalItems: 5 },
385
395
  },
@@ -408,8 +418,11 @@ describe( 'getQueriedItems', () => {
408
418
  },
409
419
  queries: {
410
420
  default: {
411
- 'offset=84': {
412
- itemIds: [],
421
+ '': {
422
+ itemIds: getMergedItemIds( [], [], {
423
+ offset: 84,
424
+ perPage: 7,
425
+ } ),
413
426
  meta: { totalItems: 84 },
414
427
  },
415
428
  },
package/src/reducer.js CHANGED
@@ -721,6 +721,25 @@ export function collaborationSupported( state = true, action ) {
721
721
  return state;
722
722
  }
723
723
 
724
+ /**
725
+ * Reducer managing view configs, keyed by `kind/name`.
726
+ *
727
+ * @param {Object} state Current state.
728
+ * @param {Object} action Dispatched action.
729
+ *
730
+ * @return {Object} Updated state.
731
+ */
732
+ export function viewConfigs( state = {}, action ) {
733
+ switch ( action.type ) {
734
+ case 'RECEIVE_VIEW_CONFIG':
735
+ return {
736
+ ...state,
737
+ [ `${ action.kind }/${ action.name }` ]: action.config,
738
+ };
739
+ }
740
+ return state;
741
+ }
742
+
724
743
  export default combineReducers( {
725
744
  users,
726
745
  currentTheme,
@@ -745,4 +764,5 @@ export default combineReducers( {
745
764
  editorAssets,
746
765
  syncConnectionStatuses,
747
766
  collaborationSupported,
767
+ viewConfigs,
748
768
  } );
package/src/resolvers.js CHANGED
@@ -991,11 +991,12 @@ export const getDefaultTemplateId =
991
991
  const id = window?.__experimentalTemplateActivate
992
992
  ? template?.wp_id || template?.id
993
993
  : template?.id;
994
- // Endpoint may return an empty object if no template is found.
995
- if ( id ) {
996
- template.id = id;
997
- registry.batch( () => {
998
- dispatch.receiveDefaultTemplateId( query, id );
994
+
995
+ registry.batch( () => {
996
+ dispatch.receiveDefaultTemplateId( query, id || '' );
997
+ // Endpoint may return an empty object if no template is found.
998
+ if ( id ) {
999
+ template.id = id;
999
1000
  dispatch.receiveEntityRecords(
1000
1001
  'postType',
1001
1002
  template.type,
@@ -1007,8 +1008,8 @@ export const getDefaultTemplateId =
1007
1008
  template.type,
1008
1009
  id,
1009
1010
  ] );
1010
- } );
1011
- }
1011
+ }
1012
+ } );
1012
1013
  };
1013
1014
 
1014
1015
  getDefaultTemplateId.shouldInvalidate = ( action ) => {
@@ -1320,3 +1321,18 @@ export const getEditorAssets =
1320
1321
  } );
1321
1322
  dispatch.receiveEditorAssets( assets );
1322
1323
  };
1324
+
1325
+ /**
1326
+ * Requests view config for a given entity type from the REST API.
1327
+ *
1328
+ * @param {string} kind Entity kind.
1329
+ * @param {string} name Entity name.
1330
+ */
1331
+ export const getViewConfig =
1332
+ ( kind, name ) =>
1333
+ async ( { dispatch } ) => {
1334
+ const config = await apiFetch( {
1335
+ path: addQueryArgs( '/wp/v2/view-config', { kind, name } ),
1336
+ } );
1337
+ dispatch.receiveViewConfig( kind, name, config );
1338
+ };
package/src/selectors.ts CHANGED
@@ -20,7 +20,6 @@ import { DEFAULT_ENTITY_KEY } from './entities';
20
20
  import { getUndoManager } from './private-selectors';
21
21
  import {
22
22
  getNormalizedCommaSeparable,
23
- isRawAttribute,
24
23
  setNestedValue,
25
24
  isNumericID,
26
25
  getUserPermissionCacheKey,
@@ -55,6 +54,7 @@ export interface State {
55
54
  editorAssets: Record< string, any > | null;
56
55
  syncConnectionStatuses?: Record< string, ConnectionStatus >;
57
56
  collaborationSupported: boolean;
57
+ viewConfigs: Record< string, Record< string, any > >;
58
58
  }
59
59
 
60
60
  type EntityRecordKey = string | number;
@@ -523,25 +523,26 @@ export const getRawEntityRecord = createSelector(
523
523
  name,
524
524
  key
525
525
  );
526
- return (
527
- record &&
528
- Object.keys( record ).reduce( ( accumulator, _key ) => {
529
- if (
530
- isRawAttribute( getEntityConfig( state, kind, name ), _key )
531
- ) {
532
- // Because edits are the "raw" attribute values,
533
- // we return those from record selectors to make rendering,
534
- // comparisons, and joins with edits easier.
535
- accumulator[ _key ] =
536
- record[ _key ]?.raw !== undefined
537
- ? record[ _key ]?.raw
538
- : record[ _key ];
539
- } else {
540
- accumulator[ _key ] = record[ _key ];
526
+ const config = getEntityConfig( state, kind, name );
527
+ if ( ! record || ! config?.rawAttributes?.length ) {
528
+ return record;
529
+ }
530
+
531
+ // Because edits are the "raw" attribute values,
532
+ // we return those from record selectors to make rendering,
533
+ // comparisons, and joins with edits easier.
534
+ return Object.fromEntries(
535
+ Object.keys( record ).map( ( _key ) => {
536
+ if ( config.rawAttributes.includes( _key ) ) {
537
+ const rawValue = record[ _key ]?.raw;
538
+ return [
539
+ _key,
540
+ rawValue !== undefined ? rawValue : record[ _key ],
541
+ ];
541
542
  }
542
- return accumulator;
543
- }, {} as any )
544
- );
543
+ return [ _key, record[ _key ] ];
544
+ } )
545
+ ) as EntityRecord;
545
546
  },
546
547
  (
547
548
  state: State,