@wordpress/block-editor 8.5.1 → 8.5.4

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 (100) hide show
  1. package/build/components/block-alignment-control/ui.js +1 -1
  2. package/build/components/block-alignment-control/ui.js.map +1 -1
  3. package/build/components/block-content-overlay/index.js +4 -13
  4. package/build/components/block-content-overlay/index.js.map +1 -1
  5. package/build/components/block-lock/menu-item.js +1 -1
  6. package/build/components/block-lock/menu-item.js.map +1 -1
  7. package/build/components/block-lock/modal.js +5 -35
  8. package/build/components/block-lock/modal.js.map +1 -1
  9. package/build/components/block-lock/toolbar.js +1 -2
  10. package/build/components/block-lock/toolbar.js.map +1 -1
  11. package/build/components/block-lock/use-block-lock.js +4 -10
  12. package/build/components/block-lock/use-block-lock.js.map +1 -1
  13. package/build/components/copy-handler/index.js +44 -9
  14. package/build/components/copy-handler/index.js.map +1 -1
  15. package/build/components/inserter/index.js +21 -7
  16. package/build/components/inserter/index.js.map +1 -1
  17. package/build/components/inserter/quick-inserter.js +4 -5
  18. package/build/components/inserter/quick-inserter.js.map +1 -1
  19. package/build/components/link-control/index.js +6 -7
  20. package/build/components/link-control/index.js.map +1 -1
  21. package/build/components/list-view/block.js +13 -2
  22. package/build/components/list-view/block.js.map +1 -1
  23. package/build/components/writing-flow/use-click-selection.js +1 -3
  24. package/build/components/writing-flow/use-click-selection.js.map +1 -1
  25. package/build/components/writing-flow/use-selection-observer.js +49 -8
  26. package/build/components/writing-flow/use-selection-observer.js.map +1 -1
  27. package/build/hooks/duotone.js +66 -16
  28. package/build/hooks/duotone.js.map +1 -1
  29. package/build/hooks/index.js +7 -1
  30. package/build/hooks/index.js.map +1 -1
  31. package/build/index.js +7 -0
  32. package/build/index.js.map +1 -1
  33. package/build/store/actions.js +22 -29
  34. package/build/store/actions.js.map +1 -1
  35. package/build/store/selectors.js +96 -25
  36. package/build/store/selectors.js.map +1 -1
  37. package/build/store/utils.js +27 -0
  38. package/build/store/utils.js.map +1 -0
  39. package/build-module/components/block-alignment-control/ui.js +2 -2
  40. package/build-module/components/block-alignment-control/ui.js.map +1 -1
  41. package/build-module/components/block-content-overlay/index.js +4 -13
  42. package/build-module/components/block-content-overlay/index.js.map +1 -1
  43. package/build-module/components/block-lock/menu-item.js +1 -1
  44. package/build-module/components/block-lock/menu-item.js.map +1 -1
  45. package/build-module/components/block-lock/modal.js +6 -35
  46. package/build-module/components/block-lock/modal.js.map +1 -1
  47. package/build-module/components/block-lock/toolbar.js +1 -2
  48. package/build-module/components/block-lock/toolbar.js.map +1 -1
  49. package/build-module/components/block-lock/use-block-lock.js +4 -10
  50. package/build-module/components/block-lock/use-block-lock.js.map +1 -1
  51. package/build-module/components/copy-handler/index.js +44 -9
  52. package/build-module/components/copy-handler/index.js.map +1 -1
  53. package/build-module/components/inserter/index.js +21 -7
  54. package/build-module/components/inserter/index.js.map +1 -1
  55. package/build-module/components/inserter/quick-inserter.js +4 -5
  56. package/build-module/components/inserter/quick-inserter.js.map +1 -1
  57. package/build-module/components/link-control/index.js +6 -7
  58. package/build-module/components/link-control/index.js.map +1 -1
  59. package/build-module/components/list-view/block.js +13 -2
  60. package/build-module/components/list-view/block.js.map +1 -1
  61. package/build-module/components/writing-flow/use-click-selection.js +1 -3
  62. package/build-module/components/writing-flow/use-click-selection.js.map +1 -1
  63. package/build-module/components/writing-flow/use-selection-observer.js +49 -8
  64. package/build-module/components/writing-flow/use-selection-observer.js.map +1 -1
  65. package/build-module/hooks/duotone.js +63 -16
  66. package/build-module/hooks/duotone.js.map +1 -1
  67. package/build-module/hooks/index.js +1 -0
  68. package/build-module/hooks/index.js.map +1 -1
  69. package/build-module/index.js +1 -1
  70. package/build-module/index.js.map +1 -1
  71. package/build-module/store/actions.js +5 -14
  72. package/build-module/store/actions.js.map +1 -1
  73. package/build-module/store/selectors.js +88 -22
  74. package/build-module/store/selectors.js.map +1 -1
  75. package/build-module/store/utils.js +20 -0
  76. package/build-module/store/utils.js.map +1 -0
  77. package/build-style/style-rtl.css +1 -3
  78. package/build-style/style.css +1 -3
  79. package/package.json +28 -28
  80. package/src/components/block-alignment-control/ui.js +2 -2
  81. package/src/components/block-content-overlay/index.js +2 -19
  82. package/src/components/block-content-overlay/style.scss +0 -1
  83. package/src/components/block-lock/menu-item.js +1 -1
  84. package/src/components/block-lock/modal.js +3 -42
  85. package/src/components/block-lock/style.scss +1 -2
  86. package/src/components/block-lock/toolbar.js +2 -2
  87. package/src/components/block-lock/use-block-lock.js +5 -12
  88. package/src/components/copy-handler/index.js +52 -10
  89. package/src/components/inserter/index.js +20 -0
  90. package/src/components/inserter/quick-inserter.js +3 -11
  91. package/src/components/link-control/index.js +5 -5
  92. package/src/components/list-view/block.js +16 -7
  93. package/src/components/writing-flow/use-click-selection.js +1 -4
  94. package/src/components/writing-flow/use-selection-observer.js +55 -10
  95. package/src/hooks/duotone.js +98 -62
  96. package/src/hooks/index.js +1 -0
  97. package/src/index.js +1 -0
  98. package/src/store/actions.js +5 -13
  99. package/src/store/selectors.js +126 -20
  100. package/src/store/utils.js +19 -0
