@wordpress/block-library 9.33.2 → 9.34.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 (102) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/accordion-heading/block.json +1 -1
  3. package/build/accordion-item/block.json +1 -1
  4. package/build/breadcrumbs/block.json +3 -4
  5. package/build/breadcrumbs/edit.js +43 -77
  6. package/build/breadcrumbs/edit.js.map +2 -2
  7. package/build/categories/edit.js +8 -4
  8. package/build/categories/edit.js.map +2 -2
  9. package/build/latest-comments/block.json +4 -3
  10. package/build/latest-comments/deprecated.js +56 -0
  11. package/build/latest-comments/deprecated.js.map +7 -0
  12. package/build/latest-comments/edit.js +16 -10
  13. package/build/latest-comments/edit.js.map +2 -2
  14. package/build/latest-comments/index.js +3 -1
  15. package/build/latest-comments/index.js.map +3 -3
  16. package/build/math/deprecated.js +54 -0
  17. package/build/math/deprecated.js.map +7 -0
  18. package/build/math/index.js +3 -1
  19. package/build/math/index.js.map +3 -3
  20. package/build/math/save.js +2 -3
  21. package/build/math/save.js.map +2 -2
  22. package/build/navigation/edit/menu-inspector-controls.js +1 -1
  23. package/build/navigation/edit/menu-inspector-controls.js.map +2 -2
  24. package/build/navigation-link/edit.js +1 -1
  25. package/build/navigation-link/edit.js.map +2 -2
  26. package/build/navigation-link/link-ui/page-creator.js +20 -0
  27. package/build/navigation-link/link-ui/page-creator.js.map +3 -3
  28. package/build/navigation-link/shared/controls.js +36 -16
  29. package/build/navigation-link/shared/controls.js.map +2 -2
  30. package/build/navigation-link/shared/use-entity-binding.js +1 -1
  31. package/build/navigation-link/shared/use-entity-binding.js.map +2 -2
  32. package/build/navigation-submenu/edit.js +1 -1
  33. package/build/navigation-submenu/edit.js.map +2 -2
  34. package/build-module/accordion-heading/block.json +1 -1
  35. package/build-module/accordion-item/block.json +1 -1
  36. package/build-module/breadcrumbs/block.json +3 -4
  37. package/build-module/breadcrumbs/edit.js +44 -78
  38. package/build-module/breadcrumbs/edit.js.map +2 -2
  39. package/build-module/categories/edit.js +8 -4
  40. package/build-module/categories/edit.js.map +2 -2
  41. package/build-module/latest-comments/block.json +4 -3
  42. package/build-module/latest-comments/deprecated.js +36 -0
  43. package/build-module/latest-comments/deprecated.js.map +7 -0
  44. package/build-module/latest-comments/edit.js +17 -10
  45. package/build-module/latest-comments/edit.js.map +2 -2
  46. package/build-module/latest-comments/index.js +3 -1
  47. package/build-module/latest-comments/index.js.map +2 -2
  48. package/build-module/math/deprecated.js +34 -0
  49. package/build-module/math/deprecated.js.map +7 -0
  50. package/build-module/math/index.js +3 -1
  51. package/build-module/math/index.js.map +2 -2
  52. package/build-module/math/save.js +2 -3
  53. package/build-module/math/save.js.map +2 -2
  54. package/build-module/navigation/edit/menu-inspector-controls.js +1 -1
  55. package/build-module/navigation/edit/menu-inspector-controls.js.map +2 -2
  56. package/build-module/navigation-link/edit.js +1 -1
  57. package/build-module/navigation-link/edit.js.map +2 -2
  58. package/build-module/navigation-link/link-ui/page-creator.js +21 -1
  59. package/build-module/navigation-link/link-ui/page-creator.js.map +2 -2
  60. package/build-module/navigation-link/shared/controls.js +37 -17
  61. package/build-module/navigation-link/shared/controls.js.map +2 -2
  62. package/build-module/navigation-link/shared/use-entity-binding.js +1 -1
  63. package/build-module/navigation-link/shared/use-entity-binding.js.map +2 -2
  64. package/build-module/navigation-submenu/edit.js +1 -1
  65. package/build-module/navigation-submenu/edit.js.map +2 -2
  66. package/build-style/editor-rtl.css +2 -2
  67. package/build-style/editor.css +2 -2
  68. package/build-style/math/style-rtl.css +4 -0
  69. package/build-style/math/style.css +4 -0
  70. package/build-style/navigation-link/editor-rtl.css +1 -1
  71. package/build-style/navigation-link/editor.css +1 -1
  72. package/build-style/style-rtl.css +5 -0
  73. package/build-style/style.css +5 -0
  74. package/build-style/video/editor-rtl.css +1 -1
  75. package/build-style/video/editor.css +1 -1
  76. package/package.json +37 -37
  77. package/src/accordion-heading/block.json +1 -1
  78. package/src/accordion-item/block.json +1 -1
  79. package/src/breadcrumbs/block.json +3 -4
  80. package/src/breadcrumbs/edit.js +96 -132
  81. package/src/breadcrumbs/index.php +203 -50
  82. package/src/categories/edit.js +10 -6
  83. package/src/categories/index.php +1 -1
  84. package/src/latest-comments/block.json +4 -3
  85. package/src/latest-comments/deprecated.js +37 -0
  86. package/src/latest-comments/edit.js +17 -10
  87. package/src/latest-comments/index.js +2 -0
  88. package/src/latest-comments/index.php +11 -2
  89. package/src/math/deprecated.js +44 -0
  90. package/src/math/index.js +2 -0
  91. package/src/math/save.js +6 -5
  92. package/src/math/style.scss +4 -0
  93. package/src/navigation/edit/menu-inspector-controls.js +1 -1
  94. package/src/navigation-link/edit.js +1 -1
  95. package/src/navigation-link/editor.scss +1 -1
  96. package/src/navigation-link/link-ui/page-creator.js +25 -2
  97. package/src/navigation-link/shared/controls.js +58 -18
  98. package/src/navigation-link/shared/test/controls.js +14 -9
  99. package/src/navigation-link/shared/use-entity-binding.js +1 -1
  100. package/src/navigation-submenu/edit.js +1 -1
  101. package/src/style.scss +1 -0
  102. package/src/video/editor.scss +1 -1
