@wordpress/core-data 7.50.1-next.v.202607070741.0 → 7.52.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 (139) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/actions.cjs +54 -1
  3. package/build/actions.cjs.map +3 -3
  4. package/build/components/entities-saved-states/entity-record-item.cjs +67 -0
  5. package/build/components/entities-saved-states/entity-record-item.cjs.map +7 -0
  6. package/build/components/entities-saved-states/entity-type-list.cjs +135 -0
  7. package/build/components/entities-saved-states/entity-type-list.cjs.map +7 -0
  8. package/build/components/entities-saved-states/hooks/use-is-dirty.cjs +97 -0
  9. package/build/components/entities-saved-states/hooks/use-is-dirty.cjs.map +7 -0
  10. package/build/components/entities-saved-states/index.cjs +215 -0
  11. package/build/components/entities-saved-states/index.cjs.map +7 -0
  12. package/build/entities.cjs +3 -1
  13. package/build/entities.cjs.map +2 -2
  14. package/build/private-actions.cjs +85 -0
  15. package/build/private-actions.cjs.map +3 -3
  16. package/build/private-apis.cjs +19 -0
  17. package/build/private-apis.cjs.map +3 -3
  18. package/build/utils/crdt.cjs +4 -2
  19. package/build/utils/crdt.cjs.map +2 -2
  20. package/build/utils/get-template-info.cjs +53 -0
  21. package/build/utils/get-template-info.cjs.map +7 -0
  22. package/build/utils/get-template-part-icon.cjs +43 -0
  23. package/build/utils/get-template-part-icon.cjs.map +7 -0
  24. package/build-module/actions.mjs +57 -1
  25. package/build-module/actions.mjs.map +2 -2
  26. package/build-module/components/entities-saved-states/entity-record-item.mjs +46 -0
  27. package/build-module/components/entities-saved-states/entity-record-item.mjs.map +7 -0
  28. package/build-module/components/entities-saved-states/entity-type-list.mjs +104 -0
  29. package/build-module/components/entities-saved-states/entity-type-list.mjs.map +7 -0
  30. package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs +72 -0
  31. package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs.map +7 -0
  32. package/build-module/components/entities-saved-states/index.mjs +187 -0
  33. package/build-module/components/entities-saved-states/index.mjs.map +7 -0
  34. package/build-module/entities.mjs +3 -1
  35. package/build-module/entities.mjs.map +2 -2
  36. package/build-module/private-actions.mjs +84 -0
  37. package/build-module/private-actions.mjs.map +2 -2
  38. package/build-module/private-apis.mjs +12 -0
  39. package/build-module/private-apis.mjs.map +2 -2
  40. package/build-module/utils/crdt.mjs +2 -1
  41. package/build-module/utils/crdt.mjs.map +2 -2
  42. package/build-module/utils/get-template-info.mjs +28 -0
  43. package/build-module/utils/get-template-info.mjs.map +7 -0
  44. package/build-module/utils/get-template-part-icon.mjs +24 -0
  45. package/build-module/utils/get-template-part-icon.mjs.map +7 -0
  46. package/build-types/actions.d.ts +3 -3
  47. package/build-types/actions.d.ts.map +1 -1
  48. package/build-types/awareness/base-awareness.d.ts +3 -0
  49. package/build-types/awareness/base-awareness.d.ts.map +1 -1
  50. package/build-types/awareness/post-editor-awareness.d.ts +7 -0
  51. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  52. package/build-types/batch/create-batch.d.ts +1 -1
  53. package/build-types/batch/create-batch.d.ts.map +1 -1
  54. package/build-types/components/entities-saved-states/entity-record-item.d.ts +6 -0
  55. package/build-types/components/entities-saved-states/entity-record-item.d.ts.map +1 -0
  56. package/build-types/components/entities-saved-states/entity-type-list.d.ts +6 -0
  57. package/build-types/components/entities-saved-states/entity-type-list.d.ts.map +1 -0
  58. package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts +11 -0
  59. package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts.map +1 -0
  60. package/build-types/components/entities-saved-states/index.d.ts +49 -0
  61. package/build-types/components/entities-saved-states/index.d.ts.map +1 -0
  62. package/build-types/entities.d.ts +2 -2
  63. package/build-types/entities.d.ts.map +1 -1
  64. package/build-types/entity-provider.d.ts +1 -1
  65. package/build-types/entity-provider.d.ts.map +1 -1
  66. package/build-types/entity-types/attachment.d.ts.map +1 -1
  67. package/build-types/entity-types/base-entity-records.d.ts.map +1 -1
  68. package/build-types/entity-types/base.d.ts.map +1 -1
  69. package/build-types/entity-types/comment.d.ts.map +1 -1
  70. package/build-types/entity-types/font-collection.d.ts.map +1 -1
  71. package/build-types/entity-types/font-family.d.ts.map +1 -1
  72. package/build-types/entity-types/global-styles-revision.d.ts.map +1 -1
  73. package/build-types/entity-types/icon.d.ts.map +1 -1
  74. package/build-types/entity-types/menu-location.d.ts.map +1 -1
  75. package/build-types/entity-types/nav-menu-item.d.ts.map +1 -1
  76. package/build-types/entity-types/nav-menu.d.ts.map +1 -1
  77. package/build-types/entity-types/page.d.ts.map +1 -1
  78. package/build-types/entity-types/plugin.d.ts.map +1 -1
  79. package/build-types/entity-types/post-revision.d.ts.map +1 -1
  80. package/build-types/entity-types/post-status.d.ts.map +1 -1
  81. package/build-types/entity-types/post.d.ts.map +1 -1
  82. package/build-types/entity-types/settings.d.ts.map +1 -1
  83. package/build-types/entity-types/sidebar.d.ts.map +1 -1
  84. package/build-types/entity-types/taxonomy.d.ts.map +1 -1
  85. package/build-types/entity-types/term.d.ts.map +1 -1
  86. package/build-types/entity-types/theme.d.ts.map +1 -1
  87. package/build-types/entity-types/type.d.ts.map +1 -1
  88. package/build-types/entity-types/user.d.ts.map +1 -1
  89. package/build-types/entity-types/widget-type.d.ts.map +1 -1
  90. package/build-types/entity-types/widget.d.ts.map +1 -1
  91. package/build-types/entity-types/wp-template-part.d.ts.map +1 -1
  92. package/build-types/entity-types/wp-template.d.ts.map +1 -1
  93. package/build-types/fetch/__experimental-fetch-url-data.d.ts +3 -0
  94. package/build-types/fetch/__experimental-fetch-url-data.d.ts.map +1 -1
  95. package/build-types/index.d.ts +17 -47
  96. package/build-types/index.d.ts.map +1 -1
  97. package/build-types/locks/actions.d.ts +1 -1
  98. package/build-types/locks/engine.d.ts +1 -1
  99. package/build-types/locks/utils.d.ts +1 -1
  100. package/build-types/locks/utils.d.ts.map +1 -1
  101. package/build-types/private-actions.d.ts +32 -1
  102. package/build-types/private-actions.d.ts.map +1 -1
  103. package/build-types/private-apis.d.ts +9 -0
  104. package/build-types/private-apis.d.ts.map +1 -1
  105. package/build-types/queried-data/actions.d.ts +1 -1
  106. package/build-types/queried-data/actions.d.ts.map +1 -1
  107. package/build-types/queried-data/get-query-parts.d.ts +21 -0
  108. package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
  109. package/build-types/reducer.d.ts +3 -3
  110. package/build-types/reducer.d.ts.map +1 -1
  111. package/build-types/resolvers.d.ts +1 -1
  112. package/build-types/resolvers.d.ts.map +1 -1
  113. package/build-types/utils/crdt.d.ts +8 -0
  114. package/build-types/utils/crdt.d.ts.map +1 -1
  115. package/build-types/utils/get-template-info.d.ts +13 -0
  116. package/build-types/utils/get-template-info.d.ts.map +1 -0
  117. package/build-types/utils/get-template-part-icon.d.ts +9 -0
  118. package/build-types/utils/get-template-part-icon.d.ts.map +1 -0
  119. package/build-types/utils/log-entity-deprecation.d.ts +1 -1
  120. package/build-types/utils/log-entity-deprecation.d.ts.map +1 -1
  121. package/build-types/utils/on-sub-key.d.ts +4 -0
  122. package/build-types/utils/on-sub-key.d.ts.map +1 -0
  123. package/package.json +30 -25
  124. package/src/actions.js +92 -1
  125. package/src/components/entities-saved-states/entity-record-item.js +60 -0
  126. package/src/components/entities-saved-states/entity-type-list.js +130 -0
  127. package/src/components/entities-saved-states/hooks/use-is-dirty.js +101 -0
  128. package/src/components/entities-saved-states/index.js +242 -0
  129. package/src/components/entities-saved-states/style.scss +70 -0
  130. package/src/components/entities-saved-states/test/use-is-dirty.js +88 -0
  131. package/src/entities.js +3 -1
  132. package/src/private-actions.js +127 -0
  133. package/src/private-apis.ts +12 -0
  134. package/src/style.scss +1 -0
  135. package/src/test/actions.js +587 -0
  136. package/src/utils/crdt.ts +1 -1
  137. package/src/utils/get-template-info.js +52 -0
  138. package/src/utils/get-template-part-icon.js +30 -0
  139. package/src/utils/test/get-template-info.js +224 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/core-data",
