@wordpress/block-editor 9.3.0 → 9.4.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 (171) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-pattern-setup/index.js +3 -9
  3. package/build/components/block-pattern-setup/index.js.map +1 -1
  4. package/build/components/block-pattern-setup/setup-toolbar.js +3 -8
  5. package/build/components/block-pattern-setup/setup-toolbar.js.map +1 -1
  6. package/build/components/block-preview/auto.js +21 -5
  7. package/build/components/block-preview/auto.js.map +1 -1
  8. package/build/components/block-settings-menu/block-edit-visually-button.js +70 -0
  9. package/build/components/block-settings-menu/block-edit-visually-button.js.map +1 -0
  10. package/build/components/block-settings-menu/block-settings-dropdown.js +1 -1
  11. package/build/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  12. package/build/components/block-settings-menu/index.js +6 -2
  13. package/build/components/block-settings-menu/index.js.map +1 -1
  14. package/build/components/block-settings-menu-controls/index.js +4 -1
  15. package/build/components/block-settings-menu-controls/index.js.map +1 -1
  16. package/build/components/block-title/use-block-display-title.js +3 -10
  17. package/build/components/block-title/use-block-display-title.js.map +1 -1
  18. package/build/components/colors-gradients/dropdown.js +2 -1
  19. package/build/components/colors-gradients/dropdown.js.map +1 -1
  20. package/build/components/duotone/components.js +145 -0
  21. package/build/components/duotone/components.js.map +1 -0
  22. package/build/components/duotone/index.js +40 -0
  23. package/build/components/duotone/index.js.map +1 -0
  24. package/build/components/duotone/utils.js +38 -0
  25. package/build/components/duotone/utils.js.map +1 -0
  26. package/build/components/duotone-control/index.js +17 -5
  27. package/build/components/duotone-control/index.js.map +1 -1
  28. package/build/components/index.js +14 -0
  29. package/build/components/index.js.map +1 -1
  30. package/build/components/inserter/index.js +3 -3
  31. package/build/components/inserter/index.js.map +1 -1
  32. package/build/components/media-placeholder/index.js +1 -0
  33. package/build/components/media-placeholder/index.js.map +1 -1
  34. package/build/components/media-placeholder/index.native.js +4 -4
  35. package/build/components/media-placeholder/index.native.js.map +1 -1
  36. package/build/components/media-replace-flow/index.js +3 -7
  37. package/build/components/media-replace-flow/index.js.map +1 -1
  38. package/build/components/publish-date-time-picker/index.js +3 -0
  39. package/build/components/publish-date-time-picker/index.js.map +1 -1
  40. package/build/components/rich-text/use-input-rules.js +4 -13
  41. package/build/components/rich-text/use-input-rules.js.map +1 -1
  42. package/build/components/rich-text/use-paste-handler.js +20 -5
  43. package/build/components/rich-text/use-paste-handler.js.map +1 -1
  44. package/build/elements/index.js +11 -3
  45. package/build/elements/index.js.map +1 -1
  46. package/build/hooks/aria-label.js +71 -0
  47. package/build/hooks/aria-label.js.map +1 -0
  48. package/build/hooks/duotone.js +33 -160
  49. package/build/hooks/duotone.js.map +1 -1
  50. package/build/hooks/index.js +3 -7
  51. package/build/hooks/index.js.map +1 -1
  52. package/build/hooks/layout.js +6 -4
  53. package/build/hooks/layout.js.map +1 -1
  54. package/build/index.js +0 -7
  55. package/build/index.js.map +1 -1
  56. package/build/layouts/flex.js +2 -2
  57. package/build/layouts/flex.js.map +1 -1
  58. package/build/store/actions.js +10 -14
  59. package/build/store/actions.js.map +1 -1
  60. package/build/store/reducer.js +18 -9
  61. package/build/store/reducer.js.map +1 -1
  62. package/build/utils/selection.js +34 -0
  63. package/build/utils/selection.js.map +1 -0
  64. package/build-module/components/block-pattern-setup/index.js +3 -9
  65. package/build-module/components/block-pattern-setup/index.js.map +1 -1
  66. package/build-module/components/block-pattern-setup/setup-toolbar.js +3 -8
  67. package/build-module/components/block-pattern-setup/setup-toolbar.js.map +1 -1
  68. package/build-module/components/block-preview/auto.js +20 -5
  69. package/build-module/components/block-preview/auto.js.map +1 -1
  70. package/build-module/components/block-settings-menu/block-edit-visually-button.js +56 -0
  71. package/build-module/components/block-settings-menu/block-edit-visually-button.js.map +1 -0
  72. package/build-module/components/block-settings-menu/block-settings-dropdown.js +2 -4
  73. package/build-module/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  74. package/build-module/components/block-settings-menu/index.js +6 -3
  75. package/build-module/components/block-settings-menu/index.js.map +1 -1
  76. package/build-module/components/block-settings-menu-controls/index.js +5 -2
  77. package/build-module/components/block-settings-menu-controls/index.js.map +1 -1
  78. package/build-module/components/block-title/use-block-display-title.js +3 -9
  79. package/build-module/components/block-title/use-block-display-title.js.map +1 -1
  80. package/build-module/components/colors-gradients/dropdown.js +2 -1
  81. package/build-module/components/colors-gradients/dropdown.js.map +1 -1
  82. package/build-module/components/duotone/components.js +130 -0
  83. package/build-module/components/duotone/components.js.map +1 -0
  84. package/build-module/components/duotone/index.js +3 -0
  85. package/build-module/components/duotone/index.js.map +1 -0
  86. package/build-module/components/duotone/utils.js +30 -0
  87. package/build-module/components/duotone/utils.js.map +1 -0
  88. package/build-module/components/duotone-control/index.js +18 -6
  89. package/build-module/components/duotone-control/index.js.map +1 -1
  90. package/build-module/components/index.js +1 -0
  91. package/build-module/components/index.js.map +1 -1
  92. package/build-module/components/inserter/index.js +3 -2
  93. package/build-module/components/inserter/index.js.map +1 -1
  94. package/build-module/components/media-placeholder/index.js +1 -0
  95. package/build-module/components/media-placeholder/index.js.map +1 -1
  96. package/build-module/components/media-placeholder/index.native.js +5 -3
  97. package/build-module/components/media-placeholder/index.native.js.map +1 -1
  98. package/build-module/components/media-replace-flow/index.js +3 -6
  99. package/build-module/components/media-replace-flow/index.js.map +1 -1
  100. package/build-module/components/publish-date-time-picker/index.js +2 -0
  101. package/build-module/components/publish-date-time-picker/index.js.map +1 -1
  102. package/build-module/components/rich-text/use-input-rules.js +3 -11
  103. package/build-module/components/rich-text/use-input-rules.js.map +1 -1
  104. package/build-module/components/rich-text/use-paste-handler.js +20 -5
  105. package/build-module/components/rich-text/use-paste-handler.js.map +1 -1
  106. package/build-module/elements/index.js +7 -1
  107. package/build-module/elements/index.js.map +1 -1
  108. package/build-module/hooks/aria-label.js +59 -0
  109. package/build-module/hooks/aria-label.js.map +1 -0
  110. package/build-module/hooks/duotone.js +22 -140
  111. package/build-module/hooks/duotone.js.map +1 -1
  112. package/build-module/hooks/index.js +1 -1
  113. package/build-module/hooks/index.js.map +1 -1
  114. package/build-module/hooks/layout.js +6 -4
  115. package/build-module/hooks/layout.js.map +1 -1
  116. package/build-module/index.js +1 -1
  117. package/build-module/index.js.map +1 -1
  118. package/build-module/layouts/flex.js +2 -2
  119. package/build-module/layouts/flex.js.map +1 -1
  120. package/build-module/store/actions.js +6 -11
  121. package/build-module/store/actions.js.map +1 -1
  122. package/build-module/store/reducer.js +19 -10
  123. package/build-module/store/reducer.js.map +1 -1
  124. package/build-module/utils/selection.js +24 -0
  125. package/build-module/utils/selection.js.map +1 -0
  126. package/build-style/style-rtl.css +5 -1
  127. package/build-style/style.css +5 -1
  128. package/package.json +28 -28
  129. package/src/components/block-draggable/test/helpers.native.js +3 -3
  130. package/src/components/block-list/style.scss +1 -1
  131. package/src/components/block-pattern-setup/index.js +2 -10
  132. package/src/components/block-pattern-setup/setup-toolbar.js +2 -9
  133. package/src/components/block-preview/auto.js +17 -3
  134. package/src/components/block-settings-menu/block-edit-visually-button.js +52 -0
  135. package/src/components/block-settings-menu/block-settings-dropdown.js +3 -2
  136. package/src/components/block-settings-menu/index.js +15 -11
  137. package/src/components/block-settings-menu-controls/index.js +3 -2
  138. package/src/components/block-title/use-block-display-title.js +9 -7
  139. package/src/components/colors-gradients/dropdown.js +1 -0
  140. package/src/components/duotone/components.js +133 -0
  141. package/src/components/duotone/index.js +7 -0
  142. package/src/components/duotone/utils.js +25 -0
  143. package/src/components/duotone-control/index.js +12 -7
  144. package/src/components/duotone-control/style.scss +5 -0
  145. package/src/components/index.js +1 -0
  146. package/src/components/inserter/index.js +3 -5
  147. package/src/components/link-control/test/fixtures/index.js +3 -4
  148. package/src/components/link-control/test/index.js +58 -69
  149. package/src/components/media-placeholder/index.js +1 -0
  150. package/src/components/media-placeholder/index.native.js +9 -5
  151. package/src/components/media-replace-flow/index.js +2 -8
  152. package/src/components/media-upload/README.md +8 -0
  153. package/src/components/publish-date-time-picker/index.js +2 -0
  154. package/src/components/responsive-block-control/README.md +3 -1
  155. package/src/components/responsive-block-control/test/index.js +1 -2
  156. package/src/components/rich-text/use-input-rules.js +6 -15
  157. package/src/components/rich-text/use-paste-handler.js +17 -5
  158. package/src/elements/index.js +8 -1
  159. package/src/elements/test/index.js +18 -0
  160. package/src/hooks/aria-label.js +67 -0
  161. package/src/hooks/duotone.js +18 -139
  162. package/src/hooks/index.js +1 -1
  163. package/src/hooks/layout.js +20 -9
  164. package/src/index.js +0 -1
  165. package/src/layouts/flex.js +2 -2
  166. package/src/store/actions.js +8 -21
  167. package/src/store/reducer.js +21 -9
  168. package/src/store/test/reducer.js +138 -10
  169. package/src/store/test/selectors.js +3 -6
  170. package/src/utils/selection.js +26 -0
  171. package/src/utils/test/selection.js +39 -0
