@wordpress/block-editor 14.7.0 → 14.8.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 (204) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-controls/slot.js +6 -3
  3. package/build/components/block-controls/slot.js.map +1 -1
  4. package/build/components/block-inspector/index.js +1 -2
  5. package/build/components/block-inspector/index.js.map +1 -1
  6. package/build/components/block-lock/modal.js +1 -1
  7. package/build/components/block-lock/modal.js.map +1 -1
  8. package/build/components/block-manager/category.js +79 -0
  9. package/build/components/block-manager/category.js.map +1 -0
  10. package/build/components/block-manager/checklist.js +40 -0
  11. package/build/components/block-manager/checklist.js.map +1 -0
  12. package/build/components/block-manager/index.js +108 -0
  13. package/build/components/block-manager/index.js.map +1 -0
  14. package/build/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  15. package/build/components/block-settings-menu/index.js.map +1 -1
  16. package/build/components/block-toolbar/index.js +16 -4
  17. package/build/components/block-toolbar/index.js.map +1 -1
  18. package/build/components/block-toolbar/switch-section-style.js +105 -0
  19. package/build/components/block-toolbar/switch-section-style.js.map +1 -0
  20. package/build/components/collab/block-comment-icon-slot.js +2 -6
  21. package/build/components/collab/block-comment-icon-slot.js.map +1 -1
  22. package/build/components/collab/block-comment-icon-toolbar-slot.js +2 -6
  23. package/build/components/collab/block-comment-icon-toolbar-slot.js.map +1 -1
  24. package/build/components/color-palette/with-color-context.js +4 -2
  25. package/build/components/color-palette/with-color-context.js.map +1 -1
  26. package/build/components/font-family/index.js +14 -13
  27. package/build/components/font-family/index.js.map +1 -1
  28. package/build/components/global-styles/use-global-styles-output.js +1 -1
  29. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  30. package/build/components/iframe/index.js +13 -100
  31. package/build/components/iframe/index.js.map +1 -1
  32. package/build/components/iframe/use-scale-canvas.js +377 -0
  33. package/build/components/iframe/use-scale-canvas.js.map +1 -0
  34. package/build/components/image-editor/use-save-image.js +22 -3
  35. package/build/components/image-editor/use-save-image.js.map +1 -1
  36. package/build/components/inserter/block-patterns-tab/pattern-category-previews.js +1 -5
  37. package/build/components/inserter/block-patterns-tab/pattern-category-previews.js.map +1 -1
  38. package/build/components/inserter/category-tabs/index.js +7 -8
  39. package/build/components/inserter/category-tabs/index.js.map +1 -1
  40. package/build/components/inserter-draggable-blocks/index.js +2 -1
  41. package/build/components/inserter-draggable-blocks/index.js.map +1 -1
  42. package/build/components/inspector-controls/slot.js +7 -4
  43. package/build/components/inspector-controls/slot.js.map +1 -1
  44. package/build/components/inspector-controls-tabs/position-controls-panel.js +1 -1
  45. package/build/components/inspector-controls-tabs/position-controls-panel.js.map +1 -1
  46. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js +4 -4
  47. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +1 -1
  48. package/build/components/media-placeholder/index.js +29 -21
  49. package/build/components/media-placeholder/index.js.map +1 -1
  50. package/build/hooks/border.js +3 -3
  51. package/build/hooks/border.js.map +1 -1
  52. package/build/hooks/color.js +1 -1
  53. package/build/hooks/color.js.map +1 -1
  54. package/build/hooks/dimensions.js +2 -2
  55. package/build/hooks/dimensions.js.map +1 -1
  56. package/build/hooks/style.js +6 -6
  57. package/build/hooks/style.js.map +1 -1
  58. package/build/hooks/supports.js +1 -1
  59. package/build/hooks/supports.js.map +1 -1
  60. package/build/hooks/typography.js +1 -1
  61. package/build/hooks/typography.js.map +1 -1
  62. package/build/hooks/utils.js +1 -1
  63. package/build/hooks/utils.js.map +1 -1
  64. package/build/layouts/flex.js +11 -9
  65. package/build/layouts/flex.js.map +1 -1
  66. package/build/private-apis.js +4 -4
  67. package/build/private-apis.js.map +1 -1
  68. package/build/store/private-selectors.js +1 -1
  69. package/build/store/private-selectors.js.map +1 -1
  70. package/build/store/selectors.js +3 -0
  71. package/build/store/selectors.js.map +1 -1
  72. package/build-module/components/block-controls/slot.js +6 -3
  73. package/build-module/components/block-controls/slot.js.map +1 -1
  74. package/build-module/components/block-inspector/index.js +1 -2
  75. package/build-module/components/block-inspector/index.js.map +1 -1
  76. package/build-module/components/block-lock/modal.js +1 -1
  77. package/build-module/components/block-lock/modal.js.map +1 -1
  78. package/build-module/components/block-manager/category.js +71 -0
  79. package/build-module/components/block-manager/category.js.map +1 -0
  80. package/build-module/components/block-manager/checklist.js +32 -0
  81. package/build-module/components/block-manager/checklist.js.map +1 -0
  82. package/build-module/components/block-manager/index.js +102 -0
  83. package/build-module/components/block-manager/index.js.map +1 -0
  84. package/build-module/components/block-settings-menu/block-settings-dropdown.js +2 -2
  85. package/build-module/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  86. package/build-module/components/block-settings-menu/index.js +2 -2
  87. package/build-module/components/block-settings-menu/index.js.map +1 -1
  88. package/build-module/components/block-toolbar/index.js +16 -4
  89. package/build-module/components/block-toolbar/index.js.map +1 -1
  90. package/build-module/components/block-toolbar/switch-section-style.js +97 -0
  91. package/build-module/components/block-toolbar/switch-section-style.js.map +1 -0
  92. package/build-module/components/collab/block-comment-icon-slot.js +2 -6
  93. package/build-module/components/collab/block-comment-icon-slot.js.map +1 -1
  94. package/build-module/components/collab/block-comment-icon-toolbar-slot.js +2 -6
  95. package/build-module/components/collab/block-comment-icon-toolbar-slot.js.map +1 -1
  96. package/build-module/components/color-palette/with-color-context.js +4 -2
  97. package/build-module/components/color-palette/with-color-context.js.map +1 -1
  98. package/build-module/components/font-family/index.js +15 -14
  99. package/build-module/components/font-family/index.js.map +1 -1
  100. package/build-module/components/global-styles/use-global-styles-output.js +1 -1
  101. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  102. package/build-module/components/iframe/index.js +15 -102
  103. package/build-module/components/iframe/index.js.map +1 -1
  104. package/build-module/components/iframe/use-scale-canvas.js +371 -0
  105. package/build-module/components/iframe/use-scale-canvas.js.map +1 -0
  106. package/build-module/components/image-editor/use-save-image.js +22 -3
  107. package/build-module/components/image-editor/use-save-image.js.map +1 -1
  108. package/build-module/components/inserter/block-patterns-tab/pattern-category-previews.js +1 -5
  109. package/build-module/components/inserter/block-patterns-tab/pattern-category-previews.js.map +1 -1
  110. package/build-module/components/inserter/category-tabs/index.js +8 -9
  111. package/build-module/components/inserter/category-tabs/index.js.map +1 -1
  112. package/build-module/components/inserter-draggable-blocks/index.js +2 -1
  113. package/build-module/components/inserter-draggable-blocks/index.js.map +1 -1
  114. package/build-module/components/inspector-controls/slot.js +7 -4
  115. package/build-module/components/inspector-controls/slot.js.map +1 -1
  116. package/build-module/components/inspector-controls-tabs/position-controls-panel.js +1 -1
  117. package/build-module/components/inspector-controls-tabs/position-controls-panel.js.map +1 -1
  118. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js +4 -4
  119. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +1 -1
  120. package/build-module/components/media-placeholder/index.js +29 -21
  121. package/build-module/components/media-placeholder/index.js.map +1 -1
  122. package/build-module/hooks/border.js +3 -3
  123. package/build-module/hooks/border.js.map +1 -1
  124. package/build-module/hooks/color.js +1 -1
  125. package/build-module/hooks/color.js.map +1 -1
  126. package/build-module/hooks/dimensions.js +2 -2
  127. package/build-module/hooks/dimensions.js.map +1 -1
  128. package/build-module/hooks/style.js +6 -6
  129. package/build-module/hooks/style.js.map +1 -1
  130. package/build-module/hooks/supports.js +1 -1
  131. package/build-module/hooks/supports.js.map +1 -1
  132. package/build-module/hooks/typography.js +1 -1
  133. package/build-module/hooks/typography.js.map +1 -1
  134. package/build-module/hooks/utils.js +1 -1
  135. package/build-module/hooks/utils.js.map +1 -1
  136. package/build-module/layouts/flex.js +11 -9
  137. package/build-module/layouts/flex.js.map +1 -1
  138. package/build-module/private-apis.js +6 -6
  139. package/build-module/private-apis.js.map +1 -1
  140. package/build-module/store/private-selectors.js +1 -1
  141. package/build-module/store/private-selectors.js.map +1 -1
  142. package/build-module/store/selectors.js +3 -0
  143. package/build-module/store/selectors.js.map +1 -1
  144. package/build-style/content-rtl.css +27 -28
  145. package/build-style/content.css +27 -28
  146. package/build-style/style-rtl.css +108 -0
  147. package/build-style/style.css +108 -0
  148. package/package.json +2 -2
  149. package/src/components/block-canvas/style.scss +2 -1
  150. package/src/components/block-controls/slot.js +5 -3
  151. package/src/components/block-inspector/index.js +0 -2
  152. package/src/components/block-lock/modal.js +1 -1
  153. package/src/components/block-manager/category.js +102 -0
  154. package/src/components/block-manager/checklist.js +34 -0
  155. package/src/components/block-manager/index.js +127 -0
  156. package/src/components/block-manager/style.scss +82 -0
  157. package/src/components/block-mover/README.md +15 -8
  158. package/src/components/block-mover/stories/index.story.js +73 -71
  159. package/src/components/block-mover/style.scss +3 -0
  160. package/src/components/block-settings-menu/block-settings-dropdown.js +2 -2
  161. package/src/components/block-settings-menu/index.js +2 -2
  162. package/src/components/block-toolbar/index.js +14 -0
  163. package/src/components/block-toolbar/switch-section-style.js +115 -0
  164. package/src/components/block-tools/style.scss +39 -0
  165. package/src/components/collab/block-comment-icon-slot.js +2 -6
  166. package/src/components/collab/block-comment-icon-toolbar-slot.js +3 -5
  167. package/src/components/color-palette/test/__snapshots__/control.js.snap +2 -2
  168. package/src/components/color-palette/with-color-context.js +25 -7
  169. package/src/components/font-family/index.js +13 -13
  170. package/src/components/global-styles/test/use-global-styles-output.js +1 -1
  171. package/src/components/global-styles/use-global-styles-output.js +1 -1
  172. package/src/components/iframe/content.scss +49 -43
  173. package/src/components/iframe/index.js +14 -171
  174. package/src/components/iframe/use-scale-canvas.js +468 -0
  175. package/src/components/image-editor/use-save-image.js +27 -2
  176. package/src/components/inserter/block-patterns-tab/pattern-category-previews.js +7 -16
  177. package/src/components/inserter/category-tabs/index.js +8 -9
  178. package/src/components/inserter-draggable-blocks/index.js +10 -1
  179. package/src/components/inspector-controls/README.md +2 -0
  180. package/src/components/inspector-controls/slot.js +6 -4
  181. package/src/components/inspector-controls-tabs/position-controls-panel.js +1 -3
  182. package/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +10 -10
  183. package/src/components/media-placeholder/index.js +37 -33
  184. package/src/components/provider/test/use-block-sync.js +3 -1
  185. package/src/components/rich-text/content.scss +15 -10
  186. package/src/hooks/border.js +3 -9
  187. package/src/hooks/color.js +1 -1
  188. package/src/hooks/dimensions.js +2 -2
  189. package/src/hooks/style.js +6 -12
  190. package/src/hooks/supports.js +1 -1
  191. package/src/hooks/test/style.js +1 -2
  192. package/src/hooks/typography.js +1 -1
  193. package/src/hooks/utils.js +1 -1
  194. package/src/layouts/flex.js +26 -18
  195. package/src/private-apis.js +6 -6
  196. package/src/store/private-selectors.js +1 -1
  197. package/src/store/selectors.js +3 -0
  198. package/src/store/test/selectors.js +87 -58
  199. package/src/style.scss +1 -0
  200. package/build/components/block-info-slot-fill/index.js +0 -39
  201. package/build/components/block-info-slot-fill/index.js.map +0 -1
  202. package/build-module/components/block-info-slot-fill/index.js +0 -32
  203. package/build-module/components/block-info-slot-fill/index.js.map +0 -1
  204. package/src/components/block-info-slot-fill/index.js +0 -27