3
- "version": "7.50.1-next.v.202607070741.0+a51d59513",
3
+ "version": "7.52.0",
4
4
  "description": "Access to and manipulation of core WordPress entities.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -48,41 +48,46 @@
48
48
  "build-module/index.mjs"
49
49
  ],
50
50
  "dependencies": {
51
- "@wordpress/api-fetch": "^7.50.1-next.v.202607070741.0+a51d59513",
52
- "@wordpress/block-editor": "^16.0.1-next.v.202607070741.0+a51d59513",
53
- "@wordpress/blocks": "^15.23.1-next.v.202607070741.0+a51d59513",
54
- "@wordpress/compose": "^8.3.1-next.v.202607070741.0+a51d59513",
55
- "@wordpress/data": "^10.50.1-next.v.202607070741.0+a51d59513",
56
- "@wordpress/deprecated": "^4.50.1-next.v.202607070741.0+a51d59513",
57
- "@wordpress/element": "^8.2.1-next.v.202607070741.0+a51d59513",
58
- "@wordpress/html-entities": "^4.50.1-next.v.202607070741.0+a51d59513",
59
- "@wordpress/i18n": "^6.23.1-next.v.202607070741.0+a51d59513",
60
- "@wordpress/is-shallow-equal": "^5.50.1-next.v.202607070741.0+a51d59513",
61
- "@wordpress/private-apis": "^1.50.1-next.v.202607070741.0+a51d59513",
62
- "@wordpress/rich-text": "^7.50.1-next.v.202607070741.0+a51d59513",
63
- "@wordpress/sync": "^1.50.1-next.v.202607070741.0+a51d59513",
64
- "@wordpress/undo-manager": "^1.50.1-next.v.202607070741.0+a51d59513",
65
- "@wordpress/url": "^4.50.1-next.v.202607070741.0+a51d59513",
66
- "@wordpress/warning": "^3.50.1-next.v.202607070741.0+a51d59513",
51
+ "@wordpress/api-fetch": "^7.52.0",
52
+ "@wordpress/block-editor": "^16.1.0",
53
+ "@wordpress/blocks": "^15.25.0",
54
+ "@wordpress/components": "^38.0.0",
55
+ "@wordpress/compose": "^8.5.0",
56
+ "@wordpress/data": "^10.52.0",
57
+ "@wordpress/deprecated": "^4.52.0",
58
+ "@wordpress/element": "^8.4.0",
59
+ "@wordpress/global-styles-engine": "^1.19.0",
60
+ "@wordpress/html-entities": "^4.52.0",
61
+ "@wordpress/i18n": "^6.25.0",
62
+ "@wordpress/icons": "^15.3.0",
63
+ "@wordpress/is-shallow-equal": "^5.52.0",
64
+ "@wordpress/notices": "^5.52.0",
65
+ "@wordpress/private-apis": "^1.52.0",
66
+ "@wordpress/rich-text": "^7.52.0",
67
+ "@wordpress/sync": "^1.52.0",
68
+ "@wordpress/undo-manager": "^1.52.0",
69
+ "@wordpress/url": "^4.52.0",
70
+ "@wordpress/warning": "^3.52.0",
67
71
  "change-case": "^4.1.2",
72
+ "clsx": "^2.1.1",
68
73
  "equivalent-key-map": "^0.2.2",
69
74
  "fast-deep-equal": "^3.1.3",
70
- "memize": "^2.1.0",
75
+ "memize": "^2.1.1",
71
76
  "uuid": "^14.0.0"
72
77
  },
