@wordpress/block-editor 8.3.0-next.e230fbab09.0 → 8.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 (174) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/build/components/block-list/block-html.js +4 -1
  4. package/build/components/block-list/block-html.js.map +1 -1
  5. package/build/components/block-list/block.js +4 -1
  6. package/build/components/block-list/block.js.map +1 -1
  7. package/build/components/block-list/use-block-props/use-focus-first-element.js +19 -0
  8. package/build/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
  9. package/build/components/block-lock/index.js +32 -0
  10. package/build/components/block-lock/index.js.map +1 -0
  11. package/build/components/block-lock/menu-item.js +58 -0
  12. package/build/components/block-lock/menu-item.js.map +1 -0
  13. package/build/components/block-lock/modal.js +143 -0
  14. package/build/components/block-lock/modal.js.map +1 -0
  15. package/build/components/block-lock/toolbar.js +70 -0
  16. package/build/components/block-lock/toolbar.js.map +1 -0
  17. package/build/components/block-settings-menu/block-settings-dropdown.js +26 -6
  18. package/build/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  19. package/build/components/block-settings-menu-controls/index.js +19 -9
  20. package/build/components/block-settings-menu-controls/index.js.map +1 -1
  21. package/build/components/block-title/use-block-display-title.js +7 -5
  22. package/build/components/block-title/use-block-display-title.js.map +1 -1
  23. package/build/components/block-toolbar/index.js +4 -0
  24. package/build/components/block-toolbar/index.js.map +1 -1
  25. package/build/components/border-radius-control/index.js +0 -1
  26. package/build/components/border-radius-control/index.js.map +1 -1
  27. package/build/components/border-radius-control/utils.js +1 -1
  28. package/build/components/border-radius-control/utils.js.map +1 -1
  29. package/build/components/colors-gradients/control.js +3 -1
  30. package/build/components/colors-gradients/control.js.map +1 -1
  31. package/build/components/date-format-picker/index.js +132 -0
  32. package/build/components/date-format-picker/index.js.map +1 -0
  33. package/build/components/index.js +9 -0
  34. package/build/components/index.js.map +1 -1
  35. package/build/components/line-height-control/index.js +5 -3
  36. package/build/components/line-height-control/index.js.map +1 -1
  37. package/build/components/list-view/block-select-button.js +4 -22
  38. package/build/components/list-view/block-select-button.js.map +1 -1
  39. package/build/components/list-view/block.js +33 -12
  40. package/build/components/list-view/block.js.map +1 -1
  41. package/build/components/list-view/branch.js +16 -13
  42. package/build/components/list-view/branch.js.map +1 -1
  43. package/build/components/list-view/index.js +7 -1
  44. package/build/components/list-view/index.js.map +1 -1
  45. package/build/components/list-view/use-block-selection.js +9 -2
  46. package/build/components/list-view/use-block-selection.js.map +1 -1
  47. package/build/components/rich-text/index.js +2 -2
  48. package/build/components/rich-text/index.js.map +1 -1
  49. package/build/components/rich-text/index.native.js +13 -9
  50. package/build/components/rich-text/index.native.js.map +1 -1
  51. package/build/components/url-popover/image-url-input-ui.js +11 -27
  52. package/build/components/url-popover/image-url-input-ui.js.map +1 -1
  53. package/build/hooks/anchor.js +7 -6
  54. package/build/hooks/anchor.js.map +1 -1
  55. package/build/hooks/gap.js +70 -5
  56. package/build/hooks/gap.js.map +1 -1
  57. package/build/layouts/flex.js +8 -5
  58. package/build/layouts/flex.js.map +1 -1
  59. package/build/layouts/flow.js +16 -12
  60. package/build/layouts/flow.js.map +1 -1
  61. package/build/store/defaults.js +1 -0
  62. package/build/store/defaults.js.map +1 -1
  63. package/build/store/selectors.js +29 -3
  64. package/build/store/selectors.js.map +1 -1
  65. package/build-module/components/block-list/block-html.js +5 -2
  66. package/build-module/components/block-list/block-html.js.map +1 -1
  67. package/build-module/components/block-list/block.js +5 -2
  68. package/build-module/components/block-list/block.js.map +1 -1
  69. package/build-module/components/block-list/use-block-props/use-focus-first-element.js +18 -0
  70. package/build-module/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
  71. package/build-module/components/block-lock/index.js +4 -0
  72. package/build-module/components/block-lock/index.js.map +1 -0
  73. package/build-module/components/block-lock/menu-item.js +44 -0
  74. package/build-module/components/block-lock/menu-item.js.map +1 -0
  75. package/build-module/components/block-lock/modal.js +128 -0
  76. package/build-module/components/block-lock/modal.js.map +1 -0
  77. package/build-module/components/block-lock/toolbar.js +55 -0
  78. package/build-module/components/block-lock/toolbar.js.map +1 -0
  79. package/build-module/components/block-settings-menu/block-settings-dropdown.js +26 -6
  80. package/build-module/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  81. package/build-module/components/block-settings-menu-controls/index.js +18 -9
  82. package/build-module/components/block-settings-menu-controls/index.js.map +1 -1
  83. package/build-module/components/block-title/use-block-display-title.js +7 -5
  84. package/build-module/components/block-title/use-block-display-title.js.map +1 -1
  85. package/build-module/components/block-toolbar/index.js +3 -0
  86. package/build-module/components/block-toolbar/index.js.map +1 -1
  87. package/build-module/components/border-radius-control/index.js +0 -1
  88. package/build-module/components/border-radius-control/index.js.map +1 -1
  89. package/build-module/components/border-radius-control/utils.js +1 -1
  90. package/build-module/components/border-radius-control/utils.js.map +1 -1
  91. package/build-module/components/colors-gradients/control.js +3 -1
  92. package/build-module/components/colors-gradients/control.js.map +1 -1
  93. package/build-module/components/date-format-picker/index.js +122 -0
  94. package/build-module/components/date-format-picker/index.js.map +1 -0
  95. package/build-module/components/index.js +1 -0
  96. package/build-module/components/index.js.map +1 -1
  97. package/build-module/components/line-height-control/index.js +5 -3
  98. package/build-module/components/line-height-control/index.js.map +1 -1
  99. package/build-module/components/list-view/block-select-button.js +5 -20
  100. package/build-module/components/list-view/block-select-button.js.map +1 -1
  101. package/build-module/components/list-view/block.js +31 -12
  102. package/build-module/components/list-view/block.js.map +1 -1
  103. package/build-module/components/list-view/branch.js +16 -13
  104. package/build-module/components/list-view/branch.js.map +1 -1
  105. package/build-module/components/list-view/index.js +7 -1
  106. package/build-module/components/list-view/index.js.map +1 -1
  107. package/build-module/components/list-view/use-block-selection.js +10 -3
  108. package/build-module/components/list-view/use-block-selection.js.map +1 -1
  109. package/build-module/components/rich-text/index.js +2 -2
  110. package/build-module/components/rich-text/index.js.map +1 -1
  111. package/build-module/components/rich-text/index.native.js +13 -9
  112. package/build-module/components/rich-text/index.native.js.map +1 -1
  113. package/build-module/components/url-popover/image-url-input-ui.js +12 -28
  114. package/build-module/components/url-popover/image-url-input-ui.js.map +1 -1
  115. package/build-module/hooks/anchor.js +7 -6
  116. package/build-module/hooks/anchor.js.map +1 -1
  117. package/build-module/hooks/gap.js +68 -7
  118. package/build-module/hooks/gap.js.map +1 -1
  119. package/build-module/layouts/flex.js +7 -5
  120. package/build-module/layouts/flex.js.map +1 -1
  121. package/build-module/layouts/flow.js +15 -12
  122. package/build-module/layouts/flow.js.map +1 -1
  123. package/build-module/store/defaults.js +1 -0
  124. package/build-module/store/defaults.js.map +1 -1
  125. package/build-module/store/selectors.js +24 -1
  126. package/build-module/store/selectors.js.map +1 -1
  127. package/build-style/style-rtl.css +157 -0
  128. package/build-style/style.css +157 -0
  129. package/package.json +28 -27
  130. package/src/components/block-list/block-html.js +8 -4
  131. package/src/components/block-list/block.js +5 -1
  132. package/src/components/block-list/use-block-props/use-focus-first-element.js +28 -0
  133. package/src/components/block-lock/index.js +3 -0
  134. package/src/components/block-lock/menu-item.js +52 -0
  135. package/src/components/block-lock/modal.js +165 -0
  136. package/src/components/block-lock/style.scss +67 -0
  137. package/src/components/block-lock/toolbar.js +58 -0
  138. package/src/components/block-settings-menu/block-settings-dropdown.js +47 -5
  139. package/src/components/block-settings-menu-controls/index.js +33 -12
  140. package/src/components/block-title/README.md +6 -1
  141. package/src/components/block-title/test/index.js +43 -1
  142. package/src/components/block-title/use-block-display-title.js +9 -6
  143. package/src/components/block-toolbar/index.js +6 -0
  144. package/src/components/block-toolbar/style.scss +4 -0
  145. package/src/components/block-tools/style.scss +29 -0
  146. package/src/components/border-radius-control/index.js +0 -1
  147. package/src/components/border-radius-control/test/utils.js +4 -0
  148. package/src/components/border-radius-control/utils.js +2 -1
  149. package/src/components/color-palette/test/__snapshots__/control.js.snap +70 -4
  150. package/src/components/colors-gradients/control.js +1 -1
  151. package/src/components/colors-gradients/style.scss +6 -0
  152. package/src/components/date-format-picker/README.md +58 -0
  153. package/src/components/date-format-picker/index.js +161 -0
  154. package/src/components/date-format-picker/style.scss +31 -0
  155. package/src/components/index.js +1 -0
  156. package/src/components/line-height-control/index.js +3 -3
  157. package/src/components/link-control/README.md +1 -1
  158. package/src/components/list-view/block-select-button.js +2 -29
  159. package/src/components/list-view/block.js +47 -12
  160. package/src/components/list-view/branch.js +37 -15
  161. package/src/components/list-view/index.js +6 -0
  162. package/src/components/list-view/use-block-selection.js +15 -2
  163. package/src/components/rich-text/index.js +1 -1
  164. package/src/components/rich-text/index.native.js +24 -8
  165. package/src/components/url-popover/image-url-input-ui.js +16 -29
  166. package/src/hooks/anchor.js +8 -6
  167. package/src/hooks/gap.js +83 -12
  168. package/src/hooks/test/gap.js +42 -0
  169. package/src/layouts/flex.js +6 -3
  170. package/src/layouts/flow.js +16 -11
  171. package/src/store/defaults.js +1 -0
  172. package/src/store/selectors.js +26 -1
  173. package/src/store/test/selectors.js +63 -0
  174. package/src/style.scss +2 -0