@@ -11,7 +11,7 @@ import {
11
11
  TextareaControl,
12
12
  } from '@wordpress/components';
13
13
  import { __, sprintf } from '@wordpress/i18n';
14
- import { useRef } from '@wordpress/element';
14
+ import { useRef, useEffect, useState } from '@wordpress/element';
15
15
  import { useInstanceId } from '@wordpress/compose';
16
16
  import { safeDecodeURI } from '@wordpress/url';
17
17
  import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
@@ -72,9 +72,20 @@ export function Controls( { attributes, setAttributes, clientId } ) {
72
72
  const { label, url, description, rel, opensInNewTab } = attributes;
73
73
  const lastURLRef = useRef( url );
74
74
  const dropdownMenuProps = useToolsPanelDropdownMenuProps();
75
+ const urlInputRef = useRef();
76
+ const shouldFocusURLInputRef = useRef( false );
75
77
  const inputId = useInstanceId( Controls, 'link-input' );
76
78
  const helpTextId = `${ inputId }__help`;
77
79
 
80
+ // Local state to control the input value
81
+ const [ inputValue, setInputValue ] = useState( url );
82
+
83
+ // Sync local state when url prop changes (e.g., from undo/redo or external updates)
84
+ useEffect( () => {
85
+ setInputValue( url );
86
+ lastURLRef.current = url;
87
+ }, [ url ] );
88
+
78
89
  // Use the entity binding hook internally
79
90
  const { hasUrlBinding, clearBinding } = useEntityBinding( {
80
91
  clientId,
@@ -84,7 +95,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
84
95
  // Get direct store dispatch to bypass setBoundAttributes wrapper
85
96
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
86
97
 
87
- const editBoundLink = () => {
98
+ const unsyncBoundLink = () => {
88
99
  // Clear the binding first
89
100
  clearBinding();
90
101
 
@@ -93,9 +104,23 @@ export function Controls( { attributes, setAttributes, clientId } ) {
93
104
  // setAttributes is actually setBoundAttributes, a wrapper function that
94
105
  // processes attributes through the binding system.
95
106
  // See: packages/block-editor/src/components/block-edit/edit.js
96
- updateBlockAttributes( clientId, { url: '', id: undefined } );
107
+ updateBlockAttributes( clientId, {
108
+ url: lastURLRef.current, // set the lastURLRef as the new editable value so we avoid bugs from empty link states
109
+ id: undefined,
110
+ } );
97
111
  };
98
112
 
113
+ useEffect( () => {
114
+ // Checking for ! hasUrlBinding is a defensive check, as we would
115
+ // only want to focus the input if the url is not bound to an entity.
116
+ if ( ! hasUrlBinding && shouldFocusURLInputRef.current ) {
117
+ // focuses and highlights the url input value, giving the user
118
+ // the ability to delete the value quickly or edit it.
119
+ urlInputRef.current?.select();
120
+ }
121
+ shouldFocusURLInputRef.current = false;
122
+ }, [ hasUrlBinding ] );
123
+
99
124
  return (
100
125
  <ToolsPanel
101
126
  label={ __( 'Settings' ) }
@@ -135,22 +160,25 @@ export function Controls( { attributes, setAttributes, clientId } ) {
135
160
  isShownByDefault
136
161
  >
137
162
  <InputControl
163
+ ref={ urlInputRef }
138
164
  __nextHasNoMarginBottom
139
165
  __next40pxDefaultSize
140
166
  id={ inputId }
141
167
  label={ __( 'Link' ) }
142
- value={ url ? safeDecodeURI( url ) : '' }
143
- onChange={ ( urlValue ) => {
144
- if ( hasUrlBinding ) {
145
- return; // Prevent editing when URL is bound
146
- }
147
- setAttributes( {
148
- url: encodeURI( safeDecodeURI( urlValue ) ),
149
- } );
150
- } }
168
+ value={ inputValue ? safeDecodeURI( inputValue ) : '' }
151
169
  autoComplete="off"
152
170
  type="url"
153
171
  disabled={ hasUrlBinding }
172
+ onChange={ ( newValue ) => {
173
+ if ( hasUrlBinding ) {
174
+ return;
175
+ }
176
+
177
+ // Defer updating the url attribute until onBlur to prevent the canvas from
178
+ // treating a temporary empty value as a committed value, which replaces the
179
+ // label with placeholder text.
180
+ setInputValue( newValue );
181
+ } }
154
182
  onFocus={ () => {
155
183
  if ( hasUrlBinding ) {
156
184
  return;
@@ -161,12 +189,19 @@ export function Controls( { attributes, setAttributes, clientId } ) {
161
189
  if ( hasUrlBinding ) {
162
190
  return;
163
191
  }
192
+
193
+ const finalValue = ! inputValue
194
+ ? lastURLRef.current
195
+ : inputValue;
196
+
197
+ // Update local state immediately so input reflects the reverted value if the value was cleared
198
+ setInputValue( finalValue );
199
+
164
200
  // Defer the updateAttributes call to ensure entity connection isn't severed by accident.
165
- updateAttributes(
166
- { url: ! url ? lastURLRef.current : url },
167
- setAttributes,
168
- { ...attributes, url: lastURLRef.current }
169
- );
201
+ updateAttributes( { url: finalValue }, setAttributes, {
202
+ ...attributes,
203
+ url: lastURLRef.current,
204
+ } );
170
205
  } }
171
206
  help={
172
207
  hasUrlBinding && (
@@ -180,7 +215,12 @@ export function Controls( { attributes, setAttributes, clientId } ) {
180
215
  hasUrlBinding && (
181
216
  <Button
182
217
  icon={ unlinkIcon }
183
- onClick={ editBoundLink }
218
+ onClick={ () => {
219
+ unsyncBoundLink();
220
+ // Focus management to send focus to the URL input
221
+ // on next render after disabled state is removed.
222
+ shouldFocusURLInputRef.current = true;
223
+ } }
184
224
  aria-describedby={ helpTextId }
185
225
  showTooltip
186
226
  label={ __( 'Unsync and edit' ) }
@@ -6,6 +6,7 @@
6
6
  * External dependencies
7
7
  */
8
8
  import { render, screen, fireEvent } from '@testing-library/react';
9
+ import userEvent from '@testing-library/user-event';
9
10
 
10
11
  /**
11
12
  * Internal dependencies
@@ -95,18 +96,22 @@ describe( 'Controls', () => {
95
96
  expect( urlInput.value ).toBe( 'https://example.com/test page' );
96
97
  } );
97
98
 
98
- it( 'encodes URL values when changed', () => {
99
+ it( 'calls updateAttributes with new URL on blur', async () => {
100
+ const user = userEvent.setup();
99
101
  render( <Controls { ...defaultProps } /> );
100
102
 
101
103
  const urlInput = screen.getByLabelText( 'Link' );
102
104
 
103
- fireEvent.change( urlInput, {
104
- target: { value: 'https://example.com/test page' },
105
- } );
105
+ await user.click( urlInput );
106
+ await user.clear( urlInput );
107
+ await user.type( urlInput, 'https://example.com/test page' );
108
+ await user.tab();
106
109
 
107
- expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
108
- url: 'https://example.com/test%20page',
109
- } );
110
+ expect( mockUpdateAttributes ).toHaveBeenCalledWith(
111
+ { url: 'https://example.com/test page' },
112
+ defaultProps.setAttributes,
113
+ { ...defaultProps.attributes, url: 'https://example.com' }
114
+ );
110
115
  } );
111
116
 
112
117
  it( 'calls updateAttributes on URL blur', () => {
@@ -143,11 +148,11 @@ describe( 'Controls', () => {
143
148
  target: { value: 'https://new.com' },
144
149
  } );
145
150
 
146
- // Blur should call updateAttributes with the current URL (since url exists)
151
+ // Blur should call updateAttributes with the new URL value from the input
147
152
  fireEvent.blur( urlInput );
148
153
 
149
154
  expect( mockUpdateAttributes ).toHaveBeenCalledWith(
150
- { url: 'https://different.com' }, // Current URL from attributes (not input value)
155
+ { url: 'https://new.com' }, // New URL from input value
151
156
  defaultProps.setAttributes,
152
157
  {
153
158
  ...propsWithDifferentUrl.attributes,
@@ -69,7 +69,7 @@ export function useEntityBinding( { clientId, attributes } ) {
69
69
  if ( hasUrlBinding ) {
70
70
  updateBlockBindings( { url: undefined } );
71
71
  }
72
- }, [ updateBlockBindings, hasUrlBinding, metadata, id ] );
72
+ }, [ updateBlockBindings, hasUrlBinding ] );
73
73
 
74
74
  const createBinding = useCallback(
75
75
  ( updatedAttributes ) => {
@@ -447,7 +447,7 @@ export default function NavigationSubmenuEdit( {
447
447
  if ( isEntityLink ) {
448
448
  createBinding( updatedAttributes );
449
449
  } else {
450
- clearBinding( updatedAttributes );
450
+ clearBinding();
451
451
  }
452
452
  } }
453
453
  />
package/src/style.scss CHANGED
@@ -32,6 +32,7 @@
32
32
  @use "./latest-posts/style.scss" as *;
33
33
  @use "./list/style.scss" as *;
34
34
  @use "./loginout/style.scss" as *;
35
+ @use "./math/style.scss" as *;
35
36
  @use "./media-text/style.scss" as *;
36
37
  @use "./navigation/style.scss" as *;
37
38
  @use "./navigation-link/style.scss" as *;
@@ -41,7 +41,7 @@
41
41
  color: $gray-700;
42
42
  text-transform: uppercase;
43
43
  font-size: 11px;
44
- font-weight: 500;
44
+ font-weight: $font-weight-medium;
45
45
  display: block;
46
46
  }
47
47