@wordpress/editor 14.18.0 → 14.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 (141) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +2 -0
  3. package/build/components/document-outline/index.js +16 -16
  4. package/build/components/document-outline/index.js.map +1 -1
  5. package/build/components/entities-saved-states/entity-record-item.js +2 -1
  6. package/build/components/entities-saved-states/entity-record-item.js.map +1 -1
  7. package/build/components/entities-saved-states/entity-type-list.js +2 -1
  8. package/build/components/entities-saved-states/entity-type-list.js.map +1 -1
  9. package/build/components/entities-saved-states/index.js +66 -42
  10. package/build/components/entities-saved-states/index.js.map +1 -1
  11. package/build/components/post-actions/index.js +19 -28
  12. package/build/components/post-actions/index.js.map +1 -1
  13. package/build/components/post-card-panel/index.js +2 -2
  14. package/build/components/post-card-panel/index.js.map +1 -1
  15. package/build/components/post-featured-image/index.js +4 -2
  16. package/build/components/post-featured-image/index.js.map +1 -1
  17. package/build/components/post-template/block-theme.js +68 -48
  18. package/build/components/post-template/block-theme.js.map +1 -1
  19. package/build/components/post-template/classic-theme.js +30 -18
  20. package/build/components/post-template/classic-theme.js.map +1 -1
  21. package/build/components/post-template/create-new-template-modal.js +3 -1
  22. package/build/components/post-template/create-new-template-modal.js.map +1 -1
  23. package/build/components/post-template/panel.js +3 -11
  24. package/build/components/post-template/panel.js.map +1 -1
  25. package/build/components/preferences-modal/block-visibility.js +2 -1
  26. package/build/components/preferences-modal/block-visibility.js.map +1 -1
  27. package/build/components/preferences-modal/index.js +1 -1
  28. package/build/components/preferences-modal/index.js.map +1 -1
  29. package/build/components/preview-dropdown/index.js +6 -3
  30. package/build/components/preview-dropdown/index.js.map +1 -1
  31. package/build/components/provider/index.js +19 -18
  32. package/build/components/provider/index.js.map +1 -1
  33. package/build/components/sidebar/header.js +1 -1
  34. package/build/components/sidebar/header.js.map +1 -1
  35. package/build/components/sidebar/index.js +1 -1
  36. package/build/components/sidebar/index.js.map +1 -1
  37. package/build/components/start-page-options/index.js +148 -23
  38. package/build/components/start-page-options/index.js.map +1 -1
  39. package/build/components/visual-editor/edit-template-blocks-notification.js +1 -1
  40. package/build/components/visual-editor/edit-template-blocks-notification.js.map +1 -1
  41. package/build/store/private-actions.js +29 -2
  42. package/build/store/private-actions.js.map +1 -1
  43. package/build/store/private-selectors.js +44 -0
  44. package/build/store/private-selectors.js.map +1 -1
  45. package/build/utils/media-upload/index.js +5 -2
  46. package/build/utils/media-upload/index.js.map +1 -1
  47. package/build-module/components/document-outline/index.js +17 -17
  48. package/build-module/components/document-outline/index.js.map +1 -1
  49. package/build-module/components/entities-saved-states/entity-record-item.js +2 -1
  50. package/build-module/components/entities-saved-states/entity-record-item.js.map +1 -1
  51. package/build-module/components/entities-saved-states/entity-type-list.js +2 -1
  52. package/build-module/components/entities-saved-states/entity-type-list.js.map +1 -1
  53. package/build-module/components/entities-saved-states/index.js +67 -43
  54. package/build-module/components/entities-saved-states/index.js.map +1 -1
  55. package/build-module/components/post-actions/index.js +19 -28
  56. package/build-module/components/post-actions/index.js.map +1 -1
  57. package/build-module/components/post-card-panel/index.js +2 -2
  58. package/build-module/components/post-card-panel/index.js.map +1 -1
  59. package/build-module/components/post-featured-image/index.js +4 -2
  60. package/build-module/components/post-featured-image/index.js.map +1 -1
  61. package/build-module/components/post-template/block-theme.js +68 -48
  62. package/build-module/components/post-template/block-theme.js.map +1 -1
  63. package/build-module/components/post-template/classic-theme.js +30 -18
  64. package/build-module/components/post-template/classic-theme.js.map +1 -1
  65. package/build-module/components/post-template/create-new-template-modal.js +3 -1
  66. package/build-module/components/post-template/create-new-template-modal.js.map +1 -1
  67. package/build-module/components/post-template/panel.js +3 -11
  68. package/build-module/components/post-template/panel.js.map +1 -1
  69. package/build-module/components/preferences-modal/block-visibility.js +2 -1
  70. package/build-module/components/preferences-modal/block-visibility.js.map +1 -1
  71. package/build-module/components/preferences-modal/index.js +1 -1
  72. package/build-module/components/preferences-modal/index.js.map +1 -1
  73. package/build-module/components/preview-dropdown/index.js +6 -3
  74. package/build-module/components/preview-dropdown/index.js.map +1 -1
  75. package/build-module/components/provider/index.js +19 -18
  76. package/build-module/components/provider/index.js.map +1 -1
  77. package/build-module/components/sidebar/header.js +1 -1
  78. package/build-module/components/sidebar/header.js.map +1 -1
  79. package/build-module/components/sidebar/index.js +1 -1
  80. package/build-module/components/sidebar/index.js.map +1 -1
  81. package/build-module/components/start-page-options/index.js +148 -24
  82. package/build-module/components/start-page-options/index.js.map +1 -1
  83. package/build-module/components/visual-editor/edit-template-blocks-notification.js +1 -1
  84. package/build-module/components/visual-editor/edit-template-blocks-notification.js.map +1 -1
  85. package/build-module/store/private-actions.js +25 -0
  86. package/build-module/store/private-actions.js.map +1 -1
  87. package/build-module/store/private-selectors.js +43 -0
  88. package/build-module/store/private-selectors.js.map +1 -1
  89. package/build-module/utils/media-upload/index.js +5 -2
  90. package/build-module/utils/media-upload/index.js.map +1 -1
  91. package/build-style/style-rtl.css +49 -8
  92. package/build-style/style.css +49 -8
  93. package/build-types/components/document-outline/index.d.ts.map +1 -1
  94. package/build-types/components/entities-saved-states/entity-record-item.d.ts.map +1 -1
  95. package/build-types/components/entities-saved-states/entity-type-list.d.ts.map +1 -1
  96. package/build-types/components/entities-saved-states/index.d.ts +6 -2
  97. package/build-types/components/entities-saved-states/index.d.ts.map +1 -1
  98. package/build-types/components/post-actions/index.d.ts.map +1 -1
  99. package/build-types/components/post-card-panel/index.d.ts.map +1 -1
  100. package/build-types/components/post-template/block-theme.d.ts.map +1 -1
  101. package/build-types/components/post-template/classic-theme.d.ts.map +1 -1
  102. package/build-types/components/post-template/create-new-template-modal.d.ts.map +1 -1
  103. package/build-types/components/post-template/panel.d.ts.map +1 -1
  104. package/build-types/components/preferences-modal/block-visibility.d.ts.map +1 -1
  105. package/build-types/components/preview-dropdown/index.d.ts.map +1 -1
  106. package/build-types/components/provider/index.d.ts.map +1 -1
  107. package/build-types/components/start-page-options/index.d.ts +2 -1
  108. package/build-types/components/start-page-options/index.d.ts.map +1 -1
  109. package/build-types/store/private-actions.d.ts +4 -0
  110. package/build-types/store/private-actions.d.ts.map +1 -1
  111. package/build-types/store/private-selectors.d.ts +13 -0
  112. package/build-types/store/private-selectors.d.ts.map +1 -1
  113. package/build-types/utils/media-upload/index.d.ts +3 -1
  114. package/build-types/utils/media-upload/index.d.ts.map +1 -1
  115. package/package.json +37 -37
  116. package/src/components/document-outline/index.js +22 -16
  117. package/src/components/entities-saved-states/entity-record-item.js +1 -0
  118. package/src/components/entities-saved-states/entity-type-list.js +6 -2
  119. package/src/components/entities-saved-states/index.js +98 -58
  120. package/src/components/entities-saved-states/style.scss +40 -5
  121. package/src/components/post-actions/index.js +19 -37
  122. package/src/components/post-card-panel/index.js +7 -5
  123. package/src/components/post-featured-image/index.js +2 -0
  124. package/src/components/post-template/block-theme.js +76 -56
  125. package/src/components/post-template/classic-theme.js +31 -15
  126. package/src/components/post-template/create-new-template-modal.js +1 -0
  127. package/src/components/post-template/panel.js +2 -12
  128. package/src/components/post-text-editor/style.scss +0 -2
  129. package/src/components/preferences-modal/block-visibility.js +2 -1
  130. package/src/components/preferences-modal/index.js +1 -1
  131. package/src/components/preview-dropdown/index.js +8 -6
  132. package/src/components/provider/index.js +21 -30
  133. package/src/components/sidebar/header.js +1 -1
  134. package/src/components/sidebar/index.js +1 -1
  135. package/src/components/start-page-options/index.js +149 -23
  136. package/src/components/start-page-options/style.scss +27 -0
  137. package/src/components/visual-editor/edit-template-blocks-notification.js +1 -1
  138. package/src/store/private-actions.js +33 -0
  139. package/src/store/private-selectors.js +57 -0
  140. package/src/utils/media-upload/index.js +3 -0
  141. package/tsconfig.tsbuildinfo +1 -1