@@ -2,14 +2,13 @@
2
2
  * External dependencies
3
3
  */
4
4
  import classnames from 'classnames';
5
- import { colord, extend } from 'colord';
5
+ import { extend } from 'colord';
6
6
  import namesPlugin from 'colord/plugins/names';
7
7
 
8
8
  /**
9
9
  * WordPress dependencies
10
10
  */
11
11
  import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks';
12
- import { SVG } from '@wordpress/components';
13
12
  import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
14
13
  import { addFilter } from '@wordpress/hooks';
15
14
  import { useMemo, useContext, createPortal } from '@wordpress/element';
@@ -23,145 +22,34 @@ import {
23
22
  useSetting,
24
23
  } from '../components';
25
24
  import BlockList from '../components/block-list';
25
+ import {
26
+ __unstableDuotoneFilter as DuotoneFilter,
27
+ __unstableDuotoneStylesheet as DuotoneStylesheet,
28
+ __unstableDuotoneUnsetStylesheet as DuotoneUnsetStylesheet,
29
+ } from '../components/duotone';
26
30
 
27
31
  const EMPTY_ARRAY = [];
28
32
 
29
33
  extend( [ namesPlugin ] );
30
34
 
31
- /**
32
- * Convert a list of colors to an object of R, G, and B values.
33
- *
34
- * @param {string[]} colors Array of RBG color strings.
35
- *
36
- * @return {Object} R, G, and B values.
37
- */
38
- export function getValuesFromColors( colors = [] ) {
39
- const values = { r: [], g: [], b: [], a: [] };
40
-
41
- colors.forEach( ( color ) => {
42
- const rgbColor = colord( color ).toRgb();
43
- values.r.push( rgbColor.r / 255 );
44
- values.g.push( rgbColor.g / 255 );
45
- values.b.push( rgbColor.b / 255 );
46
- values.a.push( rgbColor.a );
47
- } );
48
-
49
- return values;
50
- }
51
-
52
- /**
53
- * Values for the SVG `feComponentTransfer`.
54
- *
55
- * @typedef Values {Object}
56
- * @property {number[]} r Red values.
57
- * @property {number[]} g Green values.
58
- * @property {number[]} b Blue values.
59
- * @property {number[]} a Alpha values.
60
- */
61
-
62
- /**
63
- * Stylesheet for rendering the duotone filter.
64
- *
65
- * @param {Object} props Duotone props.
66
- * @param {string} props.selector Selector to apply the filter to.
67
- * @param {string} props.id Unique id for this duotone filter.
68
- *
69
- * @return {WPElement} Duotone element.
70
- */
71
- function DuotoneStylesheet( { selector, id } ) {
72
- const css = `
73
- ${ selector } {
74
- filter: url( #${ id } );
75
- }
76
- `;
77
- return <style>{ css }</style>;
78
- }
79
-
80
- /**
81
- * SVG for rendering the duotone filter.
82
- *
83
- * @param {Object} props Duotone props.
84
- * @param {string} props.id Unique id for this duotone filter.
85
- * @param {Values} props.values R, G, B, and A values to filter with.
86
- *
87
- * @return {WPElement} Duotone element.
88
- */
89
- function DuotoneFilter( { id, values } ) {
90
- return (
91
- <SVG
92
- xmlnsXlink="http://www.w3.org/1999/xlink"
93
- viewBox="0 0 0 0"
94
- width="0"
95
- height="0"
96
- focusable="false"
97
- role="none"
98
- style={ {
99
- visibility: 'hidden',
100
- position: 'absolute',
101
- left: '-9999px',
102
- overflow: 'hidden',
103
- } }
104
- >
105
- <defs>
106
- <filter id={ id }>
107
- <feColorMatrix
108
- // Use sRGB instead of linearRGB so transparency looks correct.
109
- colorInterpolationFilters="sRGB"
110
- type="matrix"
111
- // Use perceptual brightness to convert to grayscale.
112
- values="
113
- .299 .587 .114 0 0
114
- .299 .587 .114 0 0
115
- .299 .587 .114 0 0
116
- .299 .587 .114 0 0
117
- "
118
- />
119
- <feComponentTransfer
120
- // Use sRGB instead of linearRGB to be consistent with how CSS gradients work.
121
- colorInterpolationFilters="sRGB"
122
- >
123
- <feFuncR
124
- type="table"
125
- tableValues={ values.r.join( ' ' ) }
126
- />
127
- <feFuncG
128
- type="table"
129
- tableValues={ values.g.join( ' ' ) }
130
- />
131
- <feFuncB
132
- type="table"
133
- tableValues={ values.b.join( ' ' ) }
134
- />
135
- <feFuncA
136
- type="table"
137
- tableValues={ values.a.join( ' ' ) }
138
- />
139
- </feComponentTransfer>
140
- <feComposite
141
- // Re-mask the image with the original transparency since the feColorMatrix above loses that information.
142
- in2="SourceGraphic"
143
- operator="in"
144
- />
145
- </filter>
146
- </defs>
147
- </SVG>
148
- );
149
- }
150
-
151
35
  /**
152
36
  * SVG and stylesheet needed for rendering the duotone filter.
153
37
  *
154
- * @param {Object} props Duotone props.
38
+ * @param {Object} props Duotone props.
155
39
  * @param {string} props.selector Selector to apply the filter to.
156
- * @param {string} props.id Unique id for this duotone filter.
157
- * @param {Values} props.values R, G, B, and A values to filter with.
40
+ * @param {string} props.id Unique id for this duotone filter.
41
+ * @param {string[]|"unset"} props.colors Array of RGB color strings ordered from dark to light.
158
42
  *
159
43
  * @return {WPElement} Duotone element.
160
44
  */
