@wordpress/block-editor 15.8.1-next.16d95556a.0 → 15.8.1-next.dc3f6d3c1.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 (169) hide show
  1. package/build/components/block-inspector/edit-contents.js +19 -23
  2. package/build/components/block-inspector/edit-contents.js.map +3 -3
  3. package/build/components/block-inspector/index.js +7 -1
  4. package/build/components/block-inspector/index.js.map +2 -2
  5. package/build/components/block-list/block.js +4 -0
  6. package/build/components/block-list/block.js.map +2 -2
  7. package/build/components/block-list/use-block-props/index.js +3 -1
  8. package/build/components/block-list/use-block-props/index.js.map +2 -2
  9. package/build/components/block-list/use-block-props/use-is-hovered.js +16 -10
  10. package/build/components/block-list/use-block-props/use-is-hovered.js.map +2 -2
  11. package/build/components/block-settings-menu-controls/edit-section-menu-item.js +64 -0
  12. package/build/components/block-settings-menu-controls/edit-section-menu-item.js.map +7 -0
  13. package/build/components/block-settings-menu-controls/index.js +8 -0
  14. package/build/components/block-settings-menu-controls/index.js.map +2 -2
  15. package/build/components/block-toolbar/block-toolbar-icon.js +9 -9
  16. package/build/components/block-toolbar/block-toolbar-icon.js.map +2 -2
  17. package/build/components/block-variation-transforms/index.js +32 -5
  18. package/build/components/block-variation-transforms/index.js.map +2 -2
  19. package/build/components/border-radius-control/single-input-control.js +1 -0
  20. package/build/components/border-radius-control/single-input-control.js.map +2 -2
  21. package/build/components/content-only-controls/index.js +263 -0
  22. package/build/components/content-only-controls/index.js.map +7 -0
  23. package/build/components/content-only-controls/link/index.js +204 -0
  24. package/build/components/content-only-controls/link/index.js.map +7 -0
  25. package/build/components/content-only-controls/media/index.js +264 -0
  26. package/build/components/content-only-controls/media/index.js.map +7 -0
  27. package/build/components/content-only-controls/plain-text/index.js +68 -0
  28. package/build/components/content-only-controls/plain-text/index.js.map +7 -0
  29. package/build/components/content-only-controls/rich-text/index.js +197 -0
  30. package/build/components/content-only-controls/rich-text/index.js.map +7 -0
  31. package/build/components/content-only-controls/use-inspector-popover-placement.js +41 -0
  32. package/build/components/content-only-controls/use-inspector-popover-placement.js.map +7 -0
  33. package/build/components/inserter/media-tab/media-tab.js +1 -33
  34. package/build/components/inserter/media-tab/media-tab.js.map +3 -3
  35. package/build/components/inspector-controls-tabs/content-tab.js +6 -2
  36. package/build/components/inspector-controls-tabs/content-tab.js.map +3 -3
  37. package/build/components/inspector-controls-tabs/index.js +7 -1
  38. package/build/components/inspector-controls-tabs/index.js.map +2 -2
  39. package/build/components/list-view/block-select-button.js +11 -4
  40. package/build/components/list-view/block-select-button.js.map +2 -2
  41. package/build/components/media-placeholder/index.js +1 -31
  42. package/build/components/media-placeholder/index.js.map +3 -3
  43. package/build/components/media-replace-flow/index.js +4 -30
  44. package/build/components/media-replace-flow/index.js.map +3 -3
  45. package/build/components/use-block-display-information/index.js +21 -1
  46. package/build/components/use-block-display-information/index.js.map +3 -3
  47. package/build/hooks/block-bindings.js +52 -61
  48. package/build/hooks/block-bindings.js.map +3 -3
  49. package/build/hooks/use-content-only-section-edit.js +67 -0
  50. package/build/hooks/use-content-only-section-edit.js.map +7 -0
  51. package/build/layouts/constrained.js +2 -2
  52. package/build/layouts/constrained.js.map +2 -2
  53. package/build/private-apis.js +2 -3
  54. package/build/private-apis.js.map +3 -3
  55. package/build/store/private-keys.js +3 -0
  56. package/build/store/private-keys.js.map +2 -2
  57. package/build/store/private-selectors.js +1 -2
  58. package/build/store/private-selectors.js.map +2 -2
  59. package/build/store/reducer.js +1 -2
  60. package/build/store/reducer.js.map +2 -2
  61. package/build/utils/fit-text-utils.js +9 -1
  62. package/build/utils/fit-text-utils.js.map +2 -2
  63. package/build-module/components/block-inspector/edit-contents.js +9 -23
  64. package/build-module/components/block-inspector/edit-contents.js.map +2 -2
  65. package/build-module/components/block-inspector/index.js +7 -1
  66. package/build-module/components/block-inspector/index.js.map +2 -2
  67. package/build-module/components/block-list/block.js +4 -0
  68. package/build-module/components/block-list/block.js.map +2 -2
  69. package/build-module/components/block-list/use-block-props/index.js +3 -1
  70. package/build-module/components/block-list/use-block-props/index.js.map +2 -2
  71. package/build-module/components/block-list/use-block-props/use-is-hovered.js +16 -10
  72. package/build-module/components/block-list/use-block-props/use-is-hovered.js.map +2 -2
  73. package/build-module/components/block-settings-menu-controls/edit-section-menu-item.js +29 -0
  74. package/build-module/components/block-settings-menu-controls/edit-section-menu-item.js.map +7 -0
  75. package/build-module/components/block-settings-menu-controls/index.js +8 -0
  76. package/build-module/components/block-settings-menu-controls/index.js.map +2 -2
  77. package/build-module/components/block-toolbar/block-toolbar-icon.js +10 -10
  78. package/build-module/components/block-toolbar/block-toolbar-icon.js.map +2 -2
  79. package/build-module/components/block-variation-transforms/index.js +32 -5
  80. package/build-module/components/block-variation-transforms/index.js.map +2 -2
  81. package/build-module/components/border-radius-control/single-input-control.js +1 -0
  82. package/build-module/components/border-radius-control/single-input-control.js.map +2 -2
  83. package/build-module/components/content-only-controls/index.js +237 -0
  84. package/build-module/components/content-only-controls/index.js.map +7 -0
  85. package/build-module/components/content-only-controls/link/index.js +172 -0
  86. package/build-module/components/content-only-controls/link/index.js.map +7 -0
  87. package/build-module/components/content-only-controls/media/index.js +243 -0
  88. package/build-module/components/content-only-controls/media/index.js.map +7 -0
  89. package/build-module/components/content-only-controls/plain-text/index.js +50 -0
  90. package/build-module/components/content-only-controls/plain-text/index.js.map +7 -0
  91. package/build-module/components/content-only-controls/rich-text/index.js +174 -0
  92. package/build-module/components/content-only-controls/rich-text/index.js.map +7 -0
  93. package/build-module/components/content-only-controls/use-inspector-popover-placement.js +16 -0
  94. package/build-module/components/content-only-controls/use-inspector-popover-placement.js.map +7 -0
  95. package/build-module/components/inserter/media-tab/media-tab.js +2 -34
  96. package/build-module/components/inserter/media-tab/media-tab.js.map +2 -2
  97. package/build-module/components/inspector-controls-tabs/content-tab.js +7 -3
  98. package/build-module/components/inspector-controls-tabs/content-tab.js.map +2 -2
  99. package/build-module/components/inspector-controls-tabs/index.js +7 -1
  100. package/build-module/components/inspector-controls-tabs/index.js.map +2 -2
  101. package/build-module/components/list-view/block-select-button.js +18 -5
  102. package/build-module/components/list-view/block-select-button.js.map +2 -2
  103. package/build-module/components/media-placeholder/index.js +1 -31
  104. package/build-module/components/media-placeholder/index.js.map +2 -2
  105. package/build-module/components/media-replace-flow/index.js +4 -30
  106. package/build-module/components/media-replace-flow/index.js.map +2 -2
  107. package/build-module/components/use-block-display-information/index.js +21 -1
  108. package/build-module/components/use-block-display-information/index.js.map +3 -3
  109. package/build-module/hooks/block-bindings.js +57 -62
  110. package/build-module/hooks/block-bindings.js.map +2 -2
  111. package/build-module/hooks/use-content-only-section-edit.js +46 -0
  112. package/build-module/hooks/use-content-only-section-edit.js.map +7 -0
  113. package/build-module/layouts/constrained.js +2 -2
  114. package/build-module/layouts/constrained.js.map +2 -2
  115. package/build-module/private-apis.js +3 -3
  116. package/build-module/private-apis.js.map +2 -2
  117. package/build-module/store/private-keys.js +2 -0
  118. package/build-module/store/private-keys.js.map +2 -2
  119. package/build-module/store/private-selectors.js +1 -2
  120. package/build-module/store/private-selectors.js.map +2 -2
  121. package/build-module/store/reducer.js +1 -2
  122. package/build-module/store/reducer.js.map +2 -2
  123. package/build-module/utils/fit-text-utils.js +9 -1
  124. package/build-module/utils/fit-text-utils.js.map +2 -2
  125. package/build-style/style-rtl.css +132 -0
  126. package/build-style/style.css +132 -0
  127. package/package.json +37 -37
  128. package/src/components/block-inspector/edit-contents.js +10 -29
  129. package/src/components/block-inspector/index.js +4 -2
  130. package/src/components/block-list/block.js +6 -0
  131. package/src/components/block-list/use-block-props/index.js +3 -1
  132. package/src/components/block-list/use-block-props/use-is-hovered.js +24 -12
  133. package/src/components/block-settings-menu-controls/edit-section-menu-item.js +39 -0
  134. package/src/components/block-settings-menu-controls/index.js +7 -0
  135. package/src/components/block-toolbar/block-toolbar-icon.js +14 -10
  136. package/src/components/block-variation-transforms/index.js +96 -35
  137. package/src/components/border-radius-control/single-input-control.js +1 -0
  138. package/src/components/content-only-controls/index.js +296 -0
  139. package/src/components/content-only-controls/link/index.js +195 -0
  140. package/src/components/content-only-controls/link/styles.scss +23 -0
  141. package/src/components/content-only-controls/media/index.js +285 -0
  142. package/src/components/content-only-controls/media/styles.scss +47 -0
  143. package/src/components/content-only-controls/plain-text/index.js +49 -0
  144. package/src/components/content-only-controls/rich-text/index.js +193 -0
  145. package/src/components/content-only-controls/rich-text/styles.scss +24 -0
  146. package/src/components/content-only-controls/styles.scss +35 -0
  147. package/src/components/content-only-controls/use-inspector-popover-placement.js +19 -0
  148. package/src/components/inserter/media-tab/media-tab.js +2 -44
  149. package/src/components/inspector-controls-tabs/content-tab.js +12 -4
  150. package/src/components/inspector-controls-tabs/index.js +4 -1
  151. package/src/components/list-view/block-select-button.js +37 -24
  152. package/src/components/media-placeholder/index.js +1 -41
  153. package/src/components/media-replace-flow/index.js +3 -39
  154. package/src/components/use-block-display-information/index.js +30 -2
  155. package/src/hooks/block-bindings.js +71 -82
  156. package/src/hooks/use-content-only-section-edit.js +63 -0
  157. package/src/layouts/constrained.js +8 -2
  158. package/src/private-apis.js +2 -2
  159. package/src/store/private-keys.js +1 -0
  160. package/src/store/private-selectors.js +1 -2
  161. package/src/store/reducer.js +0 -3
  162. package/src/store/test/reducer.js +7 -17
  163. package/src/style.scss +1 -0
  164. package/src/utils/fit-text-utils.js +19 -1
  165. package/build/components/media-upload-modal/index.js +0 -29
  166. package/build/components/media-upload-modal/index.js.map +0 -7
  167. package/build-module/components/media-upload-modal/index.js +0 -8
  168. package/build-module/components/media-upload-modal/index.js.map +0 -7
  169. package/src/components/media-upload-modal/index.js +0 -18