@@ -1,3 +1,8 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
@@ -32,15 +37,21 @@ function identity( values ) {
32
37
  * @param {Object} props The component props.
33
38
  * @param {Function} props.close The function to close the dialog.
34
39
  * @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior.
40
+ * @param {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
35
41
  *
36
42
  * @return {React.ReactNode} The rendered component.
37
43
  */
38
- export default function EntitiesSavedStates( { close, renderDialog } ) {
44
+ export default function EntitiesSavedStates( {
45
+ close,
46
+ renderDialog,
47
+ variant,
48
+ } ) {
39
49
  const isDirtyProps = useIsDirty();
40
50
  return (
41
51
  <EntitiesSavedStatesExtensible
42
52
  close={ close }
43
53
  renderDialog={ renderDialog }
54
+ variant={ variant }
44
55
  { ...isDirtyProps }
45
56
  />
46
57
  );
@@ -60,6 +71,7 @@ export default function EntitiesSavedStates( { close, renderDialog } ) {
60
71
  * @param {boolean} props.isDirty Flag indicating if there are dirty entities.
61
72
  * @param {Function} props.setUnselectedEntities Function to set unselected entities.
62
73
  * @param {Array} props.unselectedEntities Array of unselected entities.
74
+ * @param {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
63
75
  *
64
76
  * @return {React.ReactNode} The rendered component.
65
77
  */
@@ -74,6 +86,7 @@ export function EntitiesSavedStatesExtensible( {
74
86
  isDirty,
75
87
  setUnselectedEntities,
76
88
  unselectedEntities,
89
+ variant = 'default',
77
90
  } ) {
78
91
  const saveButtonRef = useRef();
79
92
  const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
@@ -109,83 +122,100 @@ export function EntitiesSavedStatesExtensible( {
109
122
  const [ saveDialogRef, saveDialogProps ] = useDialog( {
110
123
  onClose: () => dismissPanel(),
111
124
  } );
112
- const dialogLabel = useInstanceId( EntitiesSavedStatesExtensible, 'label' );
113
- const dialogDescription = useInstanceId(
125
+ const dialogLabelId = useInstanceId(
126
+ EntitiesSavedStatesExtensible,
127
+ 'entities-saved-states__panel-label'
128
+ );
129
+ const dialogDescriptionId = useInstanceId(
114
130
  EntitiesSavedStatesExtensible,
115
- 'description'
131
+ 'entities-saved-states__panel-description'
116
132
  );
117
133
 
118
134
  const selectItemsToSaveDescription = !! dirtyEntityRecords.length
119
135
  ? __( 'Select the items you want to save.' )
120
136
  : undefined;
121
137
 
138
+ const isInline = variant === 'inline';
139
+
140
+ const actionButtons = (
141
+ <>
142
+ <FlexItem
143
+ isBlock={ isInline ? false : true }
144
+ as={ Button }
145
+ variant={ isInline ? 'tertiary' : 'secondary' }
146
+ size={ isInline ? undefined : 'compact' }
147
+ onClick={ dismissPanel }
148
+ >
149
+ { __( 'Cancel' ) }
150
+ </FlexItem>
151
+ <FlexItem
152
+ isBlock={ isInline ? false : true }
153
+ as={ Button }
154
+ ref={ saveButtonRef }
155
+ variant="primary"
156
+ size={ isInline ? undefined : 'compact' }
157
+ disabled={ ! saveEnabled }
158
+ accessibleWhenDisabled
159
+ onClick={ () =>
160
+ saveDirtyEntities( {
161
+ onSave,
162
+ dirtyEntityRecords,
163
+ entitiesToSkip: unselectedEntities,
164
+ close,
165
+ } )
166
+ }
167
+ className="editor-entities-saved-states__save-button"
168
+ >
169
+ { saveLabel }
170
+ </FlexItem>
171
+ </>
172
+ );
173
+
122
174
  return (
123
175
  <div
124
176
  ref={ renderDialog ? saveDialogRef : undefined }
125
177
  { ...( renderDialog && saveDialogProps ) }
126
- className="entities-saved-states__panel"
178
+ className={ clsx( 'entities-saved-states__panel', {
179
+ 'is-inline': isInline,
180
+ } ) }
127
181
  role={ renderDialog ? 'dialog' : undefined }
128
- aria-labelledby={ renderDialog ? dialogLabel : undefined }
129
- aria-describedby={ renderDialog ? dialogDescription : undefined }
182
+ aria-labelledby={ renderDialog ? dialogLabelId : undefined }
183
+ aria-describedby={ renderDialog ? dialogDescriptionId : undefined }
130
184
  >
131
- <Flex className="entities-saved-states__panel-header" gap={ 2 }>
132
- <FlexItem
133
- isBlock
134
- as={ Button }
135
- variant="secondary"
136
- size="compact"
137
- onClick={ dismissPanel }
138
- >
139
- { __( 'Cancel' ) }
140
- </FlexItem>
141
- <FlexItem
142
- isBlock
143
- as={ Button }
144
- ref={ saveButtonRef }
145
- variant="primary"
146
- size="compact"
147
- disabled={ ! saveEnabled }
148
- accessibleWhenDisabled
149
- onClick={ () =>
150
- saveDirtyEntities( {
151
- onSave,
152
- dirtyEntityRecords,
153
- entitiesToSkip: unselectedEntities,
154
- close,
155
- } )
156
- }
157
- className="editor-entities-saved-states__save-button"
158
- >
159
- { saveLabel }
160
- </FlexItem>
161
- </Flex>
185
+ { ! isInline && (
186
+ <Flex className="entities-saved-states__panel-header" gap={ 2 }>
187
+ { actionButtons }
188
+ </Flex>
189
+ ) }
162
190
 
163
191
  <div className="entities-saved-states__text-prompt">
164
- <div
165
- className="entities-saved-states__text-prompt--header-wrapper"
166
- id={ renderDialog ? dialogLabel : undefined }
167
- >
168
- <strong className="entities-saved-states__text-prompt--header">
192
+ <div className="entities-saved-states__text-prompt--header-wrapper">
193
+ <strong
194
+ id={ renderDialog ? dialogLabelId : undefined }
195
+ className="entities-saved-states__text-prompt--header"
196
+ >
169
197
  { __( 'Are you ready to save?' ) }
170
198
  </strong>
171
- { additionalPrompt }
172
199
  </div>
173
- <p id={ renderDialog ? dialogDescription : undefined }>
174
- { isDirty
175
- ? createInterpolateElement(
176
- sprintf(
177
- /* translators: %d: number of site changes waiting to be saved. */
178
- _n(
179
- 'There is <strong>%d site change</strong> waiting to be saved.',
180
- 'There are <strong>%d site changes</strong> waiting to be saved.',
200
+ <div id={ renderDialog ? dialogDescriptionId : undefined }>
201
+ { additionalPrompt }
202
+ <p className="entities-saved-states__text-prompt--changes-count">
203
+ { isDirty
204
+ ? createInterpolateElement(
205
+ sprintf(
206
+ /* translators: %d: number of site changes waiting to be saved. */
207
+ _n(
208
+ 'There is <strong>%d site change</strong> waiting to be saved.',
209
+ 'There are <strong>%d site changes</strong> waiting to be saved.',
210
+ dirtyEntityRecords.length
211
+ ),
181
212
  dirtyEntityRecords.length
182
213
  ),
183
- dirtyEntityRecords.length
184
- ),
185
- { strong: <strong /> }
186
- )
187
- : selectItemsToSaveDescription }
188
- </p>
214
+ { strong: <strong /> }
215
+ )
216
+ : selectItemsToSaveDescription }
217
+ </p>
218
+ </div>
189
219
  </div>
190
220
 
191
221
  { sortedPartitionedSavables.map( ( list ) => {
@@ -198,6 +228,16 @@ export function EntitiesSavedStatesExtensible( {
198
228
  />
199
229
  );
200
230
  } ) }
231
+
232
+ { isInline && (
233
+ <Flex
234
+ direction="row"
235
+ justify="flex-end"
236
+ className="entities-saved-states__panel-footer"
237
+ >
238
+ { actionButtons }
239
+ </Flex>
240
+ ) }
201
241
  </div>
202
242
  );
203
243
  }
@@ -16,14 +16,49 @@
16
16
  }
17
17
  }
18
18
 
19
- .entities-saved-states__description-heading {
20
- font-size: $default-font-size;
19
+ .entities-saved-states__panel.is-inline {
20
+ .entities-saved-states__text-prompt {
21
+ padding: 0;
22
+ }
23
+
24
+ .entities-saved-states__panel-body {
25
+ padding-left: 0;
26
+ padding-right: 0;
27
+ border: 0;
28
+
29
+ > h2 {
30
+ margin-left: -1 * $grid-unit-20;
31
+ margin-right: -1 * $grid-unit-20;
32
+ margin-bottom: 0;
33
+
34
+ button {
35
+ font-size: $font-size-x-small;
36
+ text-transform: uppercase;
37
+ }
38
+ }
39
+ }
40
+
41
+ .entities-saved-states__text-prompt--header-wrapper {
42
+ display: none;
43
+ }
44
+
45
+ .entities-saved-states__text-prompt--changes-count {
46
+ margin-top: 0;
47
+ margin-bottom: $grid-unit-10;
48
+ }
49
+
50
+ .entities-saved-states__panel-footer {
51
+ margin-top: $grid-unit-20;
52
+ }
53
+ }
54
+
55
+ .entities-saved-states__change-control {
56
+ flex: 1;
21
57
  }
22
58
 
23
59
  .entities-saved-states__changes {
24
- color: $gray-700;
25
- font-size: $helptext-font-size;
26
- margin: $grid-unit-10 $grid-unit-20 0 $grid-unit-20;
60
+ font-size: $default-font-size;
61
+ margin: $grid-unit-05 $grid-unit-20 0 $grid-unit-30;
27
62
  list-style: disc;
28
63
 
29
64
  li {
@@ -20,57 +20,39 @@ import { usePostActions } from './actions';
20
20
 
21
21
  const { Menu, kebabCase } = unlock( componentsPrivateApis );
22
22
 
23
- function useEditedEntityRecordsWithPermissions( postType, postIds ) {
24
- const { items, permissions } = useSelect(
23
+ export default function PostActions( { postType, postId, onActionPerformed } ) {
24
+ const [ activeModalAction, setActiveModalAction ] = useState( null );
25
+
26
+ const { item, permissions } = useSelect(
25
27
  ( select ) => {
26
28
  const { getEditedEntityRecord, getEntityRecordPermissions } =
27
29
  unlock( select( coreStore ) );
28
30
  return {
29
- items: postIds.map( ( postId ) =>
30
- getEditedEntityRecord( 'postType', postType, postId )
31
- ),
32
- permissions: postIds.map( ( postId ) =>
33
- getEntityRecordPermissions( 'postType', postType, postId )
31
+ item: getEditedEntityRecord( 'postType', postType, postId ),
32
+ permissions: getEntityRecordPermissions(
33
+ 'postType',
34
+ postType,
35
+ postId
34
36
  ),
35
37
  };
36
38
  },
37
- [ postIds, postType ]
39
+ [ postId, postType ]
38
40
  );
39
-
40
- return useMemo( () => {
41
- return items.map( ( item, index ) => ( {
41
+ const itemWithPermissions = useMemo( () => {
42
+ return {
42
43
  ...item,
43
- permissions: permissions[ index ],
44
- } ) );
45
- }, [ items, permissions ] );
46
- }
47
-
48
- export default function PostActions( { postType, postId, onActionPerformed } ) {
49
- const [ activeModalAction, setActiveModalAction ] = useState( null );
50
- const _postIds = useMemo( () => {
51
- if ( Array.isArray( postId ) ) {
52
- return postId;
53
- }
54
- return postId ? [ postId ] : [];
55
- }, [ postId ] );
56
-
57
- const itemsWithPermissions = useEditedEntityRecordsWithPermissions(
58
- postType,
59
- _postIds
60
- );
44
+ permissions,
45
+ };
46
+ }, [ item, permissions ] );
61
47
  const allActions = usePostActions( { postType, onActionPerformed } );
62
48
 
63
49
  const actions = useMemo( () => {
64
50
  return allActions.filter( ( action ) => {
65
51
  return (
66
- ( ! action.isEligible ||
67
- itemsWithPermissions.some( ( itemWithPermissions ) =>
68
- action.isEligible( itemWithPermissions )
69
- ) ) &&
70
- ( itemsWithPermissions.length < 2 || action.supportsBulk )
52
+ ! action.isEligible || action.isEligible( itemWithPermissions )
71
53
  );
72
54
  } );
73
- }, [ allActions, itemsWithPermissions ] );
55
+ }, [ allActions, itemWithPermissions ] );
74
56
 
75
57
  return (
76
58
  <>
@@ -90,7 +72,7 @@ export default function PostActions( { postType, postId, onActionPerformed } ) {
90
72
  <Menu.Popover>
91
73
  <ActionsDropdownMenuGroup
92
74
  actions={ actions }
93
- items={ itemsWithPermissions }
75
+ items={ [ itemWithPermissions ] }
94
76
  setActiveModalAction={ setActiveModalAction }
95
77
  />
96
78
  </Menu.Popover>
@@ -98,7 +80,7 @@ export default function PostActions( { postType, postId, onActionPerformed } ) {
98
80
  { !! activeModalAction && (
99
81
  <ActionModal
100
82
  action={ activeModalAction }
101
- items={ itemsWithPermissions }
83
+ items={ [ itemWithPermissions ] }
102
84
  closeModal={ () => setActiveModalAction( null ) }
103
85
  />
104
86
  ) }
@@ -118,11 +118,13 @@ export default function PostCardPanel( {
118
118
  <Badge>{ pageTypeBadge }</Badge>
119
119
  ) }
120
120
  </Text>
121
- <PostActions
122
- postType={ postType }
123
- postId={ postId }
124
- onActionPerformed={ onActionPerformed }
125
- />
121
+ { postIds.length === 1 && (
122
+ <PostActions
123
+ postType={ postType }
124
+ postId={ postIds[ 0 ] }
125
+ onActionPerformed={ onActionPerformed }
126
+ />
127
+ ) }
126
128
  </HStack>
127
129
  { postIds.length > 1 && (
128
130
  <Text className="editor-post-card-panel__description">
@@ -125,6 +125,7 @@ function PostFeaturedImage( {
125
125
  noticeOperations.removeAllNotices();
126
126
  noticeOperations.createErrorNotice( message );
127
127
  },
128
+ multiple: false,
128
129
  } );
129
130
  }
130
131
 
@@ -345,6 +346,7 @@ const applyWithDispatch = withDispatch(
345
346
  noticeOperations.removeAllNotices();
346
347
  noticeOperations.createErrorNotice( message );
347
348
  },
349
+ multiple: false,
348
350
  } );
349
351
  },
350
352
  onRemoveImage() {
@@ -4,12 +4,18 @@
4
4
  import { useSelect, useDispatch } from '@wordpress/data';
5
5
  import { decodeEntities } from '@wordpress/html-entities';
6
6
  import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
7
+ import { useState, useMemo } from '@wordpress/element';
7
8
  import { __ } from '@wordpress/i18n';
8
9
  import { useEntityRecord, store as coreStore } from '@wordpress/core-data';
9
10
  import { check } from '@wordpress/icons';
10
11
  import { store as noticesStore } from '@wordpress/notices';
11
12
  import { store as preferencesStore } from '@wordpress/preferences';
12
13
 
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import PostPanelRow from '../post-panel-row';
18
+
13
19
  /**
14
20
  * Internal dependencies
15
21
  */
@@ -19,11 +25,6 @@ import ResetDefaultTemplate from './reset-default-template';
19
25
  import { unlock } from '../../lock-unlock';
20
26
  import CreateNewTemplate from './create-new-template';
21
27
 
22
- const POPOVER_PROPS = {
23
- className: 'editor-post-template__dropdown',
24
- placement: 'bottom-start',
25
- };
26
-
27
28
  export default function BlockThemeControl( { id } ) {
28
29
  const {
29
30
  isTemplateHidden,
@@ -52,7 +53,9 @@ export default function BlockThemeControl( { id } ) {
52
53
  id
53
54
  );
54
55
  const { createSuccessNotice } = useDispatch( noticesStore );
55
- const { setRenderingMode } = useDispatch( editorStore );
56
+ const { setRenderingMode, setDefaultRenderingMode } = unlock(
57
+ useDispatch( editorStore )
58
+ );
56
59
 
57
60
  const canCreateTemplate = useSelect(
58
61
  ( select ) =>
@@ -63,6 +66,21 @@ export default function BlockThemeControl( { id } ) {
63
66
  []
64
67
  );
65
68
 
69
+ const [ popoverAnchor, setPopoverAnchor ] = useState( null );
70
+ // Memoize popoverProps to avoid returning a new object every time.
71
+ const popoverProps = useMemo(
72
+ () => ( {
73
+ // Anchor the popover to the middle of the entire row so that it doesn't
74
+ // move around when the label changes.
75
+ anchor: popoverAnchor,
76
+ className: 'editor-post-template__dropdown',
77
+ placement: 'left-start',
78
+ offset: 36,
79
+ shift: true,
80
+ } ),
81
+ [ popoverAnchor ]
82
+ );
83
+
66
84
  if ( ! hasResolved ) {
67
85
  return null;
68
86
  }
@@ -90,60 +108,62 @@ export default function BlockThemeControl( { id } ) {
90
108
  }
91
109
  };
92
110
  return (
93
- <DropdownMenu
94
- popoverProps={ POPOVER_PROPS }
95
- focusOnMount
96
- toggleProps={ {
97
- size: 'compact',
98
- variant: 'tertiary',
99
- tooltipPosition: 'middle left',
100
- } }
101
- label={ __( 'Template options' ) }
102
- text={ decodeEntities( template.title ) }
103
- icon={ null }
104
- >
105
- { ( { onClose } ) => (
106
- <>
107
- <MenuGroup>
108
- { canCreateTemplate && (
111
+ <PostPanelRow label={ __( 'Template' ) } ref={ setPopoverAnchor }>
112
+ <DropdownMenu
113
+ popoverProps={ popoverProps }
114
+ focusOnMount
115
+ toggleProps={ {
116
+ size: 'compact',
117
+ variant: 'tertiary',
118
+ tooltipPosition: 'middle left',
119
+ } }
120
+ label={ __( 'Template options' ) }
121
+ text={ decodeEntities( template.title ) }
122
+ icon={ null }
123
+ >
124
+ { ( { onClose } ) => (
125
+ <>
126
+ <MenuGroup>
127
+ { canCreateTemplate && (
128
+ <MenuItem
129
+ onClick={ () => {
130
+ onNavigateToEntityRecord( {
131
+ postId: template.id,
132
+ postType: 'wp_template',
133
+ } );
134
+ onClose();
135
+ mayShowTemplateEditNotice();
136
+ } }
137
+ >
138
+ { __( 'Edit template' ) }
139
+ </MenuItem>
140
+ ) }
141
+
142
+ <SwapTemplateButton onClick={ onClose } />
143
+ <ResetDefaultTemplate onClick={ onClose } />
144
+ { canCreateTemplate && (
145
+ <CreateNewTemplate onClick={ onClose } />
146
+ ) }
147
+ </MenuGroup>
148
+ <MenuGroup>
109
149
  <MenuItem
150
+ icon={ ! isTemplateHidden ? check : undefined }
151
+ isSelected={ ! isTemplateHidden }
152
+ role="menuitemcheckbox"
110
153
  onClick={ () => {
111
- onNavigateToEntityRecord( {
112
- postId: template.id,
113
- postType: 'wp_template',
114
- } );
115
- onClose();
116
- mayShowTemplateEditNotice();
154
+ const newRenderingMode = isTemplateHidden
155
+ ? 'template-locked'
156
+ : 'post-only';
157
+ setRenderingMode( newRenderingMode );
158
+ setDefaultRenderingMode( newRenderingMode );
117
159
  } }
118
160
  >
119
- { __( 'Edit template' ) }
161
+ { __( 'Show template' ) }
120
162
  </MenuItem>
121
- ) }
122
-
123
- <SwapTemplateButton onClick={ onClose } />
124
- <ResetDefaultTemplate onClick={ onClose } />
125
- { canCreateTemplate && (
126
- <CreateNewTemplate onClick={ onClose } />
127
- ) }
128
- </MenuGroup>
129
- <MenuGroup>
130
- <MenuItem
131
- icon={ ! isTemplateHidden ? check : undefined }
132
- isSelected={ ! isTemplateHidden }
133
- role="menuitemcheckbox"
134
- onClick={ () => {
135
- setRenderingMode(
136
- isTemplateHidden
137
- ? 'template-locked'
138
- : 'post-only'
139
- );
140
- } }
141
- >
142
- { __( 'Show template' ) }
143
- </MenuItem>
144
- </MenuGroup>
145
- </>
146
- ) }
147
- </DropdownMenu>
163
+ </MenuGroup>
164
+ </>
165
+ ) }
166
+ </DropdownMenu>
167
+ </PostPanelRow>
148
168
  );
149
169
  }
@@ -16,11 +16,7 @@ import { store as noticesStore } from '@wordpress/notices';
16
16
  import { store as editorStore } from '../../store';
17
17
  import CreateNewTemplateModal from './create-new-template-modal';
18
18
  import { useAllowSwitchingTemplates } from './hooks';
19
-
20
- const POPOVER_PROPS = {
21
- className: 'editor-post-template__dropdown',
22
- placement: 'bottom-start',
23
- };
19
+ import PostPanelRow from '../post-panel-row';
24
20
 
25
21
  function PostTemplateToggle( { isOpen, onClick } ) {
26
22
  const templateTitle = useSelect( ( select ) => {
@@ -216,17 +212,37 @@ function PostTemplateDropdownContent( { onClose } ) {
216
212
  }
217
213
 
218
214
  function ClassicThemeControl() {
215
+ const [ popoverAnchor, setPopoverAnchor ] = useState( null );
216
+ // Memoize popoverProps to avoid returning a new object every time.
217
+ const popoverProps = useMemo(
218
+ () => ( {
219
+ // Anchor the popover to the middle of the entire row so that it doesn't
220
+ // move around when the label changes.
221
+ anchor: popoverAnchor,
222
+ className: 'editor-post-template__dropdown',
223
+ placement: 'left-start',
224
+ offset: 36,
225
+ shift: true,
226
+ } ),
227
+ [ popoverAnchor ]
228
+ );
229
+
219
230
  return (
220
- <Dropdown
221
- popoverProps={ POPOVER_PROPS }
222
- focusOnMount
223
- renderToggle={ ( { isOpen, onToggle } ) => (
224
- <PostTemplateToggle isOpen={ isOpen } onClick={ onToggle } />
225
- ) }
226
- renderContent={ ( { onClose } ) => (
227
- <PostTemplateDropdownContent onClose={ onClose } />
228
- ) }
229
- />
231
+ <PostPanelRow label={ __( 'Template' ) } ref={ setPopoverAnchor }>
232
+ <Dropdown
233
+ popoverProps={ popoverProps }
234
+ focusOnMount
235
+ renderToggle={ ( { isOpen, onToggle } ) => (
236
+ <PostTemplateToggle
237
+ isOpen={ isOpen }
238
+ onClick={ onToggle }
239
+ />
240
+ ) }
241
+ renderContent={ ( { onClose } ) => (
242
+ <PostTemplateDropdownContent onClose={ onClose } />
243
+ ) }
244
+ />
245
+ </PostPanelRow>
230
246
  );
231
247
  }
232
248
 
@@ -126,6 +126,7 @@ export default function CreateNewTemplateModal( { onClose } ) {
126
126
  placeholder={ DEFAULT_TITLE }
127
127
  disabled={ isBusy }
128
128
  help={ __(
129
+ // eslint-disable-next-line no-restricted-syntax -- 'sidebar' is a common web design term for layouts
129
130
  'Describe the template, e.g. "Post with sidebar". A custom template can be manually applied to any post or page.'
130
131
  ) }
131
132
  />
@@ -2,7 +2,6 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { useSelect } from '@wordpress/data';
5
- import { __ } from '@wordpress/i18n';
6
5
  import { store as coreStore } from '@wordpress/core-data';
7
6
 
8
7
  /**
@@ -11,7 +10,6 @@ import { store as coreStore } from '@wordpress/core-data';
11
10
  import { store as editorStore } from '../../store';
12
11
  import ClassicThemeControl from './classic-theme';
13
12
  import BlockThemeControl from './block-theme';
14
- import PostPanelRow from '../post-panel-row';
15
13
 
16
14
  /**
17
15
  * Displays the template controls based on the current editor settings and user permissions.
@@ -65,19 +63,11 @@ export default function PostTemplatePanel() {
65
63
  }, [] );
66
64
 
67
65
  if ( ( ! isBlockTheme || ! canViewTemplates ) && isVisible ) {
68
- return (
69
- <PostPanelRow label={ __( 'Template' ) }>
70
- <ClassicThemeControl />
71
- </PostPanelRow>
72
- );
66
+ return <ClassicThemeControl />;
73
67
  }
74
68
 
75
69
  if ( isBlockTheme && !! templateId ) {
76
- return (
77
- <PostPanelRow label={ __( 'Template' ) }>
78
- <BlockThemeControl id={ templateId } />
79
- </PostPanelRow>
80
- );
70
+ return <BlockThemeControl id={ templateId } />;
81
71
  }
82
72
  return null;
83
73
  }