@wordpress/core-data 6.18.0 → 6.19.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 (107) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +13 -1
  3. package/build/actions.js +34 -30
  4. package/build/actions.js.map +1 -1
  5. package/build/entity-provider.js +2 -99
  6. package/build/entity-provider.js.map +1 -1
  7. package/build/entity-types/helpers.js.map +1 -1
  8. package/build/footnotes/get-footnotes-order.js +35 -0
  9. package/build/footnotes/get-footnotes-order.js.map +1 -0
  10. package/build/footnotes/get-rich-text-values-cached.js +39 -0
  11. package/build/footnotes/get-rich-text-values-cached.js.map +1 -0
  12. package/build/footnotes/index.js +96 -0
  13. package/build/footnotes/index.js.map +1 -0
  14. package/build/hooks/use-entity-record.js +6 -3
  15. package/build/hooks/use-entity-record.js.map +1 -1
  16. package/build/hooks/use-resource-permissions.js.map +1 -1
  17. package/build/private-selectors.js +4 -17
  18. package/build/private-selectors.js.map +1 -1
  19. package/build/reducer.js +33 -145
  20. package/build/reducer.js.map +1 -1
  21. package/build/resolvers.js +20 -1
  22. package/build/resolvers.js.map +1 -1
  23. package/build/selectors.js +21 -25
  24. package/build/selectors.js.map +1 -1
  25. package/build/utils/get-nested-value.js +30 -0
  26. package/build/utils/get-nested-value.js.map +1 -0
  27. package/build/utils/index.js +7 -0
  28. package/build/utils/index.js.map +1 -1
  29. package/build/utils/set-nested-value.js +11 -6
  30. package/build/utils/set-nested-value.js.map +1 -1
  31. package/build-module/actions.js +32 -28
  32. package/build-module/actions.js.map +1 -1
  33. package/build-module/entity-provider.js +2 -99
  34. package/build-module/entity-provider.js.map +1 -1
  35. package/build-module/entity-types/helpers.js.map +1 -1
  36. package/build-module/footnotes/get-footnotes-order.js +27 -0
  37. package/build-module/footnotes/get-footnotes-order.js.map +1 -0
  38. package/build-module/footnotes/get-rich-text-values-cached.js +33 -0
  39. package/build-module/footnotes/get-rich-text-values-cached.js.map +1 -0
  40. package/build-module/footnotes/index.js +88 -0
  41. package/build-module/footnotes/index.js.map +1 -0
  42. package/build-module/hooks/use-entity-record.js +6 -3
  43. package/build-module/hooks/use-entity-record.js.map +1 -1
  44. package/build-module/hooks/use-resource-permissions.js.map +1 -1
  45. package/build-module/private-selectors.js +3 -15
  46. package/build-module/private-selectors.js.map +1 -1
  47. package/build-module/reducer.js +30 -144
  48. package/build-module/reducer.js.map +1 -1
  49. package/build-module/resolvers.js +18 -0
  50. package/build-module/resolvers.js.map +1 -1
  51. package/build-module/selectors.js +19 -29
  52. package/build-module/selectors.js.map +1 -1
  53. package/build-module/utils/get-nested-value.js +24 -0
  54. package/build-module/utils/get-nested-value.js.map +1 -0
  55. package/build-module/utils/index.js +1 -0
  56. package/build-module/utils/index.js.map +1 -1
  57. package/build-module/utils/set-nested-value.js +11 -6
  58. package/build-module/utils/set-nested-value.js.map +1 -1
  59. package/build-types/actions.d.ts +1 -6
  60. package/build-types/actions.d.ts.map +1 -1
  61. package/build-types/entity-provider.d.ts.map +1 -1
  62. package/build-types/footnotes/get-footnotes-order.d.ts +2 -0
  63. package/build-types/footnotes/get-footnotes-order.d.ts.map +1 -0
  64. package/build-types/footnotes/get-rich-text-values-cached.d.ts +2 -0
  65. package/build-types/footnotes/get-rich-text-values-cached.d.ts.map +1 -0
  66. package/build-types/footnotes/index.d.ts +4 -0
  67. package/build-types/footnotes/index.d.ts.map +1 -0
  68. package/build-types/hooks/use-entity-record.d.ts +2 -0
  69. package/build-types/hooks/use-entity-record.d.ts.map +1 -1
  70. package/build-types/index.d.ts +3 -2
  71. package/build-types/index.d.ts.map +1 -1
  72. package/build-types/private-selectors.d.ts +3 -13
  73. package/build-types/private-selectors.d.ts.map +1 -1
  74. package/build-types/reducer.d.ts +7 -23
  75. package/build-types/reducer.d.ts.map +1 -1
  76. package/build-types/resolvers.d.ts +4 -0
  77. package/build-types/resolvers.d.ts.map +1 -1
  78. package/build-types/selectors.d.ts +19 -14
  79. package/build-types/selectors.d.ts.map +1 -1
  80. package/build-types/utils/get-nested-value.d.ts +14 -0
  81. package/build-types/utils/get-nested-value.d.ts.map +1 -0
  82. package/build-types/utils/index.d.ts +1 -0
  83. package/build-types/utils/set-nested-value.d.ts +8 -4
  84. package/build-types/utils/set-nested-value.d.ts.map +1 -1
  85. package/package.json +16 -15
  86. package/src/actions.js +36 -26
  87. package/src/entity-provider.js +2 -134
  88. package/src/entity-types/helpers.ts +2 -2
  89. package/src/footnotes/get-footnotes-order.js +30 -0
  90. package/src/footnotes/get-rich-text-values-cached.js +35 -0
  91. package/src/footnotes/index.js +119 -0
  92. package/src/hooks/test/use-entity-record.js +4 -0
  93. package/src/hooks/use-entity-record.ts +12 -3
  94. package/src/hooks/use-resource-permissions.ts +1 -1
  95. package/src/private-selectors.ts +4 -17
  96. package/src/reducer.js +36 -155
  97. package/src/resolvers.js +25 -0
  98. package/src/selectors.ts +39 -51
  99. package/src/test/reducer.js +0 -233
  100. package/src/test/selectors.js +0 -54
  101. package/src/utils/get-nested-value.js +27 -0
  102. package/src/utils/index.js +1 -0
  103. package/src/utils/set-nested-value.js +12 -6
  104. package/src/utils/test/get-nested-value.js +61 -0
  105. package/src/utils/test/set-nested-value.js +7 -0
  106. package/tsconfig.json +1 -0
  107. package/tsconfig.tsbuildinfo +1 -1