@@ -4,7 +4,7 @@
4
4
  import { ToolbarButton } from '@wordpress/components';
5
5
  import { __ } from '@wordpress/i18n';
6
6
  import { useSelect } from '@wordpress/data';
7
- import { copy } from '@wordpress/icons';
7
+ import { copy, symbol } from '@wordpress/icons';
8
8
  import { getBlockType, store as blocksStore } from '@wordpress/blocks';
9
9
  import { store as preferencesStore } from '@wordpress/preferences';
10
10
 
@@ -24,7 +24,6 @@ function getBlockIconVariant( { select, clientIds } ) {
24
24
  getBlockName,
25
25
  getBlockAttributes,
26
26
  getBlockParentsByBlockName,
27
- isSectionBlock,
28
27
  canRemoveBlocks,
29
28
  getTemplateLock,
30
29
  getBlockEditingMode,
@@ -40,8 +39,8 @@ function getBlockIconVariant( { select, clientIds } ) {
40
39
  const blockName = isSingleBlock && getBlockName( clientIds[ 0 ] );
41
40
  const hasBlockStyles =
42
41
  isSingleBlock && !! getBlockStyles( blockName )?.length;
43
- const isSectionInSelection = clientIds.some( ( id ) =>
44
- isSectionBlock( id )
42
+ const hasPatternNameInSelection = clientIds.some(
43
+ ( id ) => !! getBlockAttributes( id )?.metadata?.patternName
45
44
  );
46
45
  const hasPatternOverrides = clientIds.every( ( clientId ) =>
47
46
  hasPatternOverridesDefaultBinding(
@@ -59,7 +58,7 @@ function getBlockIconVariant( { select, clientIds } ) {
59
58
  getBlockEditingMode( clientIds[ 0 ] ) === 'default';
60
59
  const _hideTransformsForSections =
61
60
  window?.__experimentalContentOnlyPatternInsertion &&
62
- isSectionInSelection;
61
+ hasPatternNameInSelection;
63
62
  const _showBlockSwitcher =
64
63
  ! _hideTransformsForSections &&
65
64
  isDefaultEditingMode &&
@@ -81,19 +80,24 @@ function getBlockIcon( { select, clientIds } ) {
81
80
  const { getBlockName, getBlockAttributes } = unlock(
82
81
  select( blockEditorStore )
83
82
  );
84
- const { getActiveBlockVariation } = select( blocksStore );
85
83
 
86
84
  const _isSingleBlock = clientIds.length === 1;
87
85
  const firstClientId = clientIds[ 0 ];
86
+ const blockAttributes = getBlockAttributes( firstClientId );
87
+ if (
88
+ _isSingleBlock &&
89
+ blockAttributes?.metadata?.patternName &&
90
+ window?.__experimentalContentOnlyPatternInsertion
91
+ ) {
92
+ return symbol;
93
+ }
88
94
 
89
95
  const blockName = getBlockName( firstClientId );
90
96
  const blockType = getBlockType( blockName );
91
97
 
92
98
  if ( _isSingleBlock ) {
93
- const match = getActiveBlockVariation(
94
- blockName,
95
- getBlockAttributes( firstClientId )
96
- );
99
+ const { getActiveBlockVariation } = select( blocksStore );
100
+ const match = getActiveBlockVariation( blockName, blockAttributes );
97
101
  return match?.icon || blockType?.icon;
98
102
  }
99
103
 
@@ -147,41 +147,102 @@ function VariationsToggleGroupControl( {
147
147
 
148
148
  function __experimentalBlockVariationTransforms( { blockClientId } ) {
149
149
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
150
- const { activeBlockVariation, variations, isContentOnly, isSection } =
151
- useSelect(
152
- ( select ) => {
153
- const { getActiveBlockVariation, getBlockVariations } =
154
- select( blocksStore );
155
-
156
- const {
157
- getBlockName,
158
- getBlockAttributes,
159
- getBlockEditingMode,
160
- isSectionBlock,
161
- } = unlock( select( blockEditorStore ) );
162
-
163
- const name = blockClientId && getBlockName( blockClientId );
164
-
165
- const { hasContentRoleAttribute } = unlock(
166
- select( blocksStore )
167
- );
168
- const isContentBlock = hasContentRoleAttribute( name );
169
-
170
- return {
171
- activeBlockVariation: getActiveBlockVariation(
172
- name,
173
- getBlockAttributes( blockClientId ),
174
- 'transform'
175
- ),
176
- variations: name && getBlockVariations( name, 'transform' ),
177
- isContentOnly:
178
- getBlockEditingMode( blockClientId ) ===
179
- 'contentOnly' && ! isContentBlock,
180
- isSection: isSectionBlock( blockClientId ),
181
- };
182
- },
183
- [ blockClientId ]
184
- );
150
+ const {
151
+ activeBlockVariation,
152
+ unfilteredVariations,
153
+ blockName,
154
+ isContentOnly,
155
+ isSection,
156
+ } = useSelect(
157
+ ( select ) => {
158
+ const { getActiveBlockVariation, getBlockVariations } =
159
+ select( blocksStore );
160
+
161
+ const {
162
+ getBlockName,
163
+ getBlockAttributes,
164
+ getBlockEditingMode,
165
+ isSectionBlock,
166
+ } = unlock( select( blockEditorStore ) );
167
+
168
+ const name = blockClientId && getBlockName( blockClientId );
169
+
170
+ const { hasContentRoleAttribute } = unlock( select( blocksStore ) );
171
+ const isContentBlock = hasContentRoleAttribute( name );
172
+
173
+ return {
174
+ activeBlockVariation: getActiveBlockVariation(
175
+ name,
176
+ getBlockAttributes( blockClientId ),
177
+ 'transform'
178
+ ),
179
+ unfilteredVariations:
180
+ name && getBlockVariations( name, 'transform' ),
181
+ blockName: name,
182
+ isContentOnly:
183
+ getBlockEditingMode( blockClientId ) === 'contentOnly' &&
184
+ ! isContentBlock,
185
+ isSection: isSectionBlock( blockClientId ),
186
+ };
187
+ },
188
+ [ blockClientId ]
189
+ );
190
+
191
+ /*
192
+ * Hack for WordPress 6.9
193
+ *
194
+ * The Stretchy blocks shipped in 6.9 were ultimately
195
+ * implemented as block variations of the base types Paragraph
196
+ * and Heading. See #73056 for discussion and trade-offs.
197
+ *
198
+ * The main drawback of this choice is that the Variations API
199
+ * doesn't offer enough control over how prominent and how tied
200
+ * to the base type a variation should be.
201
+ *
202
+ * In order to ship these new "blocks" with an acceptable UX,
203
+ * we need two hacks until the Variations API is improved:
204
+ *
205
+ * - Don't show the variations switcher in the block inspector
206
+ * for Paragraph, Heading, Stretchy Paragraph and Stretchy
207
+ * Heading (implemented below). Transformations are still
208
+ * available in the block switcher.
209
+ *
210
+ * - Move the stretchy variations to the end of the core blocks
211
+ * list in the block inserter (implemented in
212
+ * getInserterItems in #73056).
213
+ */
214
+ const variations = useMemo( () => {
215
+ if ( blockName === 'core/paragraph' ) {
216
+ // Always hide options when active variation is stretchy, but
217
+ // ensure that there are no third-party variations before doing the
218
+ // same elsewhere.
219
+ if (
220
+ activeBlockVariation?.name === 'stretchy-paragraph' ||
221
+ unfilteredVariations.every( ( v ) =>
222
+ [ 'paragraph', 'stretchy-paragraph' ].includes( v.name )
223
+ )
224
+ ) {
225
+ return [];
226
+ }
227
+ // If there are other variations, only hide the stretchy one.
228
+ return unfilteredVariations.filter(
229
+ ( v ) => v.name !== 'stretchy-paragraph'
230
+ );
231
+ } else if ( blockName === 'core/heading' ) {
232
+ if (
233
+ activeBlockVariation?.name === 'stretchy-heading' ||
234
+ unfilteredVariations.every( ( v ) =>
235
+ [ 'heading', 'stretchy-heading' ].includes( v.name )
236
+ )
237
+ ) {
238
+ return [];
239
+ }
240
+ return unfilteredVariations.filter(
241
+ ( v ) => v.name !== 'stretchy-heading'
242
+ );
243
+ }
244
+ return unfilteredVariations;
245
+ }, [ activeBlockVariation?.name, blockName, unfilteredVariations ] );
185
246
 
186
247
  const selectedValue = activeBlockVariation?.name;
187
248
 
@@ -74,6 +74,7 @@ export default function SingleInputControl( {
74
74
  const onChangeUnit = ( next ) => {
75
75
  const newUnits = { ...selectedUnits };
76
76
  if ( corner === 'all' ) {
77
+ newUnits.flat = next;
77
78
  newUnits.topLeft = next;
78
79
  newUnits.topRight = next;
79
80
  newUnits.bottomLeft = next;
@@ -0,0 +1,296 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { store as blocksStore } from '@wordpress/blocks';
5
+ import {
6
+ __experimentalToolsPanel as ToolsPanel,
7
+ __experimentalHStack as HStack,
8
+ Icon,
9
+ Navigator,
10
+ } from '@wordpress/components';
11
+ import { useDispatch, useSelect } from '@wordpress/data';
12
+ import { __ } from '@wordpress/i18n';
13
+ import { arrowLeft, arrowRight } from '@wordpress/icons';
14
+
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ import { unlock } from '../../lock-unlock';
19
+ import { store as blockEditorStore } from '../../store';
20
+ import BlockIcon from '../block-icon';
21
+ import useBlockDisplayTitle from '../block-title/use-block-display-title';
22
+ import useBlockDisplayInformation from '../use-block-display-information';
23
+ import { useInspectorPopoverPlacement } from './use-inspector-popover-placement';
24
+
25
+ // controls
26
+ import PlainText from './plain-text';
27
+ import RichText from './rich-text';
28
+ import Media from './media';
29
+ import Link from './link';
30
+
31
+ const controls = {
32
+ PlainText,
33
+ RichText,
34
+ Media,
35
+ Link,
36
+ };
37
+
38
+ function BlockAttributeToolsPanelItem( {
39
+ clientId,
40
+ control,
41
+ blockType,
42
+ attributeValues,
43
+ } ) {
44
+ const { updateBlockAttributes } = useDispatch( blockEditorStore );
45
+ const ControlComponent = controls[ control.type ];
46
+
47
+ if ( ! ControlComponent ) {
48
+ return null;
49
+ }
50
+
51
+ return (
52
+ <ControlComponent
53
+ clientId={ clientId }
54
+ control={ control }
55
+ blockType={ blockType }
56
+ attributeValues={ attributeValues }
57
+ updateAttributes={ ( attributes ) =>
58
+ updateBlockAttributes( clientId, attributes )
59
+ }
60
+ />
61
+ );
62
+ }
63
+
64
+ function BlockFields( { clientId } ) {
65
+ const { attributes, blockType } = useSelect(
66
+ ( select ) => {
67
+ const { getBlockAttributes, getBlockName } =
68
+ select( blockEditorStore );
69
+ const { getBlockType } = select( blocksStore );
70
+ const blockName = getBlockName( clientId );
71
+ return {
72
+ attributes: getBlockAttributes( clientId ),
73
+ blockType: getBlockType( blockName ),
74
+ };
75
+ },
76
+ [ clientId ]
77
+ );
78
+
79
+ const blockTitle = useBlockDisplayTitle( {
80
+ clientId,
81
+ context: 'list-view',
82
+ } );
83
+ const blockInformation = useBlockDisplayInformation( clientId );
84
+ const popoverPlacementProps = useInspectorPopoverPlacement();
85
+
86
+ if ( ! blockType?.fields?.length ) {
87
+ // TODO - we might still want to show a placeholder for blocks with no fields.
88
+ // for example, a way to select the block.
89
+ return null;
90
+ }
91
+
92
+ return (
93
+ <ToolsPanel
94
+ label={
95
+ <HStack spacing={ 1 }>
96
+ <BlockIcon icon={ blockInformation?.icon } />
97
+ <div>{ blockTitle }</div>
98
+ </HStack>
99
+ }
100
+ panelId={ clientId }
101
+ dropdownMenuProps={ popoverPlacementProps }
102
+ >
103
+ { blockType?.fields?.map( ( field, index ) => (
104
+ <BlockAttributeToolsPanelItem
105
+ key={ `${ clientId }/${ index }` }
106
+ clientId={ clientId }
107
+ control={ field }
108
+ blockType={ blockType }
109
+ attributeValues={ attributes }
110
+ />
111
+ ) ) }
112
+ </ToolsPanel>
113
+ );
114
+ }
115
+
116
+ function DrillDownButton( { clientId } ) {
117
+ const blockTitle = useBlockDisplayTitle( {
118
+ clientId,
119
+ context: 'list-view',
120
+ } );
121
+ const blockInformation = useBlockDisplayInformation( clientId );
122
+ return (
123
+ <div className="block-editor-content-only-controls__button-panel">
124
+ <Navigator.Button
125
+ path={ `/${ clientId }` }
126
+ className="block-editor-content-only-controls__drill-down-button"
127
+ >
128
+ <HStack expanded justify="space-between">
129
+ <HStack justify="flex-start" spacing={ 1 }>
130
+ <BlockIcon icon={ blockInformation?.icon } />
131
+ <div>{ blockTitle }</div>
132
+ </HStack>
133
+ <Icon icon={ arrowRight } />
134
+ </HStack>
135
+ </Navigator.Button>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ function ContentOnlyControlsScreen( {
141
+ rootClientId,
142
+ contentClientIds,
143
+ parentClientIds,
144
+ isNested,
145
+ } ) {
146
+ const isRootContentBlock = useSelect(
147
+ ( select ) => {
148
+ const { getBlockName } = select( blockEditorStore );
149
+ const blockName = getBlockName( rootClientId );
150
+ const { hasContentRoleAttribute } = unlock( select( blocksStore ) );
151
+ return hasContentRoleAttribute( blockName );
152
+ },
153
+ [ rootClientId ]
154
+ );
155
+
156
+ if ( ! isRootContentBlock && ! contentClientIds.length ) {
157
+ return null;
158
+ }
159
+
160
+ return (
161
+ <>
162
+ { isNested && (
163
+ <div className="block-editor-content-only-controls__button-panel">
164
+ <Navigator.BackButton className="block-editor-content-only-controls__back-button">
165
+ <HStack expanded spacing={ 1 } justify="flex-start">
166
+ <Icon icon={ arrowLeft } />
167
+ <div>{ __( 'Back' ) }</div>
168
+ </HStack>
169
+ </Navigator.BackButton>
170
+ </div>
171
+ ) }
172
+ { isRootContentBlock && <BlockFields clientId={ rootClientId } /> }
173
+ { contentClientIds.map( ( clientId ) => {
174
+ if ( parentClientIds?.[ clientId ] ) {
175
+ return (
176
+ <DrillDownButton
177
+ key={ clientId }
178
+ clientId={ clientId }
179
+ />
180
+ );
181
+ }
182
+
183
+ return <BlockFields key={ clientId } clientId={ clientId } />;
184
+ } ) }
185
+ </>
186
+ );
187
+ }
188
+
189
+ export default function ContentOnlyControls( { rootClientId } ) {
190
+ const { updatedRootClientId, nestedContentClientIds, contentClientIds } =
191
+ useSelect(
192
+ ( select ) => {
193
+ const { getClientIdsOfDescendants, getBlockEditingMode } =
194
+ select( blockEditorStore );
195
+
196
+ // _nestedContentClientIds is for content blocks within 'drilldowns'.
197
+ // It's an object where the key is the parent clientId, and the element is
198
+ // an array of child clientIds whose controls are shown within the drilldown.
199
+ const _nestedContentClientIds = {};
200
+
201
+ // _contentClientIds is the list of contentClientIds for blocks being
202
+ // shown at the root level. Includes parent blocks that might have a drilldown,
203
+ // but not the children of those blocks.
204
+ const _contentClientIds = [];
205
+
206
+ // An array of all nested client ids. Used for ensuring blocks within drilldowns
207
+ // don't appear at the root level.
208
+ let allNestedClientIds = [];
209
+
210
+ // A flattened list of all content clientIds to arrange into the
211
+ // groups above.
212
+ const allContentClientIds = getClientIdsOfDescendants(
213
+ rootClientId
214
+ ).filter(
215
+ ( clientId ) =>
216
+ getBlockEditingMode( clientId ) === 'contentOnly'
217
+ );
218
+
219
+ for ( const clientId of allContentClientIds ) {
220
+ const childClientIds = getClientIdsOfDescendants(
221
+ clientId
222
+ ).filter(
223
+ ( childClientId ) =>
224
+ getBlockEditingMode( childClientId ) ===
225
+ 'contentOnly'
226
+ );
227
+
228
+ // If there's more than one child block, use a drilldown.
229
+ if (
230
+ childClientIds.length > 1 &&
231
+ ! allNestedClientIds.includes( clientId )
232
+ ) {
233
+ _nestedContentClientIds[ clientId ] = childClientIds;
234
+ allNestedClientIds = [
235
+ allNestedClientIds,
236
+ ...childClientIds,
237
+ ];
238
+ }
239
+
240
+ if ( ! allNestedClientIds.includes( clientId ) ) {
241
+ _contentClientIds.push( clientId );
242
+ }
243
+ }
244
+
245
+ // Avoid showing only one drilldown block at the root.
246
+ if (
247
+ _contentClientIds.length === 1 &&
248
+ Object.keys( _nestedContentClientIds ).length === 1
249
+ ) {
250
+ const onlyParentClientId = Object.keys(
251
+ _nestedContentClientIds
252
+ )[ 0 ];
253
+ return {
254
+ updatedRootClientId: onlyParentClientId,
255
+ contentClientIds:
256
+ _nestedContentClientIds[ onlyParentClientId ],
257
+ nestedContentClientIds: {},
258
+ };
259
+ }
260
+
261
+ return {
262
+ nestedContentClientIds: _nestedContentClientIds,
263
+ contentClientIds: _contentClientIds,
264
+ };
265
+ },
266
+ [ rootClientId ]
267
+ );
268
+
269
+ return (
270
+ <Navigator initialPath="/">
271
+ <Navigator.Screen
272
+ path="/"
273
+ className="block-editor-content-only-controls__screen"
274
+ >
275
+ <ContentOnlyControlsScreen
276
+ rootClientId={ updatedRootClientId ?? rootClientId }
277
+ contentClientIds={ contentClientIds }
278
+ parentClientIds={ nestedContentClientIds }
279
+ />
280
+ </Navigator.Screen>
281
+ { Object.keys( nestedContentClientIds ).map( ( clientId ) => (
282
+ <Navigator.Screen
283
+ key={ clientId }
284
+ path={ `/${ clientId }` }
285
+ className="block-editor-content-only-controls__screen"
286
+ >
287
+ <ContentOnlyControlsScreen
288
+ isNested
289
+ rootClientId={ clientId }
290
+ contentClientIds={ nestedContentClientIds[ clientId ] }
291
+ />
292
+ </Navigator.Screen>
293
+ ) ) }
294
+ </Navigator>
295
+ );
296
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Button,
6
+ Icon,
7
+ __experimentalToolsPanelItem as ToolsPanelItem,
8
+ __experimentalGrid as Grid,
9
+ Popover,
10
+ } from '@wordpress/components';
11
+ import { useMemo, useState } from '@wordpress/element';
12
+ import { __ } from '@wordpress/i18n';
13
+ import { link } from '@wordpress/icons';
14
+ import { prependHTTP } from '@wordpress/url';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import LinkControl from '../../link-control';
20
+ import { useInspectorPopoverPlacement } from '../use-inspector-popover-placement';
21
+
22
+ export const NEW_TAB_REL = 'noreferrer noopener';
23
+ export const NEW_TAB_TARGET = '_blank';
24
+ export const NOFOLLOW_REL = 'nofollow';
25
+
26
+ /**
27
+ * Updates the link attributes.
28
+ *
29
+ * @param {Object} attributes The current block attributes.
30
+ * @param {string} attributes.rel The current link rel attribute.
31
+ * @param {string} attributes.url The current link url.
32
+ * @param {boolean} attributes.opensInNewTab Whether the link should open in a new window.
33
+ * @param {boolean} attributes.nofollow Whether the link should be marked as nofollow.
34
+ */
35
+ export function getUpdatedLinkAttributes( {
36
+ rel = '',
37
+ url = '',
38
+ opensInNewTab,
39
+ nofollow,
40
+ } ) {
41
+ let newLinkTarget;
42
+ // Since `rel` is editable attribute, we need to check for existing values and proceed accordingly.
43
+ let updatedRel = rel;
44
+
45
+ if ( opensInNewTab ) {
46
+ newLinkTarget = NEW_TAB_TARGET;
47
+ updatedRel = updatedRel?.includes( NEW_TAB_REL )
48
+ ? updatedRel
49
+ : updatedRel + ` ${ NEW_TAB_REL }`;
50
+ } else {
51
+ const relRegex = new RegExp( `\\b${ NEW_TAB_REL }\\s*`, 'g' );
52
+ updatedRel = updatedRel?.replace( relRegex, '' ).trim();
53
+ }
54
+
55
+ if ( nofollow ) {
56
+ updatedRel = updatedRel?.includes( NOFOLLOW_REL )
57
+ ? updatedRel
58
+ : ( updatedRel + ` ${ NOFOLLOW_REL }` ).trim();
59
+ } else {
60
+ const relRegex = new RegExp( `\\b${ NOFOLLOW_REL }\\s*`, 'g' );
61
+ updatedRel = updatedRel?.replace( relRegex, '' ).trim();
62
+ }
63
+
64
+ return {
65
+ url: prependHTTP( url ),
66
+ linkTarget: newLinkTarget,
67
+ rel: updatedRel || undefined,
68
+ };
69
+ }
70
+
71
+ export default function Link( {
72
+ clientId,
73
+ control,
74
+ blockType,
75
+ attributeValues,
76
+ updateAttributes,
77
+ } ) {
78
+ const [ isLinkControlOpen, setIsLinkControlOpen ] = useState( false );
79
+ const { popoverProps } = useInspectorPopoverPlacement( {
80
+ isControl: true,
81
+ } );
82
+ const hrefKey = control.mapping.href;
83
+ const relKey = control.mapping.rel;
84
+ const targetKey = control.mapping.target;
85
+ const destinationKey = control.mapping.destination;
86
+
87
+ const href = attributeValues[ hrefKey ];
88
+ const rel = attributeValues[ relKey ];
89
+ const target = attributeValues[ targetKey ];
90
+ const destination = attributeValues[ destinationKey ];
91
+
92
+ const hrefDefaultValue =
93
+ blockType.attributes[ href ]?.defaultValue ?? undefined;
94
+ const relDefaultValue =
95
+ blockType.attributes[ rel ]?.defaultValue ?? undefined;
96
+ const targetDefaultValue =
97
+ blockType.attributes[ target ]?.defaultValue ?? undefined;
98
+ const destinationDefaultValue =
99
+ blockType.attributes[ destination ]?.defaultValue ?? undefined;
100
+
101
+ const opensInNewTab = target === NEW_TAB_TARGET;
102
+ const nofollow = rel === NOFOLLOW_REL;
103
+
104
+ // Memoize link value to avoid overriding the LinkControl's internal state.
105
+ // This is a temporary fix. See https://github.com/WordPress/gutenberg/issues/51256.
106
+ const linkValue = useMemo(
107
+ () => ( { url: href, opensInNewTab, nofollow } ),
108
+ [ href, opensInNewTab, nofollow ]
109
+ );
110
+
111
+ return (
112
+ <ToolsPanelItem
113
+ panelId={ clientId }
114
+ label={ control.label }
115
+ hasValue={ () => !! href }
116
+ onDeselect={ () => {
117
+ updateAttributes( {
118
+ [ hrefKey ]: hrefDefaultValue,
119
+ [ relKey ]: relDefaultValue,
120
+ [ targetKey ]: targetDefaultValue,
121
+ [ destinationKey ]: destinationDefaultValue,
122
+ } );
123
+ } }
124
+ isShownByDefault={ control.shownByDefault }
125
+ >
126
+ <Button
127
+ __next40pxDefaultSize
128
+ className="block-editor-content-only-controls__link"
129
+ onClick={ () => {
130
+ setIsLinkControlOpen( true );
131
+ } }
132
+ >
133
+ <Grid
134
+ rowGap={ 0 }
135
+ columnGap={ 8 }
136
+ templateColumns="24px 1fr"
137
+ className="block-editor-content-only-controls__link-row"
138
+ >
139
+ { href && (
140
+ <>
141
+ <Icon icon={ link } size={ 24 } />
142
+ <span className="block-editor-content-only-controls__link-title">
143
+ { href }
144
+ </span>
145
+ </>
146
+ ) }
147
+ { ! href && (
148
+ <>
149
+ <Icon
150
+ icon={ link }
151
+ size={ 24 }
152
+ style={ { opacity: 0.3 } }
153
+ />
154
+ <span className="block-editor-content-only-controls__link-title">
155
+ { __( 'Link' ) }
156
+ </span>
157
+ </>
158
+ ) }
159
+ </Grid>
160
+ </Button>
161
+ { isLinkControlOpen && (
162
+ <Popover
163
+ onClose={ () => {
164
+ setIsLinkControlOpen( false );
165
+ } }
166
+ { ...( popoverProps ?? {} ) }
167
+ >
168
+ <LinkControl
169
+ value={ linkValue }
170
+ onChange={ ( newValues ) => {
171
+ const updatedAttrs = getUpdatedLinkAttributes( {
172
+ rel,
173
+ ...newValues,
174
+ } );
175
+
176
+ updateAttributes( {
177
+ [ hrefKey ]: updatedAttrs.url,
178
+ [ relKey ]: updatedAttrs.rel,
179
+ [ targetKey ]: updatedAttrs.linkTarget,
180
+ } );
181
+ } }
182
+ onRemove={ () => {
183
+ updateAttributes( {
184
+ [ hrefKey ]: hrefDefaultValue,
185
+ [ relKey ]: relDefaultValue,
186
+ [ targetKey ]: targetDefaultValue,
187
+ [ destinationKey ]: destinationDefaultValue,
188
+ } );
189
+ } }
190
+ />
191
+ </Popover>
192
+ ) }
193
+ </ToolsPanelItem>
194
+ );
195
+ }