73
78
  "devDependencies": {
74
- "@jest/globals": "^30.2.0",
79
+ "@jest/globals": "^30.4.1",
75
80
  "@testing-library/dom": "^10.4.1",
76
81
  "@testing-library/react": "^16.3.2",
77
- "@types/jest": "^29.5.14",
82
+ "@types/jest": "^30.0.0",
78
83
  "@types/node": "^20.19.39",
79
- "@wordpress/block-library": "^10.1.1-next.v.202607070741.0+a51d59513",
84
+ "@wordpress/block-library": "^10.3.0",
80
85
  "deep-freeze": "^0.0.1"
81
86
  },
82
87
  "peerDependencies": {
83
- "@types/react": "^18.3.27",
84
- "react": "^18.0.0",
85
- "react-dom": "^18.0.0"
88
+ "@types/react": "^18 || ^19",
89
+ "react": "^18 || ^19",
90
+ "react-dom": "^18 || ^19"
86
91
  },
87
92
  "peerDependenciesMeta": {
88
93
  "@types/react": {
@@ -92,5 +97,5 @@
92
97
  "publishConfig": {
93
98
  "access": "public"
94
99
  },
95
- "gitHead": "f637726e370c8b23695ed9af82adbe171ad235d8"
100
+ "gitHead": "7e8b17a983f9391d3ebc7717af2a1c9ed7efc7ab"
96
101
  }
package/src/actions.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
+ import fastDeepEqual from 'fast-deep-equal/es6/index.js';
4
5
  import { v4 as uuid } from 'uuid';
5
6
 
6
7
  /**
@@ -24,11 +25,71 @@ import {
24
25
  getSyncManager,
25
26
  } from './sync';
26
27
  import logEntityDeprecation from './utils/log-entity-deprecation';
28
+ import {
29
+ getRawValue,
30
+ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
31
+ } from './utils/crdt';
27
32
 
28
33
  function addTitleToAutoDraft( record ) {
29
34
  return record.status === 'auto-draft' ? { ...record, title: '' } : record;
30
35
  }
31
36
 
37
+ // Post meta is applied to the CRDT one subkey at a time, so compare the save
38
+ // response at the same granularity to avoid carrying stale sibling values.
39
+ function getServerMutatedMetaFields( updatedMeta, persistedMeta, syncedMeta ) {
40
+ const baseline = { ...persistedMeta, ...syncedMeta };
41
+
42
+ return Object.fromEntries(
43
+ Object.entries( updatedMeta ?? {} ).filter( ( [ key, value ] ) => {
44
+ if ( key === POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ) {
45
+ // The persisted CRDT snapshot may change on save and is
46
+ // intentionally excluded from CRDT meta synchronization, so it
47
+ // is not a server mutation.
48
+ return false;
49
+ }
50
+
51
+ return ! fastDeepEqual( value, baseline[ key ] );
52
+ } )
53
+ );
54
+ }
55
+
56
+ function getServerMutatedFields(
57
+ updatedRecord,
58
+ persistedRecord,
59
+ syncedChanges
60
+ ) {
61
+ return Object.fromEntries(
62
+ Object.entries( updatedRecord ).flatMap( ( [ key, value ] ) => {
63
+ if ( key === 'meta' ) {
64
+ const serverMutatedMeta = getServerMutatedMetaFields(
65
+ value,
66
+ persistedRecord.meta,
67
+ syncedChanges.meta
68
+ );
69
+
70
+ return Object.keys( serverMutatedMeta ).length
71
+ ? [ [ key, serverMutatedMeta ] ]
72
+ : [];
73
+ }
74
+
75
+ const baseline =
76
+ key in syncedChanges
77
+ ? syncedChanges[ key ]
78
+ : persistedRecord[ key ];
79
+
80
+ // The save response nests raw attributes as `{ raw, rendered }`
81
+ // while the baseline holds raw strings; compare raw values so the
82
+ // shape difference does not read as a server mutation.
83
+ const wasServerMutated = ! fastDeepEqual(
84
+ getRawValue( value ) ?? value,
85
+ getRawValue( baseline ) ?? baseline
86
+ );
87
+
88
+ return wasServerMutated ? [ [ key, value ] ] : [];
89
+ } )
90
+ );
91
+ }
92
+
32
93
  /**
33
94
  * Returns an action object used in signalling that authors have been received.
34
95
  * Ignored from documentation as it's internal to the data store.
@@ -759,6 +820,23 @@ export const saveEntityRecord =
759
820
  );
760
821
  }
761
822
  } else {
823
+ // `saveEntityRecord` can be called directly, bypassing
824
+ // `editEntityRecord`, so make sure its changes enter the
825
+ // CRDT before the persisted document is created below.
826
+ if (
827
+ entityConfig.syncConfig &&
828
+ ! __unstableSkipSyncUpdate &&
829
+ ! isNewRecord &&
830
+ persistedRecord
831
+ ) {
832
+ getSyncManager()?.update(
833
+ `${ kind }/${ name }`,
834
+ recordId,
835
+ record,
836
+ LOCAL_UNDO_IGNORED_ORIGIN
837
+ );
838
+ }
839
+
762
840
  let edits = record;
763
841
  if ( entityConfig.__unstablePrePersist ) {
764
842
  edits = {
@@ -783,12 +861,25 @@ export const saveEntityRecord =
783
861
  edits
784
862
  );
785
863
  if ( entityConfig.syncConfig ) {
864
+ let syncChanges;
865
+ if ( __unstableSkipSyncUpdate ) {
866
+ syncChanges = {};
867
+ } else if ( isNewRecord || ! persistedRecord ) {
868
+ syncChanges = updatedRecord;
869
+ } else {
870
+ syncChanges = getServerMutatedFields(
871
+ updatedRecord,
872
+ persistedRecord,
873
+ record
874
+ );
875
+ }
876
+
786
877
  // Use an untracked origin so that the save
787
878
  // response does not create undo levels.
788
879
  getSyncManager()?.update(
789
880
  `${ kind }/${ name }`,
790
881
  recordId,
791
- __unstableSkipSyncUpdate ? {} : updatedRecord,
882
+ syncChanges,
792
883
  LOCAL_UNDO_IGNORED_ORIGIN,
793
884
  { isSave: true }
794
885
  );
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { CheckboxControl, PanelRow } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { useSelect } from '@wordpress/data';
7
+ import { decodeEntities } from '@wordpress/html-entities';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import { STORE_NAME } from '../../name';
13
+ import { getTemplateInfo } from '../../utils/get-template-info';
14
+
15
+ export default function EntityRecordItem( { record, checked, onChange } ) {
16
+ const { name, kind, title, key } = record;
17
+
18
+ // Handle templates that might use default descriptive titles.
19
+ const { entityRecordTitle } = useSelect(
20
+ ( select ) => {
21
+ if ( 'postType' !== kind || 'wp_template' !== name ) {
22
+ return {
23
+ entityRecordTitle: title,
24
+ };
25
+ }
26
+
27
+ const template = select( STORE_NAME ).getEditedEntityRecord(
28
+ kind,
29
+ name,
30
+ key
31
+ );
32
+
33
+ const { default_template_types: templateTypes = [] } =
34
+ select( STORE_NAME ).getCurrentTheme() ?? {};
35
+
36
+ return {
37
+ entityRecordTitle: getTemplateInfo( {
38
+ template,
39
+ templateTypes,
40
+ } ).title,
41
+ };
42
+ },
43
+ [ name, kind, title, key ]
44
+ );
45
+
46
+ return (
47
+ <>
48
+ <PanelRow>
49
+ <CheckboxControl
50
+ label={
51
+ decodeEntities( entityRecordTitle ) || __( 'Untitled' )
52
+ }
53
+ checked={ checked }
54
+ onChange={ onChange }
55
+ className="entities-saved-states__change-control"
56
+ />
57
+ </PanelRow>
58
+ </>
59
+ );
60
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import { useSelect } from '@wordpress/data';
6
+ import { PanelBody, PanelRow } from '@wordpress/components';
7
+ import { getGlobalStylesChanges } from '@wordpress/global-styles-engine';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import EntityRecordItem from './entity-record-item';
13
+ import { STORE_NAME } from '../../name';
14
+
15
+ function getEntityDescription( entity, count ) {
16
+ switch ( entity ) {
17
+ case 'site':
18
+ return 1 === count
19
+ ? __( 'This change will affect your whole site.' )
20
+ : __( 'These changes will affect your whole site.' );
21
+ case 'wp_template':
22
+ return __(
23
+ 'This change will affect other parts of your site that use this template.'
24
+ );
25
+ case 'page':
26
+ case 'post':
27
+ return __( 'The following has been modified.' );
28
+ }
29
+ }
30
+
31
+ function GlobalStylesDescription( { record } ) {
32
+ const { editedRecord, savedRecord } = useSelect(
33
+ ( select ) => {
34
+ const { getEditedEntityRecord, getEntityRecord } =
35
+ select( STORE_NAME );
36
+ return {
37
+ editedRecord: getEditedEntityRecord(
38
+ record.kind,
39
+ record.name,
40
+ record.key
41
+ ),
42
+ savedRecord: getEntityRecord(
43
+ record.kind,
44
+ record.name,
45
+ record.key
46
+ ),
47
+ };
48
+ },
49
+ [ record.kind, record.name, record.key ]
50
+ );
51
+
52
+ const globalStylesChanges = getGlobalStylesChanges(
53
+ editedRecord,
54
+ savedRecord,
55
+ {
56
+ maxResults: 10,
57
+ }
58
+ );
59
+ return globalStylesChanges.length ? (
60
+ <ul className="entities-saved-states__changes">
61
+ { globalStylesChanges.map( ( change ) => (
62
+ <li key={ change }>{ change }</li>
63
+ ) ) }
64
+ </ul>
65
+ ) : null;
66
+ }
67
+
68
+ function EntityDescription( { record, count } ) {
69
+ if ( 'globalStyles' === record?.name ) {
70
+ return null;
71
+ }
72
+ const description = getEntityDescription( record?.name, count );
73
+ return description ? <PanelRow>{ description }</PanelRow> : null;
74
+ }
75
+
76
+ export default function EntityTypeList( {
77
+ list,
78
+ unselectedEntities,
79
+ setUnselectedEntities,
80
+ } ) {
81
+ const count = list.length;
82
+ const firstRecord = list[ 0 ];
83
+ const entityConfig = useSelect(
84
+ ( select ) =>
85
+ select( STORE_NAME ).getEntityConfig(
86
+ firstRecord.kind,
87
+ firstRecord.name
88
+ ),
89
+ [ firstRecord.kind, firstRecord.name ]
90
+ );
91
+
92
+ let entityLabel = entityConfig.label;
93
+ if ( firstRecord?.name === 'wp_template_part' ) {
94
+ entityLabel =
95
+ 1 === count ? __( 'Template Part' ) : __( 'Template Parts' );
96
+ }
97
+
98
+ return (
99
+ <PanelBody
100
+ title={ entityLabel }
101
+ initialOpen
102
+ className="entities-saved-states__panel-body"
103
+ >
104
+ <EntityDescription record={ firstRecord } count={ count } />
105
+ { list.map( ( record ) => {
106
+ return (
107
+ <EntityRecordItem
108
+ key={ record.key || record.property }
109
+ record={ record }
110
+ checked={
111
+ ! unselectedEntities.some(
112
+ ( elt ) =>
113
+ elt.kind === record.kind &&
114
+ elt.name === record.name &&
115
+ elt.key === record.key &&
116
+ elt.property === record.property
117
+ )
118
+ }
119
+ onChange={ ( value ) =>
120
+ setUnselectedEntities( record, value )
121
+ }
122
+ />
123
+ );
124
+ } ) }
125
+ { 'globalStyles' === firstRecord?.name && (
126
+ <GlobalStylesDescription record={ firstRecord } />
127
+ ) }
128
+ </PanelBody>
129
+ );
130
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect } from '@wordpress/data';
5
+ import { useMemo, useState } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { STORE_NAME } from '../../../name';
11
+
12
+ /**
13
+ * Custom hook that determines if any entities are dirty (edited) and provides a way to manage selected/unselected entities.
14
+ *
15
+ * @return {Object} An object containing the following properties:
16
+ * - dirtyEntityRecords: An array of dirty entity records.
17
+ * - isDirty: A boolean indicating if there are any dirty entity records.
18
+ * - setUnselectedEntities: A function to set the unselected entities.
19
+ * - unselectedEntities: An array of unselected entities.
20
+ */
21
+ export const useIsDirty = () => {
22
+ const { editedEntities, siteEdits, siteEntityConfig } = useSelect(
23
+ ( select ) => {
24
+ const {
25
+ __experimentalGetDirtyEntityRecords,
26
+ getEntityRecordEdits,
27
+ getEntityConfig,
28
+ } = select( STORE_NAME );
29
+
30
+ return {
31
+ editedEntities: __experimentalGetDirtyEntityRecords(),
32
+ siteEdits: getEntityRecordEdits( 'root', 'site' ),
33
+ siteEntityConfig: getEntityConfig( 'root', 'site' ),
34
+ };
35
+ },
36
+ []
37
+ );
38
+
39
+ const dirtyEntityRecords = useMemo( () => {
40
+ // Remove site object and decouple into its edited pieces.
41
+ const editedEntitiesWithoutSite = editedEntities.filter(
42
+ ( record ) => ! ( record.kind === 'root' && record.name === 'site' )
43
+ );
44
+
45
+ const siteEntityLabels = siteEntityConfig?.meta?.labels ?? {};
46
+ const {
47
+ site_logo: siteLogoEdit,
48
+ site_icon: siteIconEdit,
49
+ ...otherSiteEdits
50
+ } = siteEdits ?? {};
51
+ const orderedSiteProperties = [
52
+ siteLogoEdit !== undefined && 'site_logo',
53
+ siteIconEdit !== undefined && 'site_icon',
54
+ ...Object.keys( otherSiteEdits ),
55
+ ].filter( Boolean );
56
+ const editedSiteEntities = orderedSiteProperties.map(
57
+ ( property ) => ( {
58
+ kind: 'root',
59
+ name: 'site',
60
+ title: siteEntityLabels[ property ] || property,
61
+ property,
62
+ } )
63
+ );
64
+
65
+ return [ ...editedEntitiesWithoutSite, ...editedSiteEntities ];
66
+ }, [ editedEntities, siteEdits, siteEntityConfig ] );
67
+
68
+ // Unchecked entities to be ignored by save function.
69
+ const [ unselectedEntities, _setUnselectedEntities ] = useState( [] );
70
+
71
+ const setUnselectedEntities = (
72
+ { kind, name, key, property },
73
+ checked
74
+ ) => {
75
+ if ( checked ) {
76
+ _setUnselectedEntities(
77
+ unselectedEntities.filter(
78
+ ( elt ) =>
79
+ elt.kind !== kind ||
80
+ elt.name !== name ||
81
+ elt.key !== key ||
82
+ elt.property !== property
83
+ )
84
+ );
85
+ } else {
86
+ _setUnselectedEntities( [
87
+ ...unselectedEntities,
88
+ { kind, name, key, property },
89
+ ] );
90
+ }
91
+ };
92
+
93
+ const isDirty = dirtyEntityRecords.length - unselectedEntities.length > 0;
94
+
95
+ return {
96
+ dirtyEntityRecords,
97
+ isDirty,
98
+ setUnselectedEntities,
99
+ unselectedEntities,
100
+ };
101
+ };