package/src/actions.js CHANGED
@@ -14,6 +14,7 @@ import deprecated from '@wordpress/deprecated';
14
14
  /**
15
15
  * Internal dependencies
16
16
  */
17
+ import { getNestedValue, setNestedValue } from './utils';
17
18
  import { receiveItems, removeItems, receiveQueriedItems } from './queried-data';
18
19
  import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
19
20
  import { createBatch } from './batch';
@@ -391,20 +392,29 @@ export const editEntityRecord =
391
392
  edit.edits
392
393
  );
393
394
  } else {
395
+ if ( ! options.undoIgnore ) {
396
+ select.getUndoManager().addRecord(
397
+ [
398
+ {
399
+ id: { kind, name, recordId },
400
+ changes: Object.keys( edits ).reduce(
401
+ ( acc, key ) => {
402
+ acc[ key ] = {
403
+ from: editedRecord[ key ],
404
+ to: edits[ key ],
405
+ };
406
+ return acc;
407
+ },
408
+ {}
409
+ ),
410
+ },
411
+ ],
412
+ options.isCached
413
+ );
414
+ }
394
415
  dispatch( {
395
416
  type: 'EDIT_ENTITY_RECORD',
396
417
  ...edit,
397
- meta: {
398
- undo: ! options.undoIgnore && {
399
- ...edit,
400
- // Send the current values for things like the first undo stack entry.
401
- edits: Object.keys( edits ).reduce( ( acc, key ) => {
402
- acc[ key ] = editedRecord[ key ];
403
- return acc;
404
- }, {} ),
405
- isCached: options.isCached,
406
- },
407
- },
408
418
  } );
409
419
  }
410
420
  };
@@ -416,13 +426,13 @@ export const editEntityRecord =
416
426
  export const undo =