@@ -0,0 +1,468 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useEffect, useRef, useCallback } from '@wordpress/element';
5
+ import { useReducedMotion, useResizeObserver } from '@wordpress/compose';
6
+
7
+ /**
8
+ * @typedef {Object} TransitionState
9
+ * @property {number} scaleValue Scale of the canvas.
10
+ * @property {number} frameSize Size of the frame/offset around the canvas.
11
+ * @property {number} clientHeight ClientHeight of the iframe.
12
+ * @property {number} scrollTop ScrollTop of the iframe.
13
+ * @property {number} scrollHeight ScrollHeight of the iframe.
14
+ */
15
+
16
+ /**
17
+ * Calculate the scale of the canvas.
18
+ *
19
+ * @param {Object} options Object of options
20
+ * @param {number} options.frameSize Size of the frame/offset around the canvas
21
+ * @param {number} options.containerWidth Actual width of the canvas container
22
+ * @param {number} options.maxContainerWidth Maximum width of the container to use for the scale calculation. This locks the canvas to a maximum width when zooming out.
23
+ * @param {number} options.scaleContainerWidth Width the of the container wrapping the canvas container
24
+ * @return {number} Scale value between 0 and/or equal to 1
25
+ */
26
+ function calculateScale( {
27
+ frameSize,
28
+ containerWidth,
29
+ maxContainerWidth,
30
+ scaleContainerWidth,
31
+ } ) {
32
+ return (
33
+ ( Math.min( containerWidth, maxContainerWidth ) - frameSize * 2 ) /
34
+ scaleContainerWidth
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Compute the next scrollTop position after scaling the iframe content.
40
+ *
41
+ * @param {TransitionState} transitionFrom Starting point of the transition
42
+ * @param {TransitionState} transitionTo Ending state of the transition
43
+ * @return {number} Next scrollTop position after scaling the iframe content.
44
+ */
45
+ function computeScrollTopNext( transitionFrom, transitionTo ) {
46
+ const {
47
+ clientHeight: prevClientHeight,
48
+ frameSize: prevFrameSize,
49
+ scaleValue: prevScale,
50
+ scrollTop,
51
+ scrollHeight,
52
+ } = transitionFrom;
53
+ const { clientHeight, frameSize, scaleValue } = transitionTo;
54
+ // Step 0: Start with the current scrollTop.
55
+ let scrollTopNext = scrollTop;
56
+ // Step 1: Undo the effects of the previous scale and frame around the
57
+ // midpoint of the visible area.
58
+ scrollTopNext =
59
+ ( scrollTopNext + prevClientHeight / 2 - prevFrameSize ) / prevScale -
60
+ prevClientHeight / 2;
61
+
62
+ // Step 2: Apply the new scale and frame around the midpoint of the
63
+ // visible area.
64
+ scrollTopNext =
65
+ ( scrollTopNext + clientHeight / 2 ) * scaleValue +
66
+ frameSize -
67
+ clientHeight / 2;
68
+
69
+ // Step 3: Handle an edge case so that you scroll to the top of the
70
+ // iframe if the top of the iframe content is visible in the container.
71
+ // The same edge case for the bottom is skipped because changing content
72
+ // makes calculating it impossible.
73
+ scrollTopNext = scrollTop <= prevFrameSize ? 0 : scrollTopNext;
74
+
75
+ // This is the scrollTop value if you are scrolled to the bottom of the
76
+ // iframe. We can't just let the browser handle it because we need to
77
+ // animate the scaling.
78
+ const maxScrollTop =
79
+ scrollHeight * ( scaleValue / prevScale ) +
80
+ frameSize * 2 -
81
+ clientHeight;
82
+
83
+ // Step 4: Clamp the scrollTopNext between the minimum and maximum
84
+ // possible scrollTop positions. Round the value to avoid subpixel
85
+ // truncation by the browser which sometimes causes a 1px error.
86
+ return Math.round(
87
+ Math.min( Math.max( 0, scrollTopNext ), Math.max( 0, maxScrollTop ) )
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Generate the keyframes to use for the zoom out animation.
93
+ *
94
+ * @param {TransitionState} transitionFrom Starting transition state.
95
+ * @param {TransitionState} transitionTo Ending transition state.
96
+ * @return {Object[]} An array of keyframes to use for the animation.
97
+ */
98
+ function getAnimationKeyframes( transitionFrom, transitionTo ) {
99
+ const {
100
+ scaleValue: prevScale,
101
+ frameSize: prevFrameSize,
102
+ scrollTop,
103
+ } = transitionFrom;
104
+ const { scaleValue, frameSize, scrollTop: scrollTopNext } = transitionTo;
105
+
106
+ return [
107
+ {
108
+ translate: `0 0`,
109
+ scale: prevScale,
110
+ paddingTop: `${ prevFrameSize / prevScale }px`,
111
+ paddingBottom: `${ prevFrameSize / prevScale }px`,
112
+ },
113
+ {
114
+ translate: `0 ${ scrollTop - scrollTopNext }px`,
115
+ scale: scaleValue,
116
+ paddingTop: `${ frameSize / scaleValue }px`,
117
+ paddingBottom: `${ frameSize / scaleValue }px`,
118
+ },
119
+ ];
120
+ }
121
+
122
+ /**
123
+ * @typedef {Object} ScaleCanvasResult
124
+ * @property {boolean} isZoomedOut A boolean indicating if the canvas is zoomed out.
125
+ * @property {number} scaleContainerWidth The width of the container used to calculate the scale.
126
+ * @property {Object} contentResizeListener A resize observer for the content.
127
+ * @property {Object} containerResizeListener A resize observer for the container.
128
+ */
129
+
130
+ /**
131
+ * Handles scaling the canvas for the zoom out mode and animating between
132
+ * the states.
133
+ *
134
+ * @param {Object} options Object of options.
135
+ * @param {number} options.frameSize Size of the frame around the content.
136
+ * @param {Document} options.iframeDocument Document of the iframe.
137
+ * @param {number} options.maxContainerWidth Max width of the canvas to use as the starting scale point. Defaults to 750.
138
+ * @param {number|string} options.scale Scale of the canvas. Can be an decimal between 0 and 1, 1, or 'auto-scaled'.
139
+ * @return {ScaleCanvasResult} Properties of the result.
140
+ */
141
+ export function useScaleCanvas( {
142
+ frameSize,
143
+ iframeDocument,
144
+ maxContainerWidth = 750,
145
+ scale,
146
+ } ) {
147
+ const [ contentResizeListener, { height: contentHeight } ] =
148
+ useResizeObserver();
149
+ const [ containerResizeListener, { width: containerWidth } ] =
150
+ useResizeObserver();
151
+
152
+ const initialContainerWidthRef = useRef( 0 );
153
+ const isZoomedOut = scale !== 1;
154
+ const prefersReducedMotion = useReducedMotion();
155
+ const isAutoScaled = scale === 'auto-scaled';
156
+ // Track if the animation should start when the useEffect runs.
157
+ const startAnimationRef = useRef( false );
158
+ // Track the animation so we know if we have an animation running,
159
+ // and can cancel it, reverse it, call a finish event, etc.
160
+ const animationRef = useRef( null );
161
+
162
+ useEffect( () => {
163
+ if ( ! isZoomedOut ) {
164
+ initialContainerWidthRef.current = containerWidth;
165
+ }
166
+ }, [ containerWidth, isZoomedOut ] );
167
+
168
+ const scaleContainerWidth = Math.max(
169
+ initialContainerWidthRef.current,
170
+ containerWidth
171
+ );
172
+
173
+ const scaleValue = isAutoScaled
174
+ ? calculateScale( {
175
+ frameSize,
176
+ containerWidth,
177
+ maxContainerWidth,
178
+ scaleContainerWidth,
179
+ } )
180
+ : scale;
181
+
182
+ /**
183
+ * The starting transition state for the zoom out animation.
184
+ * @type {import('react').RefObject<TransitionState>}
185
+ */
186
+ const transitionFromRef = useRef( {
187
+ scaleValue,
188
+ frameSize,
189
+ clientHeight: 0,
190
+ scrollTop: 0,
191
+ scrollHeight: 0,
192
+ } );
193
+
194
+ /**
195
+ * The ending transition state for the zoom out animation.
196
+ * @type {import('react').RefObject<TransitionState>}
197
+ */
198
+ const transitionToRef = useRef( {
199
+ scaleValue,
200
+ frameSize,
201
+ clientHeight: 0,
202
+ scrollTop: 0,
203
+ scrollHeight: 0,
204
+ } );
205
+
206
+ /**
207
+ * Start the zoom out animation. This sets the necessary CSS variables
208
+ * for animating the canvas and returns the Animation object.
209
+ *
210
+ * @return {Animation} The animation object for the zoom out animation.
211
+ */
212
+ const startZoomOutAnimation = useCallback( () => {
213
+ const { scrollTop } = transitionFromRef.current;
214
+ const { scrollTop: scrollTopNext } = transitionToRef.current;
215
+
216
+ iframeDocument.documentElement.style.setProperty(
217
+ '--wp-block-editor-iframe-zoom-out-scroll-top',
218
+ `${ scrollTop }px`
219
+ );
220
+
221
+ iframeDocument.documentElement.style.setProperty(
222
+ '--wp-block-editor-iframe-zoom-out-scroll-top-next',
223
+ `${ scrollTopNext }px`
224
+ );
225
+
226
+ iframeDocument.documentElement.classList.add( 'zoom-out-animation' );
227
+
228
+ return iframeDocument.documentElement.animate(
229
+ getAnimationKeyframes(
230
+ transitionFromRef.current,
231
+ transitionToRef.current
232
+ ),
233
+ {
234
+ easing: 'cubic-bezier(0.46, 0.03, 0.52, 0.96)',
235
+ duration: 400,
236
+ }
237
+ );
238
+ }, [ iframeDocument ] );
239
+
240
+ /**
241
+ * Callback when the zoom out animation is finished.
242
+ * - Cleans up animations refs.
243
+ * - Adds final CSS vars for scale and frame size to preserve the state.
244
+ * - Removes the 'zoom-out-animation' class (which has the fixed positioning).
245
+ * - Sets the final scroll position after the canvas is no longer in fixed position.
246
+ * - Removes CSS vars related to the animation.
247
+ * - Sets the transitionFrom to the transitionTo state to be ready for the next animation.
248
+ */
249
+ const finishZoomOutAnimation = useCallback( () => {
250
+ startAnimationRef.current = false;
251
+ animationRef.current = null;
252
+
253
+ // Add our final scale and frame size now that the animation is done.
254
+ iframeDocument.documentElement.style.setProperty(
255
+ '--wp-block-editor-iframe-zoom-out-scale',
256
+ transitionToRef.current.scaleValue
257
+ );
258
+ iframeDocument.documentElement.style.setProperty(
259
+ '--wp-block-editor-iframe-zoom-out-frame-size',
260
+ `${ transitionToRef.current.frameSize }px`
261
+ );
262
+
263
+ iframeDocument.documentElement.classList.remove( 'zoom-out-animation' );
264
+
265
+ // Set the final scroll position that was just animated to.
266
+ // Disable reason: Eslint isn't smart enough to know that this is a
267
+ // DOM element. https://github.com/facebook/react/issues/31483
268
+ // eslint-disable-next-line react-compiler/react-compiler
269
+ iframeDocument.documentElement.scrollTop =
270
+ transitionToRef.current.scrollTop;
271
+
272
+ iframeDocument.documentElement.style.removeProperty(
273
+ '--wp-block-editor-iframe-zoom-out-scroll-top'
274
+ );
275
+ iframeDocument.documentElement.style.removeProperty(
276
+ '--wp-block-editor-iframe-zoom-out-scroll-top-next'
277
+ );
278
+
279
+ // Update previous values.
280
+ transitionFromRef.current = transitionToRef.current;
281
+ }, [ iframeDocument ] );
282
+
283
+ /**
284
+ * Runs when zoom out mode is toggled, and sets the startAnimation flag
285
+ * so the animation will start when the next useEffect runs. We _only_
286
+ * want to animate when the zoom out mode is toggled, not when the scale
287
+ * changes due to the container resizing.
288
+ */
289
+ useEffect( () => {
290
+ if ( ! iframeDocument ) {
291
+ return;
292
+ }
293
+
294
+ if ( isZoomedOut ) {
295
+ iframeDocument.documentElement.classList.add( 'is-zoomed-out' );
296
+ }
297
+
298
+ startAnimationRef.current = true;
299
+
300
+ return () => {
301
+ iframeDocument.documentElement.classList.remove( 'is-zoomed-out' );
302
+ };
303
+ }, [ iframeDocument, isZoomedOut ] );
304
+
305
+ /**
306
+ * This handles:
307
+ * 1. Setting the correct scale and vars of the canvas when zoomed out
308
+ * 2. If zoom out mode has been toggled, runs the animation of zooming in/out
309
+ */
310
+ useEffect( () => {
311
+ if ( ! iframeDocument ) {
312
+ return;
313
+ }
314
+
315
+ // We need to update the appropriate scale to exit from. If sidebars have been opened since setting the
316
+ // original scale, we will snap to a much smaller scale due to the scale container immediately changing sizes when exiting.
317
+ if ( isAutoScaled && transitionFromRef.current.scaleValue !== 1 ) {
318
+ // We use containerWidth as the divisor, as scaleContainerWidth will always match the containerWidth when
319
+ // exiting.
320
+ transitionFromRef.current.scaleValue = calculateScale( {
321
+ frameSize: transitionFromRef.current.frameSize,
322
+ containerWidth,
323
+ maxContainerWidth,
324
+ scaleContainerWidth: containerWidth,
325
+ } );
326
+ }
327
+
328
+ // If we are not going to animate the transition, set the scale and frame size directly.
329
+ // If we are animating, these values will be set when the animation is finished.
330
+ // Example: Opening sidebars that reduce the scale of the canvas, but we don't want to
331
+ // animate the transition.
332
+ if ( ! startAnimationRef.current ) {
333
+ iframeDocument.documentElement.style.setProperty(
334
+ '--wp-block-editor-iframe-zoom-out-scale',
335
+ scaleValue
336
+ );
337
+ iframeDocument.documentElement.style.setProperty(
338
+ '--wp-block-editor-iframe-zoom-out-frame-size',
339
+ `${ frameSize }px`
340
+ );
341
+ }
342
+
343
+ iframeDocument.documentElement.style.setProperty(
344
+ '--wp-block-editor-iframe-zoom-out-content-height',
345
+ `${ contentHeight }px`
346
+ );
347
+
348
+ const clientHeight = iframeDocument.documentElement.clientHeight;
349
+ iframeDocument.documentElement.style.setProperty(
350
+ '--wp-block-editor-iframe-zoom-out-inner-height',
351
+ `${ clientHeight }px`
352
+ );
353
+
354
+ iframeDocument.documentElement.style.setProperty(
355
+ '--wp-block-editor-iframe-zoom-out-container-width',
356
+ `${ containerWidth }px`
357
+ );
358
+ iframeDocument.documentElement.style.setProperty(
359
+ '--wp-block-editor-iframe-zoom-out-scale-container-width',
360
+ `${ scaleContainerWidth }px`
361
+ );
362
+
363
+ /**
364
+ * Handle the zoom out animation:
365
+ *
366
+ * - Get the current scrollTop position.
367
+ * - Calculate where the same scroll position is after scaling.
368
+ * - Apply fixed positioning to the canvas with a transform offset
369
+ * to keep the canvas centered.
370
+ * - Animate the scale and padding to the new scale and frame size.
371
+ * - After the animation is complete, remove the fixed positioning
372
+ * and set the scroll position that keeps everything centered.
373
+ */
374
+ if ( startAnimationRef.current ) {
375
+ // Don't allow a new transition to start again unless it was started by the zoom out mode changing.
376
+ startAnimationRef.current = false;
377
+
378
+ /**
379
+ * If we already have an animation running, reverse it.
380
+ */
381
+ if ( animationRef.current ) {
382
+ animationRef.current.reverse();
383
+ // Swap the transition to/from refs so that we set the correct values when
384
+ // finishZoomOutAnimation runs.
385
+ const tempTransitionFrom = transitionFromRef.current;
386
+ const tempTransitionTo = transitionToRef.current;
387
+ transitionFromRef.current = tempTransitionTo;
388
+ transitionToRef.current = tempTransitionFrom;
389
+ } else {
390
+ /**
391
+ * Start a new zoom animation.
392
+ */
393
+
394
+ // We can't trust the set value from contentHeight, as it was measured
395
+ // before the zoom out mode was changed. After zoom out mode is changed,
396
+ // appenders may appear or disappear, so we need to get the height from
397
+ // the iframe at this point when we're about to animate the zoom out.
398
+ // The iframe scrollTop, scrollHeight, and clientHeight will all be
399
+ // the most accurate.
400
+ transitionFromRef.current.clientHeight =
401
+ transitionFromRef.current.clientHeight ?? clientHeight;
402
+ transitionFromRef.current.scrollTop =
403
+ iframeDocument.documentElement.scrollTop;
404
+ transitionFromRef.current.scrollHeight =
405
+ iframeDocument.documentElement.scrollHeight;
406
+
407
+ transitionToRef.current = {
408
+ scaleValue,
409
+ frameSize,
410
+ clientHeight,
411
+ };
412
+ transitionToRef.current.scrollTop = computeScrollTopNext(
413
+ transitionFromRef.current,
414
+ transitionToRef.current
415
+ );
416
+
417
+ animationRef.current = startZoomOutAnimation();
418
+
419
+ // If the user prefers reduced motion, finish the animation immediately and set the final state.
420
+ if ( prefersReducedMotion ) {
421
+ finishZoomOutAnimation();
422
+ } else {
423
+ animationRef.current.onfinish = finishZoomOutAnimation;
424
+ }
425
+ }
426
+ }
427
+
428
+ return () => {
429
+ iframeDocument.documentElement.style.removeProperty(
430
+ '--wp-block-editor-iframe-zoom-out-scale'
431
+ );
432
+ iframeDocument.documentElement.style.removeProperty(
433
+ '--wp-block-editor-iframe-zoom-out-frame-size'
434
+ );
435
+ iframeDocument.documentElement.style.removeProperty(
436
+ '--wp-block-editor-iframe-zoom-out-content-height'
437
+ );
438
+ iframeDocument.documentElement.style.removeProperty(
439
+ '--wp-block-editor-iframe-zoom-out-inner-height'
440
+ );
441
+ iframeDocument.documentElement.style.removeProperty(
442
+ '--wp-block-editor-iframe-zoom-out-container-width'
443
+ );
444
+ iframeDocument.documentElement.style.removeProperty(
445
+ '--wp-block-editor-iframe-zoom-out-scale-container-width'
446
+ );
447
+ };
448
+ }, [
449
+ startZoomOutAnimation,
450
+ finishZoomOutAnimation,
451
+ prefersReducedMotion,
452
+ isAutoScaled,
453
+ scaleValue,
454
+ frameSize,
455
+ iframeDocument,
456
+ contentHeight,
457
+ containerWidth,
458
+ maxContainerWidth,
459
+ scaleContainerWidth,
460
+ ] );
461
+
462
+ return {
463
+ isZoomedOut,
464
+ scaleContainerWidth,
465
+ contentResizeListener,
466
+ containerResizeListener,
467
+ };
468
+ }
@@ -10,6 +10,12 @@ import { __, sprintf } from '@wordpress/i18n';
10
10
  import { store as noticesStore } from '@wordpress/notices';
11
11
  import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
12
12
 
13
+ const messages = {
14
+ crop: __( 'Image cropped.' ),
15
+ rotate: __( 'Image rotated.' ),
16
+ cropAndRotate: __( 'Image cropped and rotated.' ),
17
+ };
18
+
13
19
  export default function useSaveImage( {
14
20
  crop,
15
21
  rotation,
@@ -18,7 +24,8 @@ export default function useSaveImage( {
18
24
  onSaveImage,
19
25
  onFinishEditing,
20
26
  } ) {
21
- const { createErrorNotice } = useDispatch( noticesStore );
27
+ const { createErrorNotice, createSuccessNotice } =
28
+ useDispatch( noticesStore );
22
29
  const [ isInProgress, setIsInProgress ] = useState( false );
23
30
 
24
31
  const cancel = useCallback( () => {
@@ -61,6 +68,9 @@ export default function useSaveImage( {
61
68
  return;
62
69
  }
63
70
 
71
+ const modifierType =
72
+ modifiers.length === 1 ? modifiers[ 0 ].type : 'cropAndRotate';
73
+
64
74
  apiFetch( {
65
75
  path: `/wp/v2/media/${ id }/edit`,
66
76
  method: 'POST',
@@ -71,11 +81,25 @@ export default function useSaveImage( {
71
81
  id: response.id,
72
82
  url: response.source_url,
73
83
  } );
84
+ createSuccessNotice( messages[ modifierType ], {
85
+ type: 'snackbar',
86
+ actions: [
87
+ {
88
+ label: __( 'Undo' ),
89
+ onClick: () => {
90
+ onSaveImage( {
91
+ id,
92
+ url,
93
+ } );
94
+ },
95
+ },
96
+ ],
97
+ } );
74
98
  } )
75
99
  .catch( ( error ) => {
76
100
  createErrorNotice(
77
101
  sprintf(
78
- /* translators: 1. Error message */
102
+ /* translators: %s: Error message. */
79
103
  __( 'Could not edit image. %s' ),
80
104
  stripHTML( error.message )
81
105
  ),
@@ -96,6 +120,7 @@ export default function useSaveImage( {
96
120
  url,
97
121
  onSaveImage,
98
122
  createErrorNotice,
123
+ createSuccessNotice,
99
124
  onFinishEditing,
100
125
  ] );
101
126
 
@@ -17,7 +17,6 @@ import {
17
17
  __experimentalText as Text,
18
18
  FlexBlock,
19
19
  } from '@wordpress/components';
20
- import { useSelect } from '@wordpress/data';
21
20
 
22
21
  /**
23
22
  * Internal dependencies
@@ -34,8 +33,6 @@ import {
34
33
  starterPatternsCategory,
35
34
  INSERTER_PATTERN_TYPES,
36
35
  } from './utils';
37
- import { store as blockEditorStore } from '../../../store';
38
- import { unlock } from '../../../lock-unlock';
39
36
 
40
37
  const noop = () => {};
41
38
 
@@ -46,10 +43,6 @@ export function PatternCategoryPreviews( {
46
43
  category,
47
44
  showTitlesAsTooltip,
48
45
  } ) {
49
- const isZoomOutMode = useSelect(
50
- ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(),
51
- []
52
- );
53
46
  const [ allPatterns, , onClickPattern ] = usePatternsState(
54
47
  onInsert,
55
48
  rootClientId,
@@ -179,15 +172,13 @@ export function PatternCategoryPreviews( {
179
172
  </VStack>
180
173
  { currentCategoryPatterns.length > 0 && (
181
174
  <>
182
- { isZoomOutMode && (
183
- <Text
184
- size="12"
185
- as="p"
186
- className="block-editor-inserter__help-text"
187
- >
188
- { __( 'Drag and drop patterns into the canvas.' ) }
189
- </Text>
190
- ) }
175
+ <Text
176
+ size="12"
177
+ as="p"
178
+ className="block-editor-inserter__help-text"
179
+ >
180
+ { __( 'Drag and drop patterns into the canvas.' ) }
181
+ </Text>
191
182
  <BlockPatternsList
192
183
  ref={ scrollContainerRef }
193
184
  blockPatterns={ pagingProps.categoryPatterns }
@@ -6,7 +6,7 @@ import {
6
6
  privateApis as componentsPrivateApis,
7
7
  __unstableMotion as motion,
8
8
  } from '@wordpress/components';
9
- import { useState, useEffect } from '@wordpress/element';
9
+ import { useState } from '@wordpress/element';
10
10
 
11
11
  /**
12
12
  * Internal dependencies
@@ -35,14 +35,13 @@ function CategoryTabs( {
35
35
  const selectedTabId = selectedCategory ? selectedCategory.name : null;
36
36
  const [ activeTabId, setActiveId ] = useState();
37
37
  const firstTabId = categories?.[ 0 ]?.name;
38
- useEffect( () => {
39
- // If there is no active tab, make the first tab the active tab, so that
40
- // when focus is moved to the tablist, the first tab will be focused
41
- // despite not being selected
42
- if ( selectedTabId === null && ! activeTabId && firstTabId ) {
43
- setActiveId( firstTabId );
44
- }
45
- }, [ selectedTabId, activeTabId, firstTabId, setActiveId ] );
38
+
39
+ // If there is no active tab, make the first tab the active tab, so that
40
+ // when focus is moved to the tablist, the first tab will be focused
41
+ // despite not being selected
42
+ if ( selectedTabId === null && ! activeTabId && firstTabId ) {
43
+ setActiveId( firstTabId );
44
+ }
46
45
 
47
46
  return (
48
47
  <Tabs
@@ -29,6 +29,15 @@ const InserterDraggableBlocks = ( {
29
29
  blocks,
30
30
  };
31
31
 
32
+ const blocksContainMedia =
33
+ blocks.filter(
34
+ ( block ) =>
35
+ ( block.name === 'core/image' ||
36
+ block.name === 'core/audio' ||
37
+ block.name === 'core/video' ) &&
38
+ ( block.attributes.url || block.attributes.src )
39
+ ).length > 0;
40
+
32
41
  const blockTypeIcon = useSelect(
33
42
  ( select ) => {
34
43
  const { getBlockType } = select( blocksStore );
@@ -63,7 +72,7 @@ const InserterDraggableBlocks = ( {
63
72
  ? [ createBlock( 'core/block', { ref: pattern.id } ) ]
64
73
  : blocks;
65
74
  event.dataTransfer.setData(
66
- 'text/html',
75
+ blocksContainMedia ? 'default' : 'text/html',
67
76
  serialize( parsedBlocks )
68
77
  );
69
78
  } }
@@ -116,6 +116,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', {
116
116
 
117
117
  <TextControl
118
118
  __nextHasNoMarginBottom
119
+ __next40pxDefaultSize
119
120
  label="Text Field"
120
121
  help="Additional help text"
121
122
  value={ textField }
@@ -208,6 +209,7 @@ function MyBlockEdit( { attributes, setAttributes } ) {
208
209
  <InspectorAdvancedControls>
209
210
  <TextControl
210
211
  __nextHasNoMarginBottom
212
+ __next40pxDefaultSize
211
213
  label="HTML anchor"
212
214
  value={ attributes.anchor }
213
215
  onChange={ ( nextValue ) => {
@@ -34,14 +34,14 @@ export default function InspectorControlsSlot( {
34
34
  );
35
35
  group = __experimentalGroup;
36
36
  }
37
- const Slot = groups[ group ]?.Slot;
38
- const fills = useSlotFills( Slot?.__unstableName );
37
+ const slotFill = groups[ group ];
38
+ const fills = useSlotFills( slotFill?.name );
39
39
 
40
40
  const motionContextValue = useContext( MotionContext );
41
41
 
42
42
  const computedFillProps = useMemo(
43
43
  () => ( {
44
- ...( fillProps ?? {} ),
44
+ ...fillProps,
45
45
  forwardedContext: [
46
46
  ...( fillProps?.forwardedContext ?? [] ),
47
47
  [ MotionContext.Provider, { value: motionContextValue } ],
@@ -50,7 +50,7 @@ export default function InspectorControlsSlot( {
50
50
  [ motionContextValue, fillProps ]
51
51
  );
52
52
 
53
- if ( ! Slot ) {
53
+ if ( ! slotFill ) {
54
54
  warning( `Unknown InspectorControls group "${ group }" provided.` );
55
55
  return null;
56
56
  }
@@ -59,6 +59,8 @@ export default function InspectorControlsSlot( {
59
59
  return null;
60
60
  }
61
61
 
62
+ const { Slot } = slotFill;
63
+
62
64
  if ( label ) {
63
65
  return (
64
66
  <BlockSupportToolsPanel group={ group } label={ label }>