@@ -0,0 +1,165 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { useEffect, useState } from '@wordpress/element';
6
+ import {
7
+ Button,
8
+ CheckboxControl,
9
+ Flex,
10
+ FlexItem,
11
+ Icon,
12
+ Modal,
13
+ } from '@wordpress/components';
14
+ import { dragHandle, trash } from '@wordpress/icons';
15
+ import { useInstanceId } from '@wordpress/compose';
16
+ import { useDispatch, useSelect } from '@wordpress/data';
17
+
18
+ /**
19
+ * Internal dependencies
20
+ */
21
+ import useBlockDisplayInformation from '../use-block-display-information';
22
+ import { store as blockEditorStore } from '../../store';
23
+
24
+ export default function BlockLockModal( { clientId, onClose } ) {
25
+ const [ lock, setLock ] = useState( { move: false, remove: false } );
26
+ const { canMove, canRemove } = useSelect(
27
+ ( select ) => {
28
+ const {
29
+ canMoveBlock,
30
+ canRemoveBlock,
31
+ getBlockRootClientId,
32
+ } = select( blockEditorStore );
33
+ const rootClientId = getBlockRootClientId( clientId );
34
+
35
+ return {
36
+ canMove: canMoveBlock( clientId, rootClientId ),
37
+ canRemove: canRemoveBlock( clientId, rootClientId ),
38
+ };
39
+ },
40
+ [ clientId ]
41
+ );
42
+ const { updateBlockAttributes } = useDispatch( blockEditorStore );
43
+ const blockInformation = useBlockDisplayInformation( clientId );
44
+ const instanceId = useInstanceId(
45
+ BlockLockModal,
46
+ 'block-editor-block-lock-modal__options-title'
47
+ );
48
+
49
+ useEffect( () => {
50
+ setLock( {
51
+ move: ! canMove,
52
+ remove: ! canRemove,
53
+ } );
54
+ }, [ canMove, canRemove ] );
55
+
56
+ const isAllChecked = Object.values( lock ).every( Boolean );
57
+
58
+ let ariaChecked;
59
+ if ( isAllChecked ) {
60
+ ariaChecked = 'true';
61
+ } else if ( Object.values( lock ).some( Boolean ) ) {
62
+ ariaChecked = 'mixed';
63
+ } else {
64
+ ariaChecked = 'false';
65
+ }
66
+
67
+ return (
68
+ <Modal
69
+ title={ sprintf(
70
+ /* translators: %s: Name of the block. */
71
+ __( 'Lock %s' ),
72
+ blockInformation.title
73
+ ) }
74
+ overlayClassName="block-editor-block-lock-modal"
75
+ closeLabel={ __( 'Close' ) }
76
+ onRequestClose={ onClose }
77
+ >
78
+ <form
79
+ onSubmit={ ( event ) => {
80
+ event.preventDefault();
81
+ updateBlockAttributes( [ clientId ], { lock } );
82
+ onClose();
83
+ } }
84
+ >
85
+ <p>
86
+ { __(
87
+ 'Choose specific attributes to restrict or lock all available options.'
88
+ ) }
89
+ </p>
90
+ <div
91
+ role="group"
92
+ aria-labelledby={ instanceId }
93
+ className="block-editor-block-lock-modal__options"
94
+ >
95
+ <CheckboxControl
96
+ className="block-editor-block-lock-modal__options-title"
97
+ label={
98
+ <span id={ instanceId }>{ __( 'Lock all' ) }</span>
99
+ }
100
+ checked={ isAllChecked }
101
+ aria-checked={ ariaChecked }
102
+ onChange={ ( newValue ) =>
103
+ setLock( {
104
+ move: newValue,
105
+ remove: newValue,
106
+ } )
107
+ }
108
+ />
109
+ <ul className="block-editor-block-lock-modal__checklist">
110
+ <li className="block-editor-block-lock-modal__checklist-item">
111
+ <CheckboxControl
112
+ label={
113
+ <>
114
+ { __( 'Disable movement' ) }
115
+ <Icon icon={ dragHandle } />
116
+ </>
117
+ }
118
+ checked={ lock.move }
119
+ onChange={ ( move ) =>
120
+ setLock( ( prevLock ) => ( {
121
+ ...prevLock,
122
+ move,
123
+ } ) )
124
+ }
125
+ />
126
+ </li>
127
+ <li className="block-editor-block-lock-modal__checklist-item">
128
+ <CheckboxControl
129
+ label={
130
+ <>
131
+ { __( 'Prevent removal' ) }
132
+ <Icon icon={ trash } />
133
+ </>
134
+ }
135
+ checked={ lock.remove }
136
+ onChange={ ( remove ) =>
137
+ setLock( ( prevLock ) => ( {
138
+ ...prevLock,
139
+ remove,
140
+ } ) )
141
+ }
142
+ />
143
+ </li>
144
+ </ul>
145
+ </div>
146
+ <Flex
147
+ className="block-editor-block-lock-modal__actions"
148
+ justify="flex-end"
149
+ expanded={ false }
150
+ >
151
+ <FlexItem>
152
+ <Button variant="tertiary" onClick={ onClose }>
153
+ { __( 'Cancel' ) }
154
+ </Button>
155
+ </FlexItem>
156
+ <FlexItem>
157
+ <Button variant="primary" type="submit">
158
+ { __( 'Apply' ) }
159
+ </Button>
160
+ </FlexItem>
161
+ </Flex>
162
+ </form>
163
+ </Modal>
164
+ );
165
+ }
@@ -0,0 +1,67 @@
1
+ .block-editor-block-lock-modal {
2
+ z-index: z-index(".block-editor-block-lock-modal");
3
+
4
+ .components-modal__frame {
5
+ @include break-small() {
6
+ max-width: $break-mobile;
7
+ }
8
+ }
9
+ }
10
+
11
+ .block-editor-block-lock-modal__checklist {
12
+ margin: 0;
13
+ }
14
+
15
+ .block-editor-block-lock-modal__options-title {
16
+ border-bottom: 1px solid $gray-300;
17
+ padding: $grid-unit-15 0;
18
+
19
+ .components-checkbox-control__label {
20
+ font-weight: 600;
21
+ }
22
+
23
+ .components-base-control__field {
24
+ align-items: center;
25
+ display: flex;
26
+ margin: 0;
27
+ }
28
+ }
29
+ .block-editor-block-lock-modal__checklist-item {
30
+ border-bottom: 1px solid $gray-300;
31
+ margin-bottom: 0;
32
+ padding: $grid-unit-15 0 $grid-unit-15 $grid-unit-15;
33
+
34
+ .components-base-control__field {
35
+ align-items: center;
36
+ display: flex;
37
+ margin: 0;
38
+ }
39
+
40
+ .components-checkbox-control__label {
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ flex-grow: 1;
45
+
46
+ svg {
47
+ margin-right: $grid-unit-15;
48
+ fill: $gray-900;
49
+ }
50
+ }
51
+ }
52
+
53
+ .block-editor-block-lock-modal__actions {
54
+ margin-top: $grid-unit-30;
55
+ }
56
+
57
+ .block-editor-block-lock-toolbar {
58
+ .components-button.has-icon {
59
+ min-width: $button-size-small + $grid-unit-15 !important;
60
+ padding-left: 0 !important;
61
+
62
+ &:focus::before {
63
+ left: 0 !important;
64
+ right: $grid-unit-15 !important;
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
6
+ import { useReducer } from '@wordpress/element';
7
+ import { lock } from '@wordpress/icons';
8
+ import { useSelect } from '@wordpress/data';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import BlockLockModal from './modal';
14
+ import useBlockDisplayInformation from '../use-block-display-information';
15
+ import { store as blockEditorStore } from '../../store';
16
+
17
+ export default function BlockLockToolbar( { clientId } ) {
18
+ const blockInformation = useBlockDisplayInformation( clientId );
19
+ const { canMove, canRemove } = useSelect(
20
+ ( select ) => {
21
+ const { canMoveBlock, canRemoveBlock } = select( blockEditorStore );
22
+
23
+ return {
24
+ canMove: canMoveBlock( clientId ),
25
+ canRemove: canRemoveBlock( clientId ),
26
+ };
27
+ },
28
+ [ clientId ]
29
+ );
30
+
31
+ const [ isModalOpen, toggleModal ] = useReducer(
32
+ ( isActive ) => ! isActive,
33
+ false
34
+ );
35
+
36
+ if ( canMove && canRemove ) {
37
+ return null;
38
+ }
39
+
40
+ return (
41
+ <>
42
+ <ToolbarGroup className="block-editor-block-lock-toolbar">
43
+ <ToolbarButton
44
+ icon={ lock }
45
+ label={ sprintf(
46
+ /* translators: %s: block name */
47
+ __( 'Unlock %s' ),
48
+ blockInformation.title
49
+ ) }
50
+ onClick={ toggleModal }
51
+ />
52
+ </ToolbarGroup>
53
+ { isModalOpen && (
54
+ <BlockLockModal clientId={ clientId } onClose={ toggleModal } />
55
+ ) }
56
+ </>
57
+ );
58
+ }
@@ -46,11 +46,26 @@ export function BlockSettingsDropdown( {
46
46
  const blockClientIds = castArray( clientIds );
47
47
  const count = blockClientIds.length;
48
48
  const firstBlockClientId = blockClientIds[ 0 ];
49
- const { onlyBlock } = useSelect(
49
+ const {
50
+ onlyBlock,
51
+ previousBlockClientId,
52
+ nextBlockClientId,
53
+ selectedBlockClientIds,
54
+ } = useSelect(
50
55
  ( select ) => {
51
- const { getBlockCount } = select( blockEditorStore );
56
+ const {
57
+ getBlockCount,
58
+ getPreviousBlockClientId,
59
+ getNextBlockClientId,
60
+ getSelectedBlockClientIds,
61
+ } = select( blockEditorStore );
52
62
  return {
53
63
  onlyBlock: 1 === getBlockCount(),
64
+ previousBlockClientId: getPreviousBlockClientId(
65
+ firstBlockClientId
66
+ ),
67
+ nextBlockClientId: getNextBlockClientId( firstBlockClientId ),
68
+ selectedBlockClientIds: getSelectedBlockClientIds(),
54
69
  };
55
70
  },
56
71
  [ firstBlockClientId ]
@@ -72,7 +87,7 @@ export function BlockSettingsDropdown( {
72
87
  };
73
88
  }, [] );
74
89
 
75
- const updateSelection = useCallback(
90
+ const updateSelectionAfterDuplicate = useCallback(
76
91
  __experimentalSelectBlock
77
92
  ? async ( clientIdsPromise ) => {
78
93
  const ids = await clientIdsPromise;
@@ -86,6 +101,33 @@ export function BlockSettingsDropdown( {
86
101
 
87
102
  const blockTitle = useBlockDisplayTitle( firstBlockClientId, 25 );
88
103
 
104
+ const updateSelectionAfterRemove = useCallback(
105
+ __experimentalSelectBlock
106
+ ? () => {
107
+ const blockToSelect =
108
+ previousBlockClientId || nextBlockClientId;
109
+
110
+ if (
111
+ blockToSelect &&
112
+ // From the block options dropdown, it's possible to remove a block that is not selected,
113
+ // in this case, it's not necessary to update the selection since the selected block wasn't removed.
114
+ selectedBlockClientIds.includes( firstBlockClientId ) &&
115
+ // Don't update selection when next/prev block also is in the selection ( and gets removed ),
116
+ // In case someone selects all blocks and removes them at once.
117
+ ! selectedBlockClientIds.includes( blockToSelect )
118
+ ) {
119
+ __experimentalSelectBlock( blockToSelect );
120
+ }
121
+ }
122
+ : noop,
123
+ [
124
+ __experimentalSelectBlock,
125
+ previousBlockClientId,
126
+ nextBlockClientId,
127
+ selectedBlockClientIds,
128
+ ]
129
+ );
130
+
89
131
  const label = sprintf(
90
132
  /* translators: %s: block name */
91
133
  __( 'Remove %s' ),
@@ -139,7 +181,7 @@ export function BlockSettingsDropdown( {
139
181
  onClick={ flow(
140
182
  onClose,
141
183
  onDuplicate,
142
- updateSelection
184
+ updateSelectionAfterDuplicate
143
185
  ) }
144
186
  shortcut={ shortcuts.duplicate }
145
187
  >
@@ -197,7 +239,7 @@ export function BlockSettingsDropdown( {
197
239
  onClick={ flow(
198
240
  onClose,
199
241
  onRemove,
200
- updateSelection
242
+ updateSelectionAfterRemove
201
243
  ) }
202
244
  shortcut={ shortcuts.remove }
203
245
  >
@@ -20,16 +20,19 @@ import {
20
20
  useConvertToGroupButtonProps,
21
21
  ConvertToGroupButton,
22
22
  } from '../convert-to-group-buttons';
23
+ import { BlockLockMenuItem } from '../block-lock';
23
24
  import { store as blockEditorStore } from '../../store';
24
25
 
25
26
  const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' );
26
27
 
27
28
  const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
28
- const { selectedBlocks, selectedClientIds } = useSelect(
29
+ const { selectedBlocks, selectedClientIds, canRemove } = useSelect(
29
30
  ( select ) => {
30
- const { getBlocksByClientId, getSelectedBlockClientIds } = select(
31
- blockEditorStore
32
- );
31
+ const {
32
+ getBlocksByClientId,
33
+ getSelectedBlockClientIds,
34
+ canRemoveBlocks,
35
+ } = select( blockEditorStore );
33
36
  const ids =
34
37
  clientIds !== null ? clientIds : getSelectedBlockClientIds();
35
38
  return {
@@ -38,30 +41,48 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
38
41
  ( block ) => block.name
39
42
  ),
40
43
  selectedClientIds: ids,
44
+ canRemove: canRemoveBlocks( ids ),
41
45
  };
42
46
  },
43
47
  [ clientIds ]
44
48
  );
45
49
 
50
+ const showLockButton = selectedClientIds.length === 1;
51
+
46
52
  // Check if current selection of blocks is Groupable or Ungroupable
47
53
  // and pass this props down to ConvertToGroupButton.
48
54
  const convertToGroupButtonProps = useConvertToGroupButtonProps();
49
55
  const { isGroupable, isUngroupable } = convertToGroupButtonProps;
50
- const showConvertToGroupButton = isGroupable || isUngroupable;
56
+ const showConvertToGroupButton =
57
+ ( isGroupable || isUngroupable ) && canRemove;
58
+
51
59
  return (
52
60
  <Slot fillProps={ { ...fillProps, selectedBlocks, selectedClientIds } }>
53
61
  { ( fills ) => {
54
- if ( fills?.length > 0 || showConvertToGroupButton ) {
55
- return (
56
- <MenuGroup>
57
- { fills }
62
+ if (
63
+ ! fills?.length > 0 &&
64
+ ! showConvertToGroupButton &&
65
+ ! showLockButton
66
+ ) {
67
+ return null;
68
+ }
69
+
70
+ return (
71
+ <MenuGroup>
72
+ { showLockButton && (
73
+ <BlockLockMenuItem
74
+ clientId={ selectedClientIds[ 0 ] }
75
+ />
76
+ ) }
77
+ { fills }
78
+ { showConvertToGroupButton && (
58
79
  <ConvertToGroupButton
59
80
  { ...convertToGroupButtonProps }
60
81
  onClose={ fillProps?.onClose }
61
82
  />
62
- </MenuGroup>
63
- );
64
- }
83
+ ) }
84
+ </MenuGroup>
85
+ );
65
86
  } }
66
87
  </Slot>
67
88
  );
@@ -1,6 +1,11 @@
1
1
  # Block Title
2
2
 
3
- Renders the block's configured title as a string, or empty if the title cannot be determined.
3
+ The Block Title component renders a block's configured title as a string.
4
+
5
+ It prioritizes contextual titles such as block variation and reusable block labels when returning a value. If none is found, it will return the display title specified in the block's metadata.
6
+
7
+ The component will be empty if a title cannot be determined.
8
+
4
9
 
5
10
  ## Usage
6
11
 
@@ -26,6 +26,9 @@ jest.mock( '@wordpress/blocks', () => {
26
26
  case 'name-with-label':
27
27
  return { title: 'Block With Label' };
28
28
 
29
+ case 'name-with-custom-label':
30
+ return { title: 'Block With Custom Label' };
31
+
29
32
  case 'name-with-long-label':
30
33
  return { title: 'Block With Long Label' };
31
34
  }
@@ -38,6 +41,9 @@ jest.mock( '@wordpress/blocks', () => {
38
41
  case 'Block With Long Label':
39
42
  return 'This is a longer label than typical for blocks to have.';
40
43
 
44
+ case 'Block With Custom Label':
45
+ return 'A Custom Label like a Block Variation Label';
46
+
41
47
  default:
42
48
  return title;
43
49
  }
@@ -61,7 +67,7 @@ jest.mock( '@wordpress/data/src/components/use-select', () => {
61
67
  } );
62
68
 
63
69
  describe( 'BlockTitle', () => {
64
- it( 'renders nothing if name is falsey2', () => {
70
+ it( 'renders nothing if name is falsey', () => {
65
71
  useSelect.mockImplementation( () => ( {
66
72
  name: null,
67
73
  attributes: null,
@@ -106,6 +112,42 @@ describe( 'BlockTitle', () => {
106
112
  expect( wrapper.text() ).toBe( 'Test Label' );
107
113
  } );
108
114
 
115
+ it( 'should prioritize reusable block title over title', () => {
116
+ useSelect.mockImplementation( () => ( {
117
+ name: 'name-with-label',
118
+ reusableBlockTitle: 'Reuse me!',
119
+ attributes: null,
120
+ } ) );
121
+
122
+ const wrapper = shallow( <BlockTitle clientId="id-name-with-label" /> );
123
+
124
+ expect( wrapper.text() ).toBe( 'Reuse me!' );
125
+ } );
126
+
127
+ it( 'should prioritize block label over title', () => {
128
+ useSelect.mockImplementation( () => ( {
129
+ name: 'name-with-custom-label',
130
+ attributes: null,
131
+ } ) );
132
+
133
+ const wrapper = shallow( <BlockTitle clientId="id-name-with-label" /> );
134
+
135
+ expect( wrapper.text() ).toBe(
136
+ 'A Custom Label like a Block Variation Label'
137
+ );
138
+ } );
139
+
140
+ it( 'should default to block information title if no reusable title or block name is available', () => {
141
+ useSelect.mockImplementation( () => ( {
142
+ name: 'some-rando-name',
143
+ attributes: null,
144
+ } ) );
145
+
146
+ const wrapper = shallow( <BlockTitle clientId="id-name-with-label" /> );
147
+
148
+ expect( wrapper.text() ).toBe( 'Block With Label' );
149
+ } );
150
+
109
151
  it( 'truncates the label with custom truncate length', () => {
110
152
  useSelect.mockImplementation( () => ( {
111
153
  name: 'name-with-long-label',
@@ -70,14 +70,17 @@ export default function useBlockDisplayTitle( clientId, maximumLength ) {
70
70
  const blockLabel = blockType
71
71
  ? getBlockLabel( blockType, attributes )
72
72
  : null;
73
+
73
74
  const label = reusableBlockTitle || blockLabel;
74
75
  // Label will fallback to the title if no label is defined for the current
75
- // label context. If the label is defined we prioritize it over possible
76
+ // label context. If the label is defined we prioritize it over a
76
77
  // possible block variation title match.
77
- if ( label && label !== blockType.title ) {
78
- return maximumLength && maximumLength > 0
79
- ? truncate( label, { length: maximumLength } )
80
- : label;
78
+ const blockTitle =
79
+ label && label !== blockType.title ? label : blockInformation.title;
80
+
81
+ if ( maximumLength && maximumLength > 0 ) {
82
+ return truncate( blockTitle, { length: maximumLength } );
81
83
  }
82
- return blockInformation.title;
84
+
85
+ return blockTitle;
83
86
  }
@@ -20,6 +20,7 @@ import BlockParentSelector from '../block-parent-selector';
20
20
  import BlockSwitcher from '../block-switcher';
21
21
  import BlockControls from '../block-controls';
22
22
  import BlockSettingsMenu from '../block-settings-menu';
23
+ import { BlockLockToolbar } from '../block-lock';
23
24
  import { useShowMoversGestures } from './utils';
24
25
  import { store as blockEditorStore } from '../../store';
25
26
 
@@ -114,6 +115,11 @@ export default function BlockToolbar( { hideDragHandle } ) {
114
115
  { ( shouldShowVisualToolbar || isMultiToolbar ) && (
115
116
  <ToolbarGroup className="block-editor-block-toolbar__block-controls">
116
117
  <BlockSwitcher clientIds={ blockClientIds } />
118
+ { ! isMultiToolbar && (
119
+ <BlockLockToolbar
120
+ clientId={ blockClientIds[ 0 ] }
121
+ />
122
+ ) }
117
123
  <BlockMover
118
124
  clientIds={ blockClientIds }
119
125
  hideDragHandle={ hideDragHandle || hasReducedUI }
@@ -91,6 +91,10 @@
91
91
  .block-editor-block-mover {
92
92
  margin-left: -$grid-unit-15 * 0.5;
93
93
  }
94
+
95
+ .block-editor-block-lock-toolbar {
96
+ margin-left: -$grid-unit-15 * 0.5 !important;
97
+ }
94
98
  }
95
99
 
96
100
  .block-editor-block-toolbar,
@@ -307,3 +307,32 @@
307
307
  .is-dragging-components-draggable .components-tooltip {
308
308
  display: none;
309
309
  }
310
+
311
+
312
+ // Size multiple sequential buttons to be optically balanced.
313
+ // Icons are 36px, as set by a 24px icon and 12px padding.
314
+ .block-editor-block-toolbar > .components-toolbar > .block-editor-block-toolbar__slot, // When a plugin adds to a slot, the segment has a `components-toolbar` class.
315
+ .block-editor-block-toolbar > .components-toolbar-group > .block-editor-block-toolbar__slot, // When no plugin adds to slots, the segment has a `components-toolbar-group` class.
316
+ .block-editor-block-toolbar > .block-editor-block-toolbar__slot > .components-toolbar, // The nesting order is sometimes reversed.
317
+ .block-editor-block-toolbar > .block-editor-block-toolbar__slot > .components-dropdown, // Targets unique markup for the "Replace" button.
318
+ .block-editor-block-toolbar .block-editor-block-toolbar__slot .components-toolbar-group { // Inline formatting tools use this class.
319
+ padding-left: $grid-unit-15 * 0.5; // 6px.
320
+ padding-right: $grid-unit-15 * 0.5;
321
+
322
+ > .components-button,
323
+ > div > .components-button,
324
+ > .components-dropdown .components-button {
325
+ min-width: $block-toolbar-height - $grid-unit-15;
326
+ padding-left: $grid-unit-15 * 0.5; // 6px.
327
+ padding-right: $grid-unit-15 * 0.5;
328
+
329
+ svg {
330
+ min-width: $button-size-small; // This is the optimal icon size, and we size the whole button after this.
331
+ }
332
+
333
+ &::before {
334
+ left: 2px;
335
+ right: 2px;
336
+ }
337
+ }
338
+ }
@@ -79,7 +79,6 @@ export default function BorderRadiusControl( { onChange, values } ) {
79
79
  values={ values }
80
80
  min={ MIN_BORDER_RADIUS_VALUE }
81
81
  onChange={ onChange }
82
- unit={ unit }
83
82
  units={ units }
84
83
  />
85
84
  <RangeControl
@@ -150,6 +150,10 @@ describe( 'hasMixedValues', () => {
150
150
  expect( hasMixedValues( '2px' ) ).toBe( false );
151
151
  } );
152
152
 
153
+ it( 'should return false when passed a string value containing a unit with no quantity', () => {
154
+ expect( hasMixedValues( 'em' ) ).toBe( false );
155
+ } );
156
+
153
157
  it( 'should return true when passed mixed values', () => {
154
158
  const values = {
155
159
  bottomLeft: '1em',
@@ -91,7 +91,8 @@ export function getAllValue( values = {} ) {
91
91
  */
92
92
  export function hasMixedValues( values = {} ) {
93
93
  const allValue = getAllValue( values );
94
- const isMixed = isNaN( parseFloat( allValue ) );
94
+ const isMixed =
95
+ typeof values === 'string' ? false : isNaN( parseFloat( allValue ) );
95
96
 
96
97
  return isMixed;
97
98
  }