417
427
  () =>
418
428
  ( { select, dispatch } ) => {
419
- const undoEdit = select.getUndoEdits();
420
- if ( ! undoEdit ) {
429
+ const undoRecord = select.getUndoManager().undo();
430
+ if ( ! undoRecord ) {
421
431
  return;
422
432
  }
423
433
  dispatch( {
424
434
  type: 'UNDO',
425
- stackedEdits: undoEdit,
435
+ record: undoRecord,
426
436
  } );
427
437
  };
428
438
 
@@ -433,13 +443,13 @@ export const undo =
433
443
  export const redo =
434
444
  () =>
435
445
  ( { select, dispatch } ) => {
436
- const redoEdit = select.getRedoEdits();
437
- if ( ! redoEdit ) {
446
+ const redoRecord = select.getUndoManager().redo();
447
+ if ( ! redoRecord ) {
438
448
  return;
439
449
  }
440
450
  dispatch( {
441
451
  type: 'REDO',
442
- stackedEdits: redoEdit,
452
+ record: redoRecord,
443
453
  } );
444
454
  };
445
455
 
@@ -448,9 +458,11 @@ export const redo =
448
458
  *
449
459
  * @return {Object} Action object.
450
460
  */
451
- export function __unstableCreateUndoLevel() {
452
- return { type: 'CREATE_UNDO_LEVEL' };
453
- }
461
+ export const __unstableCreateUndoLevel =
462
+ () =>
463
+ ( { select } ) => {
464
+ select.getUndoManager().addRecord();
465
+ };
454
466
 
455
467
  /**
456
468
  * Action triggered to save an entity record.
@@ -779,7 +791,7 @@ export const saveEditedEntityRecord =
779
791
  * @param {string} kind Kind of the entity.
780
792
  * @param {string} name Name of the entity.
781
793
  * @param {Object} recordId ID of the record.
782
- * @param {Array} itemsToSave List of entity properties to save.
794
+ * @param {Array} itemsToSave List of entity properties or property paths to save.
783
795
  * @param {Object} options Saving options.
784
796
  */
785
797
  export const __experimentalSaveSpecifiedEntityEdits =
@@ -794,10 +806,9 @@ export const __experimentalSaveSpecifiedEntityEdits =
794
806
  recordId
795
807
  );
796
808
  const editsToSave = {};
797
- for ( const edit in edits ) {
798
- if ( itemsToSave.some( ( item ) => item === edit ) ) {
799
- editsToSave[ edit ] = edits[ edit ];
800
- }
809
+
810
+ for ( const item of itemsToSave ) {
811
+ setNestedValue( editsToSave, item, getNestedValue( edits, item ) );
801
812
  }
802
813
 
803
814
  const configs = await dispatch( getOrLoadEntitiesConfig( kind ) );
@@ -814,7 +825,6 @@ export const __experimentalSaveSpecifiedEntityEdits =
814
825
  if ( recordId ) {
815
826
  editsToSave[ entityIdKey ] = recordId;
816
827
  }
817
-
818
828
  return await dispatch.saveEntityRecord(
819
829
  kind,
820
830
  name,
@@ -9,20 +9,17 @@ import {
9
9
  } from '@wordpress/element';
10
10
  import { useSelect, useDispatch } from '@wordpress/data';
11
11
  import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
12
- import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
13
12
 
14
13
  /**
15
14
  * Internal dependencies
16
15
  */
17
16
  import { STORE_NAME } from './name';
18
- import { unlock } from './private-apis';
17
+ import { updateFootnotesFromMeta } from './footnotes';
19
18
 
20
19
  /** @typedef {import('@wordpress/blocks').WPBlock} WPBlock */
21
20
 
22
21
  const EMPTY_ARRAY = [];
23
22
 
24
- let oldFootnotes = {};
25
-
26
23
  /**
27
24
  * Internal dependencies
28
25
  */
@@ -182,136 +179,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) {
182
179
  }, [ editedBlocks, content ] );
183
180
 
184
181
  const updateFootnotes = useCallback(
185
- ( _blocks ) => {
186
- const output = { blocks: _blocks };
187
- if ( ! meta ) return output;
188
- // If meta.footnotes is empty, it means the meta is not registered.
189
- if ( meta.footnotes === undefined ) return output;
190
-
191
- const { getRichTextValues } = unlock( blockEditorPrivateApis );
192
- const _content = getRichTextValues( _blocks ).join( '' ) || '';
193
- const newOrder = [];
194
-
195
- // This can be avoided when
196
- // https://github.com/WordPress/gutenberg/pull/43204 lands. We can then
197
- // get the order directly from the rich text values.
198
- if ( _content.indexOf( 'data-fn' ) !== -1 ) {
199
- const regex = /data-fn="([^"]+)"/g;
200
- let match;
201
- while ( ( match = regex.exec( _content ) ) !== null ) {
202
- newOrder.push( match[ 1 ] );
203
- }
204
- }
205
-
206
- const footnotes = meta.footnotes
207
- ? JSON.parse( meta.footnotes )
208
- : [];
209
- const currentOrder = footnotes.map( ( fn ) => fn.id );
210
-
211
- if ( currentOrder.join( '' ) === newOrder.join( '' ) )
212
- return output;
213
-
214
- const newFootnotes = newOrder.map(
215
- ( fnId ) =>
216
- footnotes.find( ( fn ) => fn.id === fnId ) ||
217
- oldFootnotes[ fnId ] || {
218
- id: fnId,
219
- content: '',
220
- }
221
- );
222
-
223
- function updateAttributes( attributes ) {
224
- // Only attempt to update attributes, if attributes is an object.
225
- if (
226
- ! attributes ||
227
- Array.isArray( attributes ) ||
228
- typeof attributes !== 'object'
229
- ) {
230
- return attributes;
231
- }
232
-
233
- attributes = { ...attributes };
234
-
235
- for ( const key in attributes ) {
236
- const value = attributes[ key ];
237
-
238
- if ( Array.isArray( value ) ) {
239
- attributes[ key ] = value.map( updateAttributes );
240
- continue;
241
- }
242
-
243
- if ( typeof value !== 'string' ) {
244
- continue;
245
- }
246
-
247
- if ( value.indexOf( 'data-fn' ) === -1 ) {
248
- continue;
249
- }
250
-
251
- // When we store rich text values, this would no longer
252
- // require a regex.
253
- const regex =
254
- /(<sup[^>]+data-fn="([^"]+)"[^>]*><a[^>]*>)[\d*]*<\/a><\/sup>/g;
255
-
256
- attributes[ key ] = value.replace(
257
- regex,
258
- ( match, opening, fnId ) => {
259
- const index = newOrder.indexOf( fnId );
260
- return `${ opening }${ index + 1 }</a></sup>`;
261
- }
262
- );
263
-
264
- const compatRegex =
265
- /<a[^>]+data-fn="([^"]+)"[^>]*>\*<\/a>/g;
266
-
267
- attributes[ key ] = attributes[ key ].replace(
268
- compatRegex,
269
- ( match, fnId ) => {
270
- const index = newOrder.indexOf( fnId );
271
- return `<sup data-fn="${ fnId }" class="fn"><a href="#${ fnId }" id="${ fnId }-link">${
272
- index + 1
273
- }</a></sup>`;
274
- }
275
- );
276
- }
277
-
278
- return attributes;
279
- }
280
-
281
- function updateBlocksAttributes( __blocks ) {
282
- return __blocks.map( ( block ) => {
283
- return {
284
- ...block,
285
- attributes: updateAttributes( block.attributes ),
286
- innerBlocks: updateBlocksAttributes(
287
- block.innerBlocks
288
- ),
289
- };
290
- } );
291
- }
292
-
293
- // We need to go through all block attributes deeply and update the
294
- // footnote anchor numbering (textContent) to match the new order.
295
- const newBlocks = updateBlocksAttributes( _blocks );
296
-
297
- oldFootnotes = {
298
- ...oldFootnotes,
299
- ...footnotes.reduce( ( acc, fn ) => {
300
- if ( ! newOrder.includes( fn.id ) ) {
301
- acc[ fn.id ] = fn;
302
- }
303
- return acc;
304
- }, {} ),
305
- };
306
-
307
- return {
308
- meta: {
309
- ...meta,
310
- footnotes: JSON.stringify( newFootnotes ),
311
- },
312
- blocks: newBlocks,
313
- };
314
- },
182
+ ( _blocks ) => updateFootnotesFromMeta( _blocks, meta ),
315
183
  [ meta ]