@@ -11,10 +11,9 @@ import { store as blockEditorStore } from '../../store';
11
11
  import { getBlockClientId } from '../../utils/dom';
12
12
 
13
13
  export default function useClickSelection() {
14
- const { multiSelect, selectBlock } = useDispatch( blockEditorStore );
14
+ const { selectBlock } = useDispatch( blockEditorStore );
15
15
  const {
16
16
  isSelectionEnabled,
17
- getBlockParents,
18
17
  getBlockSelectionStart,
19
18
  hasMultiSelection,
20
19
  } = useSelect( blockEditorStore );
@@ -54,10 +53,8 @@ export default function useClickSelection() {
54
53
  };
55
54
  },
56
55
  [
57
- multiSelect,
58
56
  selectBlock,
59
57
  isSelectionEnabled,
60
- getBlockParents,
61
58
  getBlockSelectionStart,
62
59
  hasMultiSelection,
63
60
  ]
@@ -76,36 +76,81 @@ export default function useSelectionObserver() {
76
76
  const { multiSelect, selectBlock, selectionChange } = useDispatch(
77
77
  blockEditorStore
78
78
  );
79
- const { getBlockParents } = useSelect( blockEditorStore );
79
+ const { getBlockParents, getBlockSelectionStart } = useSelect(
80
+ blockEditorStore
81
+ );
80
82
  return useRefEffect(
81
83
  ( node ) => {
82
84
  const { ownerDocument } = node;
83
85
  const { defaultView } = ownerDocument;
84
86
 
85
- function onSelectionChange() {
87
+ function onSelectionChange( event ) {
86
88
  const selection = defaultView.getSelection();
87
-
88
89
  // If no selection is found, end multi selection and disable the
89
90
  // contentEditable wrapper.
90
- if ( ! selection.rangeCount || selection.isCollapsed ) {
91
+ if ( ! selection.rangeCount ) {
92
+ setContentEditableWrapper( node, false );
93
+ return;
94
+ }
95
+ // If selection is collapsed and we haven't used `shift+click`,
96
+ // end multi selection and disable the contentEditable wrapper.
97
+ // We have to check about `shift+click` case because elements
98
+ // that don't support text selection might be involved, and we might
99
+ // update the clientIds to multi-select blocks.
100
+ // For now we check if the event is a `mouse` event.
101
+ const isClickShift = event.shiftKey && event.type === 'mouseup';
102
+ if ( selection.isCollapsed && ! isClickShift ) {
91
103
  setContentEditableWrapper( node, false );
92
104
  return;
93
105
  }
94
106
 
95
- const clientId = getBlockClientId(
107
+ let startClientId = getBlockClientId(
96
108
  extractSelectionStartNode( selection )
97
109
  );
98
- const endClientId = getBlockClientId(
110
+ let endClientId = getBlockClientId(
99
111
  extractSelectionEndNode( selection )
100
112
  );
101
- const isSingularSelection = clientId === endClientId;
113
+ // If the selection has changed and we had pressed `shift+click`,
114
+ // we need to check if in an element that doesn't support
115
+ // text selection has been clicked.
116
+ if ( isClickShift ) {
117
+ const selectedClientId = getBlockSelectionStart();
118
+ const clickedClientId = getBlockClientId( event.target );
119
+ // `endClientId` is not defined if we end the selection by clicking a non-selectable block.
120
+ // We need to check if there was already a selection with a non-selectable focusNode.
121
+ const focusNodeIsNonSelectable =
122
+ clickedClientId !== endClientId;
123
+ if (
124
+ ( startClientId === endClientId &&
125
+ selection.isCollapsed ) ||
126
+ ! endClientId ||
127
+ focusNodeIsNonSelectable
128
+ ) {
129
+ endClientId = clickedClientId;
130
+ }
131
+ // Handle the case when we have a non-selectable block
132
+ // selected and click another one.
133
+ if ( startClientId !== selectedClientId ) {
134
+ startClientId = selectedClientId;
135
+ }
136
+ }
137
+
138
+ // If the selection did not involve a block, return.
139
+ if (
140
+ startClientId === undefined &&
141
+ endClientId === undefined
142
+ ) {
143
+ setContentEditableWrapper( node, false );
144
+ return;
145
+ }
102
146
 
147
+ const isSingularSelection = startClientId === endClientId;
103
148
  if ( isSingularSelection ) {
104
- selectBlock( clientId );
149
+ selectBlock( startClientId );
105
150
  } else {
106
151
  const startPath = [
107
- ...getBlockParents( clientId ),
108
- clientId,
152
+ ...getBlockParents( startClientId ),
153
+ startClientId,
109
154
  ];
110
155
  const endPath = [
111
156
  ...getBlockParents( endClientId ),
@@ -60,82 +60,109 @@ export function getValuesFromColors( colors = [] ) {
60
60
  */
61
61
 
62
62
  /**
63
- * SVG and stylesheet needed for rendering the duotone filter.
63
+ * Stylesheet for rendering the duotone filter.
64
64
  *
65
65
  * @param {Object} props Duotone props.
66
66
  * @param {string} props.selector Selector to apply the filter to.
67
67
  * @param {string} props.id Unique id for this duotone filter.
68
- * @param {Values} props.values R, G, B, and A values to filter with.
69
68
  *
70
69
  * @return {WPElement} Duotone element.
71
70
  */
72
- function DuotoneFilter( { selector, id, values } ) {
73
- const stylesheet = `
71
+ function DuotoneStylesheet( { selector, id } ) {
72
+ const css = `
74
73
  ${ selector } {
75
74
  filter: url( #${ id } );
76
75
  }
77
76
  `;
77
+ return <style>{ css }</style>;
78
+ }
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 } ) {
79
90
  return (
80
- <>
81
- <SVG
82
- xmlnsXlink="http://www.w3.org/1999/xlink"
83
- viewBox="0 0 0 0"
84
- width="0"
85
- height="0"
86
- focusable="false"
87
- role="none"
88
- style={ {
89
- visibility: 'hidden',
90
- position: 'absolute',
91
- left: '-9999px',
92
- overflow: 'hidden',
93
- } }
94
- >
95
- <defs>
96
- <filter id={ id }>
97
- <feColorMatrix
98
- // Use sRGB instead of linearRGB so transparency looks correct.
99
- colorInterpolationFilters="sRGB"
100
- type="matrix"
101
- // Use perceptual brightness to convert to grayscale.
102
- values="
103
- .299 .587 .114 0 0
104
- .299 .587 .114 0 0
105
- .299 .587 .114 0 0
106
- .299 .587 .114 0 0
107
- "
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( ' ' ) }
108
134
  />
109
- <feComponentTransfer
110
- // Use sRGB instead of linearRGB to be consistent with how CSS gradients work.
111
- colorInterpolationFilters="sRGB"
112
- >
113
- <feFuncR
114
- type="table"
115
- tableValues={ values.r.join( ' ' ) }
116
- />
117
- <feFuncG
118
- type="table"
119
- tableValues={ values.g.join( ' ' ) }
120
- />
121
- <feFuncB
122
- type="table"
123
- tableValues={ values.b.join( ' ' ) }
124
- />
125
- <feFuncA
126
- type="table"
127
- tableValues={ values.a.join( ' ' ) }
128
- />
129
- </feComponentTransfer>
130
- <feComposite
131
- // Re-mask the image with the original transparency since the feColorMatrix above loses that information.
132
- in2="SourceGraphic"
133
- operator="in"
135
+ <feFuncA
136
+ type="table"
137
+ tableValues={ values.a.join( ' ' ) }
134
138
  />
135
- </filter>
136
- </defs>
137
- </SVG>
138
- <style dangerouslySetInnerHTML={ { __html: stylesheet } } />
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
+ /**
152
+ * SVG and stylesheet needed for rendering the duotone filter.
153
+ *
154
+ * @param {Object} props Duotone props.
155
+ * @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.
158
+ *
159
+ * @return {WPElement} Duotone element.
160
+ */
161
+ function InlineDuotone( { selector, id, values } ) {
162
+ return (
163
+ <>
164
+ <DuotoneFilter id={ id } values={ values } />
165
+ <DuotoneStylesheet id={ id } selector={ selector } />
139
166
  </>
140
167
  );
141
168
  }
@@ -321,7 +348,7 @@ const withDuotoneStyles = createHigherOrderComponent(
321
348
  <>
322
349
  { element &&
323
350
  createPortal(
324
- <DuotoneFilter
351
+ <InlineDuotone
325
352
  selector={ selectorsGroup }
326
353
  id={ id }
327
354
  values={ getValuesFromColors( values ) }
@@ -335,6 +362,15 @@ const withDuotoneStyles = createHigherOrderComponent(
335
362
  'withDuotoneStyles'
336
363
  );
337
364
 
365
+ export function PresetDuotoneFilter( { preset } ) {
366
+ return (
367
+ <DuotoneFilter
368
+ id={ `wp-duotone-${ preset.slug }` }
369
+ values={ getValuesFromColors( preset.colors ) }
370
+ />
371
+ );
372
+ }
373
+
338
374
  addFilter(
339
375
  'blocks.registerBlockType',
340
376
  'core/editor/duotone/add-attributes',
@@ -19,3 +19,4 @@ export { getBorderClassesAndStyles, useBorderProps } from './use-border-props';
19
19
  export { getColorClassesAndStyles, useColorProps } from './use-color-props';
20
20
  export { getSpacingClassesAndStyles } from './use-spacing-props';
21
21
  export { useCachedTruthy } from './use-cached-truthy';
22
+ export { PresetDuotoneFilter } from './duotone';
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import './hooks';
5
5
  export {
6
+ PresetDuotoneFilter as __unstablePresetDuotoneFilter,
6
7
  getBorderClassesAndStyles as __experimentalGetBorderClassesAndStyles,
7
8
  useBorderProps as __experimentalUseBorderProps,
8
9
  getColorClassesAndStyles as __experimentalGetColorClassesAndStyles,
@@ -22,6 +22,11 @@ import { __, _n, sprintf } from '@wordpress/i18n';
22
22
  import { create, insert, remove, toHTMLString } from '@wordpress/rich-text';
23
23
  import deprecated from '@wordpress/deprecated';
24
24
 
25
+ /**
26
+ * Internal dependencies
27
+ */
28
+ import { mapRichTextSettings } from './utils';
29
+
25
30
  /**
26
31
  * Action which will insert a default block insert action if there
27
32
  * are no other blocks at the root of the editor. This action should be used
@@ -667,19 +672,6 @@ export const synchronizeTemplate = () => ( { select, dispatch } ) => {
667
672
  dispatch.resetBlocks( updatedBlockList );
668
673
  };
669
674
 
670
- function mapRichTextSettings( attributeDefinition ) {
671
- const {
672
- multiline: multilineTag,
673
- __unstableMultilineWrapperTags: multilineWrapperTags,
674
- __unstablePreserveWhiteSpace: preserveWhiteSpace,
675
- } = attributeDefinition;
676
- return {
677
- multilineTag,
678
- multilineWrapperTags,
679
- preserveWhiteSpace,
680
- };
681
- }
682
-
683
675
  /**
684
676
  * Delete the current selection.
685
677
  *
@@ -33,6 +33,12 @@ import { Platform } from '@wordpress/element';
33
33
  import { applyFilters } from '@wordpress/hooks';
34
34
  import { symbol } from '@wordpress/icons';
35
35
  import { __ } from '@wordpress/i18n';
36
+ import { create, remove, toHTMLString } from '@wordpress/rich-text';
37
+
38
+ /**
39
+ * Internal dependencies
40
+ */
41
+ import { mapRichTextSettings } from './utils';
36
42
 
37
43
  /**
38
44
  * A block selection object.
@@ -926,6 +932,25 @@ export function __unstableIsFullySelected( state ) {
926
932
  );
927
933
  }
928
934
 
935
+ /**
936
+ * Returns true if the selection is collapsed.
937
+ *
938
+ * @param {Object} state Editor state.
939
+ *
940
+ * @return {boolean} Whether the selection is collapsed.
941
+ */
942
+ export function __unstableIsSelectionCollapsed( state ) {
943
+ const selectionAnchor = getSelectionStart( state );
944
+ const selectionFocus = getSelectionEnd( state );
945
+ return (
946
+ !! selectionAnchor &&
947
+ !! selectionFocus &&
948
+ selectionAnchor.clientId === selectionFocus.clientId &&
949
+ selectionAnchor.attributeKey === selectionFocus.attributeKey &&
950
+ selectionAnchor.offset === selectionFocus.offset
951
+ );
952
+ }
953
+
929
954
  /**
930
955
  * Check whether the selection is mergeable.
931
956
  *
@@ -1004,6 +1029,107 @@ export function __unstableIsSelectionMergeable( state, isForward ) {
1004
1029
  return blocksToMerge && blocksToMerge.length;
1005
1030
  }
1006
1031
 
1032
+ /**
1033
+ * Get partial selected blocks with their content updated
1034
+ * based on the selection.
1035
+ *
1036
+ * @param {Object} state Editor state.
1037
+ *
1038
+ * @return {Object[]} Updated partial selected blocks.
1039
+ */
1040
+ export const __unstableGetSelectedBlocksWithPartialSelection = ( state ) => {
1041
+ const selectionAnchor = getSelectionStart( state );
1042
+ const selectionFocus = getSelectionEnd( state );
1043
+
1044
+ if ( selectionAnchor.clientId === selectionFocus.clientId ) {
1045
+ return EMPTY_ARRAY;
1046
+ }
1047
+
1048
+ // Can't split if the selection is not set.
1049
+ if (
1050
+ ! selectionAnchor.attributeKey ||
1051
+ ! selectionFocus.attributeKey ||
1052
+ typeof selectionAnchor.offset === 'undefined' ||
1053
+ typeof selectionFocus.offset === 'undefined'
1054
+ ) {
1055
+ return EMPTY_ARRAY;
1056
+ }
1057
+
1058
+ const anchorRootClientId = getBlockRootClientId(
1059
+ state,
1060
+ selectionAnchor.clientId
1061
+ );
1062
+ const focusRootClientId = getBlockRootClientId(
1063
+ state,
1064
+ selectionFocus.clientId
1065
+ );
1066
+
1067
+ // It's not splittable if the selection doesn't start and end in the same
1068
+ // block list. Maybe in the future it should be allowed.
1069
+ if ( anchorRootClientId !== focusRootClientId ) {
1070
+ return EMPTY_ARRAY;
1071
+ }
1072
+
1073
+ const blockOrder = getBlockOrder( state, anchorRootClientId );
1074
+ const anchorIndex = blockOrder.indexOf( selectionAnchor.clientId );
1075
+ const focusIndex = blockOrder.indexOf( selectionFocus.clientId );
1076
+
1077
+ // Reassign selection start and end based on order.
1078
+ const [ selectionStart, selectionEnd ] =
1079
+ anchorIndex > focusIndex
1080
+ ? [ selectionFocus, selectionAnchor ]
1081
+ : [ selectionAnchor, selectionFocus ];
1082
+
1083
+ const blockA = getBlock( state, selectionStart.clientId );
1084
+ const blockAType = getBlockType( blockA.name );
1085
+
1086
+ const blockB = getBlock( state, selectionEnd.clientId );
1087
+ const blockBType = getBlockType( blockB.name );
1088
+
1089
+ const htmlA = blockA.attributes[ selectionStart.attributeKey ];
1090
+ const htmlB = blockB.attributes[ selectionEnd.attributeKey ];
1091
+
1092
+ const attributeDefinitionA =
1093
+ blockAType.attributes[ selectionStart.attributeKey ];
1094
+ const attributeDefinitionB =
1095
+ blockBType.attributes[ selectionEnd.attributeKey ];
1096
+
1097
+ let valueA = create( {
1098
+ html: htmlA,
1099
+ ...mapRichTextSettings( attributeDefinitionA ),
1100
+ } );
1101
+ let valueB = create( {
1102
+ html: htmlB,
1103
+ ...mapRichTextSettings( attributeDefinitionB ),
1104
+ } );
1105
+
1106
+ valueA = remove( valueA, 0, selectionStart.offset );
1107
+ valueB = remove( valueB, selectionEnd.offset, valueB.text.length );
1108
+
1109
+ return [
1110
+ {
1111
+ ...blockA,
1112
+ attributes: {
1113
+ ...blockA.attributes,
1114
+ [ selectionStart.attributeKey ]: toHTMLString( {
1115
+ value: valueA,
1116
+ ...mapRichTextSettings( attributeDefinitionA ),
1117
+ } ),
1118
+ },
1119
+ },
1120
+ {
1121
+ ...blockB,
1122
+ attributes: {
1123
+ ...blockB.attributes,
1124
+ [ selectionEnd.attributeKey ]: toHTMLString( {
1125
+ value: valueB,
1126
+ ...mapRichTextSettings( attributeDefinitionB ),
1127
+ } ),
1128
+ },
1129
+ },
1130
+ ];
1131
+ };
1132
+
1007
1133
  /**
1008
1134
  * Returns an array containing all block client IDs in the editor in the order
1009
1135
  * they appear. Optionally accepts a root client ID of the block list for which
@@ -1569,26 +1695,6 @@ export function canMoveBlocks( state, clientIds, rootClientId = null ) {
1569
1695
  );
1570
1696
  }
1571
1697
 
1572
- /**
1573
- * Determines if the given block is allowed to be edited.
1574
- *
1575
- * @param {Object} state Editor state.
1576
- * @param {string} clientId The block client Id.
1577
- *
1578
- * @return {boolean} Whether the given block is allowed to be edited.
1579
- */
1580
- export function canEditBlock( state, clientId ) {
1581
- const attributes = getBlockAttributes( state, clientId );
1582
- if ( attributes === null ) {
1583
- return true;
1584
- }
1585
-
1586
- const { lock } = attributes;
1587
-
1588
- // When the edit is true, we cannot edit the block.
1589
- return ! lock?.edit;
1590
- }
1591
-
1592
1698
  /**
1593
1699
  * Determines if the given block type can be locked/unlocked by a user.
1594
1700
  *
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Helper function that maps attribute definition properties to the
3
+ * ones used by RichText utils like `create, toHTMLString, etc..`.
4
+ *
5
+ * @param {Object} attributeDefinition A block's attribute definition object.
6
+ * @return {Object} The mapped object.
7
+ */
8
+ export function mapRichTextSettings( attributeDefinition ) {
9
+ const {
10
+ multiline: multilineTag,
11
+ __unstableMultilineWrapperTags: multilineWrapperTags,
12
+ __unstablePreserveWhiteSpace: preserveWhiteSpace,
13
+ } = attributeDefinition;
14
+ return {
15
+ multilineTag,
16
+ multilineWrapperTags,
17
+ preserveWhiteSpace,
18
+ };
19
+ }