161
- function InlineDuotone( { selector, id, values } ) {
45
+ function InlineDuotone( { selector, id, colors } ) {
46
+ if ( colors === 'unset' ) {
47
+ return <DuotoneUnsetStylesheet selector={ selector } />;
48
+ }
49
+
162
50
  return (
163
51
  <>
164
- <DuotoneFilter id={ id } values={ values } />
52
+ <DuotoneFilter id={ id } colors={ colors } />
165
53
  <DuotoneStylesheet id={ id } selector={ selector } />
166
54
  </>
167
55
  );
@@ -324,9 +212,9 @@ const withDuotoneStyles = createHigherOrderComponent(
324
212
  props.name,
325
213
  'color.__experimentalDuotone'
326
214
  );
327
- const values = props?.attributes?.style?.color?.duotone;
215
+ const colors = props?.attributes?.style?.color?.duotone;
328
216
 
329
- if ( ! duotoneSupport || ! values ) {
217
+ if ( ! duotoneSupport || ! colors ) {
330
218
  return <BlockListBlock { ...props } />;
331
219
  }
332
220
 
@@ -351,7 +239,7 @@ const withDuotoneStyles = createHigherOrderComponent(
351
239
  <InlineDuotone
352
240
  selector={ selectorsGroup }
353
241
  id={ id }
354
- values={ getValuesFromColors( values ) }
242
+ colors={ colors }
355
243
  />,
356
244
  element
357
245
  ) }
@@ -362,15 +250,6 @@ const withDuotoneStyles = createHigherOrderComponent(
362
250
  'withDuotoneStyles'
363
251
  );
364
252
 
365
- export function PresetDuotoneFilter( { preset } ) {
366
- return (
367
- <DuotoneFilter
368
- id={ `wp-duotone-${ preset.slug }` }
369
- values={ getValuesFromColors( preset.colors ) }
370
- />
371
- );
372
- }
373
-
374
253
  addFilter(
375
254
  'blocks.registerBlockType',
376
255
  'core/editor/duotone/add-attributes',
@@ -5,6 +5,7 @@ import './compat';
5
5
  import './align';
6
6
  import './lock';
7
7
  import './anchor';
8
+ import './aria-label';
8
9
  import './custom-class-name';
9
10
  import './generated-class-name';
10
11
  import './style';
@@ -20,4 +21,3 @@ export { getBorderClassesAndStyles, useBorderProps } from './use-border-props';
20
21
  export { getColorClassesAndStyles, useColorProps } from './use-color-props';
21
22
  export { getSpacingClassesAndStyles } from './use-spacing-props';
22
23
  export { useCachedTruthy } from './use-cached-truthy';
23
- export { PresetDuotoneFilter } from './duotone';
@@ -130,15 +130,26 @@ function LayoutPanel( { setAttributes, attributes, name: blockName } ) {
130
130
  <InspectorControls>
131
131
  <PanelBody title={ __( 'Layout' ) }>
132
132
  { showInheritToggle && (
133
- <ToggleControl
134
- label={ __( 'Inherit default layout' ) }
135
- checked={ !! inherit }
136
- onChange={ () =>
137
- setAttributes( {
138
- layout: { inherit: ! inherit },
139
- } )
140
- }
141
- />
133
+ <>
134
+ <ToggleControl
135
+ label={ __( 'Inner blocks use full width' ) }
136
+ checked={ ! inherit }
137
+ onChange={ () =>
138
+ setAttributes( {
139
+ layout: { inherit: ! inherit },
140
+ } )
141
+ }
142
+ />
143
+ <p className="block-editor-hooks__layout-controls-helptext">
144
+ { !! inherit
145
+ ? __(
146
+ 'Nested blocks use theme content width with options for full and wide widths.'
147
+ )
148
+ : __(
149
+ 'Nested blocks will fill the width of this container.'
150
+ ) }
151
+ </p>
152
+ </>
142
153
  ) }
143
154
 
144
155
  { ! inherit && allowSwitching && (
package/src/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import './hooks';
5
5
  export {
6
- PresetDuotoneFilter as __unstablePresetDuotoneFilter,
7
6
  getBorderClassesAndStyles as __experimentalGetBorderClassesAndStyles,
8
7
  useBorderProps as __experimentalUseBorderProps,
9
8
  getColorClassesAndStyles as __experimentalGetColorClassesAndStyles,
@@ -324,7 +324,7 @@ function OrientationControl( { layout, onChange } ) {
324
324
  <fieldset className="block-editor-hooks__flex-layout-orientation-controls">
325
325
  <legend>{ __( 'Orientation' ) }</legend>
326
326
  <Button
327
- label={ 'horizontal' }
327
+ label={ __( 'Horizontal' ) }
328
328
  icon={ arrowRight }
329
329
  isPressed={ orientation === 'horizontal' }
330
330
  onClick={ () =>
@@ -335,7 +335,7 @@ function OrientationControl( { layout, onChange } ) {
335
335
  }
336
336
  />
337
337
  <Button
338
- label={ 'vertical' }
338
+ label={ __( 'Vertical' ) }
339
339
  icon={ arrowDown }
340
340
  isPressed={ orientation === 'vertical' }
341
341
  onClick={ () =>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { castArray, findKey, first, isObject, last, some } from 'lodash';
4
+ import { castArray, first, isObject, last, some } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -26,6 +26,10 @@ import deprecated from '@wordpress/deprecated';
26
26
  * Internal dependencies
27
27
  */
28
28
  import { mapRichTextSettings } from './utils';
29
+ import {
30
+ retrieveSelectedAttribute,
31
+ START_OF_SELECTED_AREA,
32
+ } from '../utils/selection';
29
33
 
30
34
  /**
31
35
  * Action which will insert a default block insert action if there
@@ -771,10 +775,6 @@ export const __unstableDeleteSelection =
771
775
  ...mapRichTextSettings( attributeDefinitionB ),
772
776
  } );
773
777
 
774
- // A robust way to retain selection position through various transforms
775
- // is to insert a special character at the position and then recover it.
776
- const START_OF_SELECTED_AREA = '\u0086';
777
-
778
778
  valueA = remove( valueA, selectionA.offset, valueA.text.length );
779
779
  valueB = insert( valueB, START_OF_SELECTED_AREA, 0, selectionB.offset );
780
780
 
@@ -822,12 +822,7 @@ export const __unstableDeleteSelection =
822
822
  );
823
823
  }
824
824
 
825
- const newAttributeKey = findKey(
826
- updatedAttributes,
827
- ( v ) =>
828
- typeof v === 'string' &&
829
- v.indexOf( START_OF_SELECTED_AREA ) !== -1
830
- );
825
+ const newAttributeKey = retrieveSelectedAttribute( updatedAttributes );
831
826
 
832
827
  const convertedHtml = updatedAttributes[ newAttributeKey ];
833
828
  const convertedValue = create( {
@@ -1052,10 +1047,6 @@ export const mergeBlocks =
1052
1047
  }
1053
1048
  }
1054
1049
 
1055
- // A robust way to retain selection position through various transforms
1056
- // is to insert a special character at the position and then recover it.
1057
- const START_OF_SELECTED_AREA = '\u0086';
1058
-
1059
1050
  // Clone the blocks so we don't insert the character in a "live" block.
1060
1051
  const cloneA = cloneBlock( blockA );
1061
1052
  const cloneB = cloneBlock( blockB );
@@ -1098,12 +1089,8 @@ export const mergeBlocks =
1098
1089
  );
1099
1090
 
1100
1091
  if ( canRestoreTextSelection ) {
1101
- const newAttributeKey = findKey(
1102
- updatedAttributes,
1103
- ( v ) =>
1104
- typeof v === 'string' &&
1105
- v.indexOf( START_OF_SELECTED_AREA ) !== -1
1106
- );
1092
+ const newAttributeKey =
1093
+ retrieveSelectedAttribute( updatedAttributes );
1107
1094
  const convertedHtml = updatedAttributes[ newAttributeKey ];
1108
1095
  const convertedValue = create( {
1109
1096
  html: convertedHtml,
@@ -9,7 +9,6 @@ import {
9
9
  omit,
10
10
  without,
11
11
  mapValues,
12
- keys,
13
12
  isEqual,
14
13
  isEmpty,
15
14
  identity,
@@ -148,7 +147,7 @@ function getMutateSafeObject( original, working ) {
148
147
  * @return {boolean} Whether the two objects have the same keys.
149
148
  */
150
149
  export function hasSameKeys( a, b ) {
151
- return isEqual( keys( a ), keys( b ) );
150
+ return isEqual( Object.keys( a ), Object.keys( b ) );
152
151
  }
153
152
 
154
153
  /**
@@ -399,13 +398,12 @@ const withBlockTree =
399
398
  const updatedBlockUids = [];
400
399
  if ( action.fromRootClientId ) {
401
400
  updatedBlockUids.push( action.fromRootClientId );
401
+ } else {
402
+ updatedBlockUids.push( '' );
402
403
  }
403
404
  if ( action.toRootClientId ) {
404
405
  updatedBlockUids.push( action.toRootClientId );
405
406
  }
406
- if ( ! action.fromRootClientId || ! action.fromRootClientId ) {
407
- updatedBlockUids.push( '' );
408
- }
409
407
  newState.tree = updateParentInnerBlocksInTree(
410
408
  newState,
411
409
  newState.tree,
@@ -428,7 +426,7 @@ const withBlockTree =
428
426
  break;
429
427
  }
430
428
  case 'SAVE_REUSABLE_BLOCK_SUCCESS': {
431
- const updatedBlockUids = keys(
429
+ const updatedBlockUids = Object.keys(
432
430
  omitBy( newState.attributes, ( attributes, clientId ) => {
433
431
  return (
434
432
  newState.byClientId[ clientId ].name !==
@@ -690,9 +688,9 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => {
690
688
  index: 0,
691
689
  } );
692
690
 
693
- // We need to re-attach the block order of the controlled inner blocks.
694
- // Otherwise, an inner block controller's blocks will be deleted entirely
695
- // from its entity..
691
+ // We need to re-attach the controlled inner blocks to the blocks tree and
692
+ // preserve their block order. Otherwise, an inner block controller's blocks
693
+ // will be deleted entirely from its entity.
696
694
  stateAfterInsert.order = {
697
695
  ...stateAfterInsert.order,
698
696
  ...reduce(
@@ -706,6 +704,20 @@ const withReplaceInnerBlocks = ( reducer ) => ( state, action ) => {
706
704
  {}
707
705
  ),
708
706
  };
707
+ stateAfterInsert.tree = {
708
+ ...stateAfterInsert.tree,
709
+ ...reduce(
710
+ nestedControllers,
711
+ ( result, value, _key ) => {
712
+ const key = `controlled||${ _key }`;
713
+ if ( state.tree[ key ] ) {
714
+ result[ key ] = state.tree[ key ];
715
+ }
716
+ return result;
717
+ },
718
+ {}
719
+ ),
720
+ };
709
721
  }
710
722
  return stateAfterInsert;
711
723
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { values, omit } from 'lodash';
4
+ import { omit } from 'lodash';
5
5
  import deepFreeze from 'deep-freeze';
6
6
 
7
7
  /**
@@ -666,7 +666,7 @@ describe( 'state', () => {
666
666
  } );
667
667
 
668
668
  expect( Object.keys( state.byClientId ) ).toHaveLength( 1 );
669
- expect( values( state.byClientId )[ 0 ].clientId ).toBe(
669
+ expect( Object.values( state.byClientId )[ 0 ].clientId ).toBe(
670
670
  'bananas'
671
671
  );
672
672
  expect( state.order ).toEqual( {
@@ -729,7 +729,9 @@ describe( 'state', () => {
729
729
  } );
730
730
 
731
731
  expect( Object.keys( state.byClientId ) ).toHaveLength( 2 );
732
- expect( values( state.byClientId )[ 1 ].clientId ).toBe( 'ribs' );
732
+ expect( Object.values( state.byClientId )[ 1 ].clientId ).toBe(
733
+ 'ribs'
734
+ );
733
735
  expect( state.order ).toEqual( {
734
736
  '': [ 'chicken', 'ribs' ],
735
737
  chicken: [],
@@ -773,10 +775,12 @@ describe( 'state', () => {
773
775
  } );
774
776
 
775
777
  expect( Object.keys( state.byClientId ) ).toHaveLength( 1 );
776
- expect( values( state.byClientId )[ 0 ].name ).toBe(
778
+ expect( Object.values( state.byClientId )[ 0 ].name ).toBe(
777
779
  'core/freeform'
778
780
  );
779
- expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'wings' );
781
+ expect( Object.values( state.byClientId )[ 0 ].clientId ).toBe(
782
+ 'wings'
783
+ );
780
784
  expect( state.order ).toEqual( {
781
785
  '': [ 'wings' ],
782
786
  wings: [],
@@ -930,15 +934,15 @@ describe( 'state', () => {
930
934
  } );
931
935
 
932
936
  expect( Object.keys( replacedState.byClientId ) ).toHaveLength( 1 );
933
- expect( values( originalState.byClientId )[ 0 ].name ).toBe(
937
+ expect( Object.values( originalState.byClientId )[ 0 ].name ).toBe(
934
938
  'core/test-block'
935
939
  );
936
- expect( values( replacedState.byClientId )[ 0 ].name ).toBe(
940
+ expect( Object.values( replacedState.byClientId )[ 0 ].name ).toBe(
937
941
  'core/freeform'
938
942
  );
939
- expect( values( replacedState.byClientId )[ 0 ].clientId ).toBe(
940
- 'chicken'
941
- );
943
+ expect(
944
+ Object.values( replacedState.byClientId )[ 0 ].clientId
945
+ ).toBe( 'chicken' );
942
946
  expect( replacedState.order ).toEqual( {
943
947
  '': [ 'chicken' ],
944
948
  chicken: [],
@@ -2090,6 +2094,130 @@ describe( 'state', () => {
2090
2094
  expect( state.tree.child ).toBeUndefined();
2091
2095
  expect( state.tree.chicken.innerBlocks ).toEqual( [] );
2092
2096
  } );
2097
+ it( 'should preserve the controlled blocks in state and re-attach them in other pieces of state(order, tree, etc..), when we replace inner blocks', () => {
2098
+ const initialState = {
2099
+ byClientId: {
2100
+ 'group-id': {
2101
+ clientId: 'group-id',
2102
+ name: 'core/group',
2103
+ isValid: true,
2104
+ },
2105
+ 'reusable-id': {
2106
+ clientId: 'reusable-id',
2107
+ name: 'core/block',
2108
+ isValid: true,
2109
+ },
2110
+ 'paragraph-id': {
2111
+ clientId: 'paragraph-id',
2112
+ name: 'core/paragraph',
2113
+ isValid: true,
2114
+ },
2115
+ },
2116
+ order: {
2117
+ '': [ 'group-id' ],
2118
+ 'group-id': [ 'reusable-id' ],
2119
+ 'reusable-id': [ 'paragraph-id' ],
2120
+ 'paragraph-id': [],
2121
+ },
2122
+ controlledInnerBlocks: {
2123
+ 'reusable-id': true,
2124
+ },
2125
+ parents: {
2126
+ 'group-id': '',
2127
+ 'reusable-id': 'group-id',
2128
+ 'paragraph-id': 'reusable-id',
2129
+ },
2130
+ tree: {
2131
+ 'group-id': {
2132
+ clientId: 'group-id',
2133
+ name: 'core/group',
2134
+ isValid: true,
2135
+ innerBlocks: [
2136
+ {
2137
+ clientId: 'reusable-id',
2138
+ name: 'core/block',
2139
+ isValid: true,
2140
+ attributes: {
2141
+ ref: 687,
2142
+ },
2143
+ innerBlocks: [],
2144
+ },
2145
+ ],
2146
+ },
2147
+ 'reusable-id': {
2148
+ clientId: 'reusable-id',
2149
+ name: 'core/block',
2150
+ isValid: true,
2151
+ attributes: {
2152
+ ref: 687,
2153
+ },
2154
+ innerBlocks: [],
2155
+ },
2156
+ '': {
2157
+ innerBlocks: [
2158
+ {
2159
+ clientId: 'group-id',
2160
+ name: 'core/group',
2161
+ isValid: true,
2162
+ innerBlocks: [
2163
+ {
2164
+ clientId: 'reusable-id',
2165
+ name: 'core/block',
2166
+ isValid: true,
2167
+ attributes: {
2168
+ ref: 687,
2169
+ },
2170
+ innerBlocks: [],
2171
+ },
2172
+ ],
2173
+ },
2174
+ ],
2175
+ },
2176
+ 'paragraph-id': {
2177
+ clientId: 'paragraph-id',
2178
+ name: 'core/paragraph',
2179
+ isValid: true,
2180
+ innerBlocks: [],
2181
+ },
2182
+ 'controlled||reusable-id': {
2183
+ innerBlocks: [
2184
+ {
2185
+ clientId: 'paragraph-id',
2186
+ name: 'core/paragraph',
2187
+ isValid: true,
2188
+ innerBlocks: [],
2189
+ },
2190
+ ],
2191
+ },
2192
+ },
2193
+ };
2194
+ // We will dispatch an action that replaces the inner
2195
+ // blocks with the same inner blocks, which contain
2196
+ // a controlled block (`reusable-id`).
2197
+ const action = {
2198
+ type: 'REPLACE_INNER_BLOCKS',
2199
+ rootClientId: 'group-id',
2200
+ blocks: [
2201
+ {
2202
+ clientId: 'reusable-id',
2203
+ name: 'core/block',
2204
+ isValid: true,
2205
+ attributes: {
2206
+ ref: 687,
2207
+ },
2208
+ innerBlocks: [],
2209
+ },
2210
+ ],
2211
+ updateSelection: false,
2212
+ };
2213
+ const state = blocks( initialState, action );
2214
+ expect( state.order ).toEqual(
2215
+ expect.objectContaining( initialState.order )
2216
+ );
2217
+ expect( state.tree ).toEqual(
2218
+ expect.objectContaining( initialState.tree )
2219
+ );
2220
+ } );
2093
2221
  } );
2094
2222
  } );
2095
2223
  } );
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { filter } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -83,7 +78,9 @@ describe( 'selectors', () => {
83
78
  let cachedSelectors;
84
79
 
85
80
  beforeAll( () => {
86
- cachedSelectors = filter( selectors, ( selector ) => selector.clear );
81
+ cachedSelectors = Object.entries( selectors )
82
+ .filter( ( [ , selector ] ) => selector.clear )
83
+ .map( ( [ , selector ] ) => selector );
87
84
  } );
88
85
 
89
86
  beforeEach( () => {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A robust way to retain selection position through various
3
+ * transforms is to insert a special character at the position and
4
+ * then recover it.
5
+ */
6
+ export const START_OF_SELECTED_AREA = '\u0086';
7
+
8
+ /**
9
+ * Retrieve the block attribute that contains the selection position.
10
+ *
11
+ * @param {Object} blockAttributes Block attributes.
12
+ * @return {string|void} The name of the block attribute that was previously selected.
13
+ */
14
+ export function retrieveSelectedAttribute( blockAttributes ) {
15
+ if ( ! blockAttributes ) {
16
+ return;
17
+ }
18
+
19
+ return Object.keys( blockAttributes ).find( ( name ) => {
20
+ const value = blockAttributes[ name ];
21
+ return (
22
+ typeof value === 'string' &&
23
+ value.indexOf( START_OF_SELECTED_AREA ) !== -1
24
+ );
25
+ } );
26
+ }