316
184
  );
317
185
 
@@ -64,7 +64,7 @@ export type Context = 'view' | 'edit' | 'embed';
64
64
  export type ContextualField<
65
65
  FieldType,
66
66
  AvailableInContexts extends Context,
67
- C extends Context
67
+ C extends Context,
68
68
  > = AvailableInContexts extends C ? FieldType : never;
69
69
 
70
70
  /**
@@ -93,7 +93,7 @@ export type OmitNevers<
93
93
  : T[ K ] extends Record< string, unknown >
94
94
  ? OmitNevers< T[ K ] >
95
95
  : T[ K ];
96
- }
96
+ },
97
97
  > = Pick<
98
98
  Nevers,
99
99
  {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import getRichTextValuesCached from './get-rich-text-values-cached';
5
+
6
+ const cache = new WeakMap();
7
+
8
+ function getBlockFootnotesOrder( block ) {
9
+ if ( ! cache.has( block ) ) {
10
+ const content = getRichTextValuesCached( block ).join( '' );
11
+ const newOrder = [];
12
+
13
+ // https://github.com/WordPress/gutenberg/pull/43204 lands. We can then
14
+ // get the order directly from the rich text values.
15
+ if ( content.indexOf( 'data-fn' ) !== -1 ) {
16
+ const regex = /data-fn="([^"]+)"/g;
17
+ let match;
18
+ while ( ( match = regex.exec( content ) ) !== null ) {
19
+ newOrder.push( match[ 1 ] );
20
+ }
21
+ }
22
+ cache.set( block, newOrder );
23
+ }
24
+
25
+ return cache.get( block );
26
+ }
27
+
28
+ export default function getFootnotesOrder( blocks ) {
29
+ return blocks.flatMap( getBlockFootnotesOrder );
30
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { unlock } from '../private-apis';
10
+
11
+ // TODO: The following line should have been:
12
+ //
13
+ // const unlockedApis = unlock( blockEditorPrivateApis );
14
+ //
15
+ // But there are hidden circular dependencies in RNMobile code, specifically in
16
+ // certain native components in the `components` package that depend on
17
+ // `block-editor`. What follows is a workaround that defers the `unlock` call
18
+ // to prevent native code from failing.
19
+ //
20
+ // Fix once https://github.com/WordPress/gutenberg/issues/52692 is closed.
21
+ let unlockedApis;
22
+
23
+ const cache = new WeakMap();
24
+
25
+ export default function getRichTextValuesCached( block ) {
26
+ if ( ! unlockedApis ) {
27
+ unlockedApis = unlock( blockEditorPrivateApis );
28
+ }
29
+
30
+ if ( ! cache.has( block ) ) {
31
+ const values = unlockedApis.getRichTextValues( [ block ] );
32
+ cache.set( block, values );
33
+ }
34
+ return cache.get( block );
35
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import getFootnotesOrder from './get-footnotes-order';
5
+
6
+ let oldFootnotes = {};
7
+
8
+ export function updateFootnotesFromMeta( blocks, meta ) {
9
+ const output = { blocks };
10
+ if ( ! meta ) return output;
11
+
12
+ // If meta.footnotes is empty, it means the meta is not registered.
13
+ if ( meta.footnotes === undefined ) return output;
14
+
15
+ const newOrder = getFootnotesOrder( blocks );
16
+
17
+ const footnotes = meta.footnotes ? JSON.parse( meta.footnotes ) : [];
18
+ const currentOrder = footnotes.map( ( fn ) => fn.id );
19
+
20
+ if ( currentOrder.join( '' ) === newOrder.join( '' ) ) return output;
21
+
22
+ const newFootnotes = newOrder.map(
23
+ ( fnId ) =>
24
+ footnotes.find( ( fn ) => fn.id === fnId ) ||
25
+ oldFootnotes[ fnId ] || {
26
+ id: fnId,
27
+ content: '',
28
+ }
29
+ );
30
+
31
+ function updateAttributes( attributes ) {
32
+ // Only attempt to update attributes, if attributes is an object.
33
+ if (
34
+ ! attributes ||
35
+ Array.isArray( attributes ) ||
36
+ typeof attributes !== 'object'
37
+ ) {
38
+ return attributes;
39
+ }
40
+
41
+ attributes = { ...attributes };
42
+
43
+ for ( const key in attributes ) {
44
+ const value = attributes[ key ];
45
+
46
+ if ( Array.isArray( value ) ) {
47
+ attributes[ key ] = value.map( updateAttributes );
48
+ continue;
49
+ }
50
+
51
+ if ( typeof value !== 'string' ) {
52
+ continue;
53
+ }
54
+
55
+ if ( value.indexOf( 'data-fn' ) === -1 ) {
56
+ continue;
57
+ }
58
+
59
+ // When we store rich text values, this would no longer
60
+ // require a regex.
61
+ const regex =
62
+ /(<sup[^>]+data-fn="([^"]+)"[^>]*><a[^>]*>)[\d*]*<\/a><\/sup>/g;
63
+
64
+ attributes[ key ] = value.replace(
65
+ regex,
66
+ ( match, opening, fnId ) => {
67
+ const index = newOrder.indexOf( fnId );
68
+ return `${ opening }${ index + 1 }</a></sup>`;
69
+ }
70
+ );
71
+
72
+ const compatRegex = /<a[^>]+data-fn="([^"]+)"[^>]*>\*<\/a>/g;
73
+
74
+ attributes[ key ] = attributes[ key ].replace(
75
+ compatRegex,
76
+ ( match, fnId ) => {
77
+ const index = newOrder.indexOf( fnId );
78
+ return `<sup data-fn="${ fnId }" class="fn"><a href="#${ fnId }" id="${ fnId }-link">${
79
+ index + 1
80
+ }</a></sup>`;
81
+ }
82
+ );
83
+ }
84
+
85
+ return attributes;
86
+ }
87
+
88
+ function updateBlocksAttributes( __blocks ) {
89
+ return __blocks.map( ( block ) => {
90
+ return {
91
+ ...block,
92
+ attributes: updateAttributes( block.attributes ),
93
+ innerBlocks: updateBlocksAttributes( block.innerBlocks ),
94
+ };
95
+ } );
96
+ }
97
+
98
+ // We need to go through all block attributes deeply and update the
99
+ // footnote anchor numbering (textContent) to match the new order.
100
+ const newBlocks = updateBlocksAttributes( blocks );
101
+
102
+ oldFootnotes = {
103
+ ...oldFootnotes,
104
+ ...footnotes.reduce( ( acc, fn ) => {
105
+ if ( ! newOrder.includes( fn.id ) ) {
106
+ acc[ fn.id ] = fn;
107
+ }
108
+ return acc;
109
+ }, {} ),
110
+ };
111
+
112
+ return {
113
+ meta: {
114
+ ...meta,
115
+ footnotes: JSON.stringify( newFootnotes ),
116
+ },
117
+ blocks: newBlocks,
118
+ };
119
+ }
@@ -46,6 +46,7 @@ describe( 'useEntityRecord', () => {
46
46
  edit: expect.any( Function ),
47
47
  editedRecord: {},
48
48
  hasEdits: false,
49
+ edits: {},
49
50
  record: undefined,
50
51
  save: expect.any( Function ),
51
52
  hasResolved: false,
@@ -64,6 +65,7 @@ describe( 'useEntityRecord', () => {
64
65
  edit: expect.any( Function ),
65
66
  editedRecord: { hello: 'world', id: 1 },
66
67
  hasEdits: false,
68
+ edits: {},
67
69
  record: { hello: 'world', id: 1 },
68
70
  save: expect.any( Function ),
69
71
  hasResolved: true,
@@ -92,6 +94,7 @@ describe( 'useEntityRecord', () => {
92
94
  edit: expect.any( Function ),
93
95
  editedRecord: { hello: 'world', id: 1 },
94
96
  hasEdits: false,
97
+ edits: {},
95
98
  record: { hello: 'world', id: 1 },
96
99
  save: expect.any( Function ),
97
100
  hasResolved: true,
@@ -108,5 +111,6 @@ describe( 'useEntityRecord', () => {
108
111
 
109
112
  expect( widget.record ).toEqual( { hello: 'world', id: 1 } );
110
113
  expect( widget.editedRecord ).toEqual( { hello: 'foo', id: 1 } );
114
+ expect( widget.edits ).toEqual( { hello: 'foo' } );
111
115
  } );
112
116
  } );
@@ -19,6 +19,9 @@ export interface EntityRecordResolution< RecordType > {
19
19
  /** The edited entity record */
20
20
  editedRecord: Partial< RecordType >;
21
21
 
22
+ /** The edits to the edited entity record */
23
+ edits: Partial< RecordType >;
24
+
22
25
  /** Apply local (in-browser) edits to the edited entity record */
23
26
  edit: ( diff: Partial< RecordType > ) => void;
24
27
 
@@ -152,8 +155,8 @@ export default function useEntityRecord< RecordType >(
152
155
 
153
156
  const mutations = useMemo(
154
157
  () => ( {
155
- edit: ( record ) =>
156
- editEntityRecord( kind, name, recordId, record ),
158
+ edit: ( record, editOptions: any = {} ) =>
159
+ editEntityRecord( kind, name, recordId, record, editOptions ),
157
160
  save: ( saveOptions: any = {} ) =>
158
161
  saveEditedEntityRecord( kind, name, recordId, {
159
162
  throwOnError: true,
@@ -163,7 +166,7 @@ export default function useEntityRecord< RecordType >(
163
166
  [ editEntityRecord, kind, name, recordId, saveEditedEntityRecord ]
164
167
  );
165
168
 
166
- const { editedRecord, hasEdits } = useSelect(
169
+ const { editedRecord, hasEdits, edits } = useSelect(
167
170
  ( select ) => ( {
168
171
  editedRecord: select( coreStore ).getEditedEntityRecord(
169
172
  kind,
@@ -175,6 +178,11 @@ export default function useEntityRecord< RecordType >(
175
178
  name,
176
179
  recordId
177
180
  ),
181
+ edits: select( coreStore ).getEntityRecordNonTransientEdits(
182
+ kind,
183
+ name,
184
+ recordId
185
+ ),
178
186
  } ),
179
187
  [ kind, name, recordId ]
180
188
  );
@@ -195,6 +203,7 @@ export default function useEntityRecord< RecordType >(
195
203
  record,
196
204
  editedRecord,
197
205
  hasEdits,
206
+ edits,
198
207
  ...querySelectRest,
199
208
  ...mutations,
200
209
  };
@@ -38,7 +38,7 @@ type ResourcePermissionsResolution< IdType > = [
38
38
  HasResolved,
39
39
  ResolutionDetails &
40
40
  GlobalResourcePermissionsResolution &
41
- ( IdType extends void ? SpecificResourcePermissionsResolution : {} )
41
+ ( IdType extends void ? SpecificResourcePermissionsResolution : {} ),
42
42
  ];
43
43
 
44
44
  /**
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import type { State, UndoEdit } from './selectors';
4
+ import type { State } from './selectors';
5
5
 
6
- type Optional< T > = T | undefined;
7
6
  type EntityRecordKey = string | number;
8
7
 
9
8
  /**
@@ -12,22 +11,10 @@ type EntityRecordKey = string | number;
12
11
  *
13
12
  * @param state State tree.
14
13
  *
15
- * @return The edit.
14
+ * @return The undo manager.
16
15
  */
17
- export function getUndoEdits( state: State ): Optional< UndoEdit[] > {
18
- return state.undo.list[ state.undo.list.length - 1 + state.undo.offset ];
19
- }
20
-
21
- /**
22
- * Returns the next edit from the current undo offset
23
- * for the entity records edits history, if any.
24
- *
25
- * @param state State tree.
26
- *
27
- * @return The edit.
28
- */
29
- export function getRedoEdits( state: State ): Optional< UndoEdit[] > {
30
- return state.undo.list[ state.undo.list.length + state.undo.offset ];
16
+ export function getUndoManager( state: State ) {
17
+ return state.undoManager;
31
18
  }
32
19
 
33
20
  /**