@wordpress/block-library 9.33.2-next.36001005c.0 → 9.33.3

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 (173) hide show
  1. package/build/accordion/block.json +1 -1
  2. package/build/accordion/edit.js +11 -9
  3. package/build/accordion/edit.js.map +2 -2
  4. package/build/accordion-heading/block.json +1 -1
  5. package/build/accordion-item/block.json +1 -1
  6. package/build/accordion-panel/block.json +1 -1
  7. package/build/buttons/transforms.js +1 -0
  8. package/build/buttons/transforms.js.map +2 -2
  9. package/build/code/transforms.js +2 -0
  10. package/build/code/transforms.js.map +2 -2
  11. package/build/freeform/block.json +5 -1
  12. package/build/home-link/block.json +2 -1
  13. package/build/image/view.js +4 -1
  14. package/build/image/view.js.map +2 -2
  15. package/build/math/deprecated.js +54 -0
  16. package/build/math/deprecated.js.map +7 -0
  17. package/build/math/edit.js +1 -0
  18. package/build/math/edit.js.map +2 -2
  19. package/build/math/index.js +3 -1
  20. package/build/math/index.js.map +3 -3
  21. package/build/math/save.js +2 -3
  22. package/build/math/save.js.map +2 -2
  23. package/build/missing/block.json +3 -0
  24. package/build/navigation/edit/menu-inspector-controls.js +1 -1
  25. package/build/navigation/edit/menu-inspector-controls.js.map +2 -2
  26. package/build/navigation/menu-items-to-blocks.js +8 -3
  27. package/build/navigation/menu-items-to-blocks.js.map +2 -2
  28. package/build/navigation/view.js +2 -2
  29. package/build/navigation/view.js.map +2 -2
  30. package/build/navigation-link/edit.js +1 -1
  31. package/build/navigation-link/edit.js.map +2 -2
  32. package/build/navigation-link/shared/controls.js +16 -36
  33. package/build/navigation-link/shared/controls.js.map +2 -2
  34. package/build/navigation-link/shared/use-entity-binding.js +1 -1
  35. package/build/navigation-link/shared/use-entity-binding.js.map +2 -2
  36. package/build/navigation-submenu/edit.js +1 -1
  37. package/build/navigation-submenu/edit.js.map +2 -2
  38. package/build/post-date/block.json +1 -1
  39. package/build/post-date/edit.js +11 -5
  40. package/build/post-date/edit.js.map +3 -3
  41. package/build/post-date/variations.js +4 -7
  42. package/build/post-date/variations.js.map +2 -2
  43. package/build/post-template/edit.js +13 -1
  44. package/build/post-template/edit.js.map +2 -2
  45. package/build/query/index.js +11 -1
  46. package/build/query/index.js.map +2 -2
  47. package/build/query/variations.js +16 -11
  48. package/build/query/variations.js.map +2 -2
  49. package/build/search/view.js +4 -4
  50. package/build/search/view.js.map +2 -2
  51. package/build/terms-query/edit/inspector-controls/index.js +1 -1
  52. package/build/terms-query/edit/inspector-controls/index.js.map +2 -2
  53. package/build/terms-query/edit/inspector-controls/inherit-control.js +2 -4
  54. package/build/terms-query/edit/inspector-controls/inherit-control.js.map +2 -2
  55. package/build/utils/get-transformed-attributes.js +1 -13
  56. package/build/utils/get-transformed-attributes.js.map +2 -2
  57. package/build-module/accordion/block.json +1 -1
  58. package/build-module/accordion/edit.js +11 -9
  59. package/build-module/accordion/edit.js.map +2 -2
  60. package/build-module/accordion-heading/block.json +1 -1
  61. package/build-module/accordion-item/block.json +1 -1
  62. package/build-module/accordion-panel/block.json +1 -1
  63. package/build-module/buttons/transforms.js +1 -0
  64. package/build-module/buttons/transforms.js.map +2 -2
  65. package/build-module/code/transforms.js +2 -0
  66. package/build-module/code/transforms.js.map +2 -2
  67. package/build-module/freeform/block.json +5 -1
  68. package/build-module/home-link/block.json +2 -1
  69. package/build-module/image/view.js +4 -1
  70. package/build-module/image/view.js.map +2 -2
  71. package/build-module/math/deprecated.js +34 -0
  72. package/build-module/math/deprecated.js.map +7 -0
  73. package/build-module/math/edit.js +1 -0
  74. package/build-module/math/edit.js.map +2 -2
  75. package/build-module/math/index.js +3 -1
  76. package/build-module/math/index.js.map +2 -2
  77. package/build-module/math/save.js +2 -3
  78. package/build-module/math/save.js.map +2 -2
  79. package/build-module/missing/block.json +3 -0
  80. package/build-module/navigation/edit/menu-inspector-controls.js +1 -1
  81. package/build-module/navigation/edit/menu-inspector-controls.js.map +2 -2
  82. package/build-module/navigation/menu-items-to-blocks.js +8 -3
  83. package/build-module/navigation/menu-items-to-blocks.js.map +2 -2
  84. package/build-module/navigation/view.js +2 -2
  85. package/build-module/navigation/view.js.map +2 -2
  86. package/build-module/navigation-link/edit.js +1 -1
  87. package/build-module/navigation-link/edit.js.map +2 -2
  88. package/build-module/navigation-link/shared/controls.js +17 -37
  89. package/build-module/navigation-link/shared/controls.js.map +2 -2
  90. package/build-module/navigation-link/shared/use-entity-binding.js +1 -1
  91. package/build-module/navigation-link/shared/use-entity-binding.js.map +2 -2
  92. package/build-module/navigation-submenu/edit.js +1 -1
  93. package/build-module/navigation-submenu/edit.js.map +2 -2
  94. package/build-module/post-date/block.json +1 -1
  95. package/build-module/post-date/edit.js +11 -5
  96. package/build-module/post-date/edit.js.map +2 -2
  97. package/build-module/post-date/variations.js +4 -7
  98. package/build-module/post-date/variations.js.map +2 -2
  99. package/build-module/post-template/edit.js +13 -1
  100. package/build-module/post-template/edit.js.map +2 -2
  101. package/build-module/query/index.js +11 -1
  102. package/build-module/query/index.js.map +2 -2
  103. package/build-module/query/variations.js +16 -11
  104. package/build-module/query/variations.js.map +2 -2
  105. package/build-module/search/view.js +4 -4
  106. package/build-module/search/view.js.map +2 -2
  107. package/build-module/terms-query/edit/inspector-controls/index.js +1 -1
  108. package/build-module/terms-query/edit/inspector-controls/index.js.map +2 -2
  109. package/build-module/terms-query/edit/inspector-controls/inherit-control.js +2 -4
  110. package/build-module/terms-query/edit/inspector-controls/inherit-control.js.map +2 -2
  111. package/build-module/utils/get-transformed-attributes.js +1 -13
  112. package/build-module/utils/get-transformed-attributes.js.map +2 -2
  113. package/build-style/editor-rtl.css +7 -2
  114. package/build-style/editor.css +8 -2
  115. package/build-style/math/editor-rtl.css +49 -0
  116. package/build-style/math/editor.css +50 -0
  117. package/build-style/math/style-rtl.css +4 -0
  118. package/build-style/math/style.css +4 -0
  119. package/build-style/navigation-link/editor-rtl.css +1 -1
  120. package/build-style/navigation-link/editor.css +1 -1
  121. package/build-style/style-rtl.css +5 -0
  122. package/build-style/style.css +5 -0
  123. package/build-style/video/editor-rtl.css +1 -1
  124. package/build-style/video/editor.css +1 -1
  125. package/package.json +37 -37
  126. package/src/accordion/block.json +1 -1
  127. package/src/accordion/edit.js +16 -16
  128. package/src/accordion-heading/block.json +1 -1
  129. package/src/accordion-item/block.json +1 -1
  130. package/src/accordion-panel/block.json +1 -1
  131. package/src/archives/index.php +53 -1
  132. package/src/buttons/transforms.js +1 -0
  133. package/src/categories/index.php +25 -7
  134. package/src/code/transforms.js +2 -0
  135. package/src/editor.scss +1 -0
  136. package/src/freeform/block.json +5 -1
  137. package/src/home-link/block.json +2 -1
  138. package/src/image/index.php +9 -9
  139. package/src/image/view.js +11 -1
  140. package/src/math/deprecated.js +44 -0
  141. package/src/math/edit.js +1 -0
  142. package/src/math/editor.scss +7 -0
  143. package/src/math/index.js +2 -0
  144. package/src/math/save.js +6 -5
  145. package/src/math/style.scss +4 -0
  146. package/src/missing/block.json +3 -0
  147. package/src/navigation/edit/menu-inspector-controls.js +1 -1
  148. package/src/navigation/index.php +7 -7
  149. package/src/navigation/menu-items-to-blocks.js +12 -2
  150. package/src/navigation/test/menu-items-to-blocks.js +144 -0
  151. package/src/navigation/view.js +2 -2
  152. package/src/navigation-link/edit.js +1 -1
  153. package/src/navigation-link/editor.scss +1 -1
  154. package/src/navigation-link/shared/controls.js +18 -58
  155. package/src/navigation-link/shared/test/controls.js +9 -14
  156. package/src/navigation-link/shared/use-entity-binding.js +1 -1
  157. package/src/navigation-submenu/edit.js +1 -1
  158. package/src/post-date/block.json +1 -1
  159. package/src/post-date/edit.js +15 -10
  160. package/src/post-date/variations.js +2 -5
  161. package/src/post-template/edit.js +13 -1
  162. package/src/query/index.js +10 -0
  163. package/src/query/variations.js +17 -11
  164. package/src/query-pagination-next/index.php +1 -1
  165. package/src/query-pagination-previous/index.php +1 -1
  166. package/src/search/index.php +2 -2
  167. package/src/search/view.js +4 -4
  168. package/src/style.scss +1 -0
  169. package/src/term-template/index.php +8 -2
  170. package/src/terms-query/edit/inspector-controls/index.js +2 -2
  171. package/src/terms-query/edit/inspector-controls/inherit-control.js +1 -3
  172. package/src/utils/get-transformed-attributes.js +5 -22
  173. package/src/video/editor.scss +1 -1
@@ -4,6 +4,11 @@
4
4
  import { createBlock, parse } from '@wordpress/blocks';
5
5
  import { applyFilters } from '@wordpress/hooks';
6
6
 
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { buildNavigationLinkEntityBinding } from '../navigation-link/shared/use-entity-binding';
11
+
7
12
  /**
8
13
  * Convert a flat menu item structure to a nested blocks structure.
9
14
  *
@@ -145,12 +150,14 @@ function menuItemToBlockAttributes(
145
150
  object = 'tag';
146
151
  }
147
152
 
153
+ const inferredKind = menuItemTypeField?.replace( '_', '-' ) || 'custom';
154
+
148
155
  return {
149
156
  label: menuItemTitleField?.rendered || '',
150
157
  ...( object?.length && {
151
158
  type: object,
152
159
  } ),
153
- kind: menuItemTypeField?.replace( '_', '-' ) || 'custom',
160
+ kind: inferredKind,
154
161
  url: url || '',
155
162
  ...( xfn?.length &&
156
163
  xfn.join( ' ' ).trim() && {
@@ -165,8 +172,11 @@ function menuItemToBlockAttributes(
165
172
  title: attr_title,
166
173
  } ),
167
174
  ...( object_id &&
168
- 'custom' !== object && {
175
+ ( inferredKind === 'post-type' || inferredKind === 'taxonomy' ) && {
169
176
  id: object_id,
177
+ metadata: {
178
+ bindings: buildNavigationLinkEntityBinding( inferredKind ),
179
+ },
170
180
  } ),
171
181
  /* eslint-enable camelcase */
172
182
  ...( description?.length && {
@@ -371,4 +371,148 @@ describe( 'converting menu items to blocks', () => {
371
371
  const { innerBlocks: actual } = menuItemsToBlocks( [] );
372
372
  expect( actual ).toEqual( [] );
373
373
  } );
374
+
375
+ it( 'adds entity bindings for non-custom menu items', () => {
376
+ const { innerBlocks: actual } = menuItemsToBlocks( [
377
+ {
378
+ id: 1,
379
+ title: {
380
+ raw: 'Page Item',
381
+ rendered: 'Page Item',
382
+ },
383
+ url: 'http://localhost:8889/page-item/',
384
+ attr_title: '',
385
+ description: '',
386
+ type: 'post_type',
387
+ type_label: 'Page',
388
+ object: 'page',
389
+ object_id: 123,
390
+ parent: 0,
391
+ menu_order: 1,
392
+ target: '',
393
+ classes: [ '' ],
394
+ xfn: [ '' ],
395
+ },
396
+ {
397
+ id: 2,
398
+ title: {
399
+ raw: 'Category Item',
400
+ rendered: 'Category Item',
401
+ },
402
+ url: 'http://localhost:8889/category/category-item/',
403
+ attr_title: '',
404
+ description: '',
405
+ type: 'taxonomy',
406
+ type_label: 'Category',
407
+ object: 'category',
408
+ object_id: 456,
409
+ parent: 0,
410
+ menu_order: 2,
411
+ target: '',
412
+ classes: [ '' ],
413
+ xfn: [ '' ],
414
+ },
415
+ {
416
+ id: 3,
417
+ title: {
418
+ raw: 'Custom Item',
419
+ rendered: 'Custom Item',
420
+ },
421
+ url: 'http://localhost:8889/custom-link/',
422
+ attr_title: '',
423
+ description: '',
424
+ type: 'custom',
425
+ type_label: 'Custom Link',
426
+ object: 'custom',
427
+ parent: 0,
428
+ menu_order: 3,
429
+ target: '',
430
+ classes: [ '' ],
431
+ xfn: [ '' ],
432
+ },
433
+ ] );
434
+
435
+ expect( actual ).toEqual( [
436
+ expect.objectContaining( {
437
+ name: 'core/navigation-link',
438
+ attributes: expect.objectContaining( {
439
+ label: 'Page Item',
440
+ id: 123,
441
+ metadata: {
442
+ bindings: {
443
+ url: {
444
+ source: 'core/post-data',
445
+ args: {
446
+ field: 'link',
447
+ },
448
+ },
449
+ },
450
+ },
451
+ } ),
452
+ innerBlocks: [],
453
+ } ),
454
+ expect.objectContaining( {
455
+ name: 'core/navigation-link',
456
+ attributes: expect.objectContaining( {
457
+ label: 'Category Item',
458
+ id: 456,
459
+ metadata: {
460
+ bindings: {
461
+ url: {
462
+ source: 'core/term-data',
463
+ args: {
464
+ field: 'link',
465
+ },
466
+ },
467
+ },
468
+ },
469
+ } ),
470
+ innerBlocks: [],
471
+ } ),
472
+ expect.objectContaining( {
473
+ name: 'core/navigation-link',
474
+ attributes: expect.objectContaining( {
475
+ label: 'Custom Item',
476
+ // Custom items should NOT have id, metadata, or bindings
477
+ } ),
478
+ innerBlocks: [],
479
+ } ),
480
+ ] );
481
+
482
+ // Verify custom item does NOT have bindings
483
+ expect( actual[ 2 ].attributes ).not.toHaveProperty( 'id' );
484
+ expect( actual[ 2 ].attributes ).not.toHaveProperty( 'metadata' );
485
+ } );
486
+
487
+ it( 'does not add bindings for invalid kinds even when object_id is present', () => {
488
+ const { innerBlocks: actual } = menuItemsToBlocks( [
489
+ {
490
+ id: 10,
491
+ title: {
492
+ raw: 'Invalid Kind Item',
493
+ rendered: 'Invalid Kind Item',
494
+ },
495
+ url: 'http://localhost:8889/invalid-kind-item/',
496
+ attr_title: '',
497
+ description: '',
498
+ type: 'invalid', // becomes inferred kind 'invalid'
499
+ type_label: 'Invalid',
500
+ object: 'page',
501
+ object_id: 999,
502
+ parent: 0,
503
+ menu_order: 1,
504
+ target: '',
505
+ classes: [ '' ],
506
+ xfn: [ '' ],
507
+ },
508
+ ] );
509
+
510
+ expect( actual ).toHaveLength( 1 );
511
+ expect( actual[ 0 ].name ).toBe( 'core/navigation-link' );
512
+ // Should not set id or metadata when kind is not supported
513
+ expect( actual[ 0 ].attributes ).not.toHaveProperty( 'id' );
514
+ expect( actual[ 0 ].attributes ).not.toHaveProperty( 'metadata' );
515
+ // Label should still be set correctly
516
+ expect( actual[ 0 ].attributes.label ).toBe( 'Invalid Kind Item' );
517
+ } );
374
518
  } );
@@ -144,7 +144,7 @@ const { state, actions } = store(
144
144
  }
145
145
  }
146
146
  } ),
147
- handleMenuFocusout( event ) {
147
+ handleMenuFocusout: withSyncEvent( ( event ) => {
148
148
  const { modal, type } = getContext();
149
149
  // If focus is outside modal, and in the document, close menu
150
150
  // event.target === The element losing focus
@@ -162,7 +162,7 @@ const { state, actions } = store(
162
162
  actions.closeMenu( 'click' );
163
163
  actions.closeMenu( 'focus' );
164
164
  }
165
- },
165
+ } ),
166
166
 
167
167
  openMenu( menuOpenedOn = 'click' ) {
168
168
  const { type } = getContext();
@@ -558,7 +558,7 @@ export default function NavigationLinkEdit( {
558
558
  if ( isEntityLink ) {
559
559
  createBinding( updatedAttributes );
560
560
  } else {
561
- clearBinding();
561
+ clearBinding( updatedAttributes );
562
562
  }
563
563
  } }
564
564
  />
@@ -110,7 +110,7 @@
110
110
  .link-control-transform__subheading {
111
111
  font-size: 11px;
112
112
  text-transform: uppercase;
113
- font-weight: $font-weight-medium;
113
+ font-weight: 500;
114
114
  color: $gray-900;
115
115
  margin-bottom: 1.5em;
116
116
  }
@@ -11,7 +11,7 @@ import {
11
11
  TextareaControl,
12
12
  } from '@wordpress/components';
13
13
  import { __, sprintf } from '@wordpress/i18n';
14
- import { useRef, useEffect, useState } from '@wordpress/element';
14
+ import { useRef } 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,20 +72,9 @@ 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 );
77
75
  const inputId = useInstanceId( Controls, 'link-input' );
78
76
  const helpTextId = `${ inputId }__help`;
79
77
 
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
-
89
78
  // Use the entity binding hook internally
90
79
  const { hasUrlBinding, clearBinding } = useEntityBinding( {
91
80
  clientId,
@@ -95,7 +84,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
95
84
  // Get direct store dispatch to bypass setBoundAttributes wrapper
96
85
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
97
86
 
98
- const unsyncBoundLink = () => {
87
+ const editBoundLink = () => {
99
88
  // Clear the binding first
100
89
  clearBinding();
101
90
 
@@ -104,23 +93,9 @@ export function Controls( { attributes, setAttributes, clientId } ) {
104
93
  // setAttributes is actually setBoundAttributes, a wrapper function that
105
94
  // processes attributes through the binding system.
106
95
  // See: packages/block-editor/src/components/block-edit/edit.js
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
- } );
96
+ updateBlockAttributes( clientId, { url: '', id: undefined } );
111
97
  };
112
98
 
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
-
124
99
  return (
125
100
  <ToolsPanel
126
101
  label={ __( 'Settings' ) }
@@ -160,25 +135,22 @@ export function Controls( { attributes, setAttributes, clientId } ) {
160
135
  isShownByDefault
161
136
  >
162
137
  <InputControl
163
- ref={ urlInputRef }
164
138
  __nextHasNoMarginBottom
165
139
  __next40pxDefaultSize
166
140
  id={ inputId }
167
141
  label={ __( 'Link' ) }
168
- value={ inputValue ? safeDecodeURI( inputValue ) : '' }
169
- autoComplete="off"
170
- type="url"
171
- disabled={ hasUrlBinding }
172
- onChange={ ( newValue ) => {
142
+ value={ url ? safeDecodeURI( url ) : '' }
143
+ onChange={ ( urlValue ) => {
173
144
  if ( hasUrlBinding ) {
174
- return;
145
+ return; // Prevent editing when URL is bound
175
146
  }
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 );
147
+ setAttributes( {
148
+ url: encodeURI( safeDecodeURI( urlValue ) ),
149
+ } );
181
150
  } }
151
+ autoComplete="off"
152
+ type="url"
153
+ disabled={ hasUrlBinding }
182
154
  onFocus={ () => {
183
155
  if ( hasUrlBinding ) {
184
156
  return;
@@ -189,19 +161,12 @@ export function Controls( { attributes, setAttributes, clientId } ) {
189
161
  if ( hasUrlBinding ) {
190
162
  return;
191
163
  }
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
-
200
164
  // Defer the updateAttributes call to ensure entity connection isn't severed by accident.
201
- updateAttributes( { url: finalValue }, setAttributes, {
202
- ...attributes,
203
- url: lastURLRef.current,
204
- } );
165
+ updateAttributes(
166
+ { url: ! url ? lastURLRef.current : url },
167
+ setAttributes,
168
+ { ...attributes, url: lastURLRef.current }
169
+ );
205
170
  } }
206
171
  help={
207
172
  hasUrlBinding && (
@@ -215,12 +180,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
215
180
  hasUrlBinding && (
216
181
  <Button
217
182
  icon={ unlinkIcon }
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
- } }
183
+ onClick={ editBoundLink }
224
184
  aria-describedby={ helpTextId }
225
185
  showTooltip
226
186
  label={ __( 'Unsync and edit' ) }
@@ -6,7 +6,6 @@
6
6
  * External dependencies
7
7
  */
8
8
  import { render, screen, fireEvent } from '@testing-library/react';
9
- import userEvent from '@testing-library/user-event';
10
9
 
11
10
  /**
12
11
  * Internal dependencies
@@ -96,22 +95,18 @@ describe( 'Controls', () => {
96
95
  expect( urlInput.value ).toBe( 'https://example.com/test page' );
97
96
  } );
98
97
 
99
- it( 'calls updateAttributes with new URL on blur', async () => {
100
- const user = userEvent.setup();
98
+ it( 'encodes URL values when changed', () => {
101
99
  render( <Controls { ...defaultProps } /> );
102
100
 
103
101
  const urlInput = screen.getByLabelText( 'Link' );
104
102
 
105
- await user.click( urlInput );
106
- await user.clear( urlInput );
107
- await user.type( urlInput, 'https://example.com/test page' );
108
- await user.tab();
103
+ fireEvent.change( urlInput, {
104
+ target: { value: 'https://example.com/test page' },
105
+ } );
109
106
 
110
- expect( mockUpdateAttributes ).toHaveBeenCalledWith(
111
- { url: 'https://example.com/test page' },
112
- defaultProps.setAttributes,
113
- { ...defaultProps.attributes, url: 'https://example.com' }
114
- );
107
+ expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
108
+ url: 'https://example.com/test%20page',
109
+ } );
115
110
  } );
116
111
 
117
112
  it( 'calls updateAttributes on URL blur', () => {
@@ -148,11 +143,11 @@ describe( 'Controls', () => {
148
143
  target: { value: 'https://new.com' },
149
144
  } );
150
145
 
151
- // Blur should call updateAttributes with the new URL value from the input
146
+ // Blur should call updateAttributes with the current URL (since url exists)
152
147
  fireEvent.blur( urlInput );
153
148
 
154
149
  expect( mockUpdateAttributes ).toHaveBeenCalledWith(
155
- { url: 'https://new.com' }, // New URL from input value
150
+ { url: 'https://different.com' }, // Current URL from attributes (not input value)
156
151
  defaultProps.setAttributes,
157
152
  {
158
153
  ...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 ] );
72
+ }, [ updateBlockBindings, hasUrlBinding, metadata, id ] );
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();
450
+ clearBinding( updatedAttributes );
451
451
  }
452
452
  } }
453
453
  />
@@ -4,7 +4,7 @@
4
4
  "name": "core/post-date",
5
5
  "title": "Date",
6
6
  "category": "theme",
7
- "description": "Display the publish date for an entry such as a post or page.",
7
+ "description": "Display a custom date.",
8
8
  "textdomain": "default",
9
9
  "attributes": {
10
10
  "datetime": {
@@ -35,6 +35,7 @@ import { __, _x, sprintf } from '@wordpress/i18n';
35
35
  import { pencil } from '@wordpress/icons';
36
36
  import { DOWN } from '@wordpress/keycodes';
37
37
  import { useSelect, useDispatch } from '@wordpress/data';
38
+ import { store as blocksStore } from '@wordpress/blocks';
38
39
 
39
40
  /**
40
41
  * Internal dependencies
@@ -42,14 +43,12 @@ import { useSelect, useDispatch } from '@wordpress/data';
42
43
  import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
43
44
 
44
45
  export default function PostDateEdit( {
45
- attributes: { datetime, textAlign, format, isLink, metadata },
46
+ attributes,
46
47
  context: { postType: postTypeSlug, queryId },
47
48
  setAttributes,
49
+ name,
48
50
  } ) {
49
- const displayType =
50
- metadata?.bindings?.datetime?.source === 'core/post-data' &&
51
- metadata?.bindings?.datetime?.args?.field;
52
-
51
+ const { datetime, textAlign, format, isLink } = attributes;
53
52
  const blockProps = useBlockProps( {
54
53
  className: clsx( {
55
54
  [ `has-text-align-${ textAlign }` ]: textAlign,
@@ -90,7 +89,6 @@ export default function PostDateEdit( {
90
89
  ( select ) => {
91
90
  const { getPostType, getEntityRecord } = select( coreStore );
92
91
  const siteSettings = getEntityRecord( 'root', 'site' );
93
-
94
92
  return {
95
93
  siteFormat: siteSettings?.date_format,
96
94
  siteTimeFormat: siteSettings?.time_format,
@@ -99,6 +97,12 @@ export default function PostDateEdit( {
99
97
  },
100
98
  [ postTypeSlug ]
101
99
  );
100
+ const activeBlockVariationName = useSelect(
101
+ ( select ) =>
102
+ select( blocksStore ).getActiveBlockVariation( name, attributes )
103
+ ?.name,
104
+ [ name, attributes ]
105
+ );
102
106
 
103
107
  const blockEditingMode = useBlockEditingMode();
104
108
 
@@ -120,7 +124,6 @@ export default function PostDateEdit( {
120
124
  </a>
121
125
  );
122
126
  }
123
-
124
127
  return (
125
128
  <>
126
129
  { ( blockEditingMode === 'default' ||
@@ -133,15 +136,17 @@ export default function PostDateEdit( {
133
136
  } }
134
137
  />
135
138
 
136
- { displayType !== 'modified' &&
137
- ! isDescendentOfQueryLoop && (
139
+ { activeBlockVariationName !== 'post-date-modified' &&
140
+ ( ! isDescendentOfQueryLoop ||
141
+ ! activeBlockVariationName ) && (
138
142
  <ToolbarGroup>
139
143
  <Dropdown
140
144
  popoverProps={ popoverProps }
141
145
  renderContent={ ( { onClose } ) => (
142
146
  <PublishDateTimePicker
143
147
  title={
144
- displayType === 'date'
148
+ activeBlockVariationName ===
149
+ 'post-date'
145
150
  ? __( 'Publish Date' )
146
151
  : __( 'Date' )
147
152
  }
@@ -2,7 +2,6 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { __ } from '@wordpress/i18n';
5
- import { postDate } from '@wordpress/icons';
6
5
 
7
6
  const variations = [
8
7
  {
@@ -19,13 +18,12 @@ const variations = [
19
18
  },
20
19
  },
21
20
  },
22
- scope: [ 'block', 'inserter', 'transform' ],
21
+ scope: [ 'inserter', 'transform' ],
23
22
  isActive: ( blockAttributes ) =>
24
23
  blockAttributes?.metadata?.bindings?.datetime?.source ===
25
24
  'core/post-data' &&
26
25
  blockAttributes?.metadata?.bindings?.datetime?.args?.field ===
27
26
  'date',
28
- icon: postDate,
29
27
  },
30
28
  {
31
29
  name: 'post-date-modified',
@@ -42,13 +40,12 @@ const variations = [
42
40
  },
43
41
  className: 'wp-block-post-date__modified-date',
44
42
  },
45
- scope: [ 'block', 'inserter', 'transform' ],
43
+ scope: [ 'inserter', 'transform' ],
46
44
  isActive: ( blockAttributes ) =>
47
45
  blockAttributes?.metadata?.bindings?.datetime?.source ===
48
46
  'core/post-data' &&
49
47
  blockAttributes?.metadata?.bindings?.datetime?.args?.field ===
50
48
  'modified',
51
- icon: postDate,
52
49
  },
53
50
  ];
54
51
 
@@ -23,7 +23,19 @@ import { list, grid } from '@wordpress/icons';
23
23
 
24
24
  const TEMPLATE = [
25
25
  [ 'core/post-title' ],
26
- [ 'core/post-date' ],
26
+ [
27
+ 'core/post-date',
28
+ {
29
+ metadata: {
30
+ bindings: {
31
+ datetime: {
32
+ source: 'core/post-data',
33
+ args: { field: 'date' },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ ],
27
39
  [ 'core/post-excerpt' ],
28
40
  ];
29
41
 
@@ -51,6 +51,16 @@ export const settings = {
51
51
  },
52
52
  {
53
53
  name: 'core/post-date',
54
+ attributes: {
55
+ metadata: {
56
+ bindings: {
57
+ datetime: {
58
+ source: 'core/post-data',
59
+ args: { field: 'date' },
60
+ },
61
+ },
62
+ },
63
+ },
54
64
  },
55
65
  {
56
66
  name: 'core/post-excerpt',
@@ -13,6 +13,20 @@ import {
13
13
  imageDateTitle,
14
14
  } from './icons';
15
15
 
16
+ const postDate = [
17
+ 'core/post-date',
18
+ {
19
+ metadata: {
20
+ bindings: {
21
+ datetime: {
22
+ source: 'core/post-data',
23
+ args: { field: 'date' },
24
+ },
25
+ },
26
+ },
27
+ },
28
+ ];
29
+
16
30
  const variations = [
17
31
  {
18
32
  name: 'title-date',
@@ -20,11 +34,7 @@ const variations = [
20
34
  icon: titleDate,
21
35
  attributes: {},
22
36
  innerBlocks: [
23
- [
24
- 'core/post-template',
25
- {},
26
- [ [ 'core/post-title' ], [ 'core/post-date' ] ],
27
- ],
37
+ [ 'core/post-template', {}, [ [ 'core/post-title' ], postDate ] ],
28
38
  [ 'core/query-pagination' ],
29
39
  [ 'core/query-no-results' ],
30
40
  ],
@@ -55,11 +65,7 @@ const variations = [
55
65
  [
56
66
  'core/post-template',
57
67
  {},
58
- [
59
- [ 'core/post-title' ],
60
- [ 'core/post-date' ],
61
- [ 'core/post-excerpt' ],
62
- ],
68
+ [ [ 'core/post-title' ], postDate, [ 'core/post-excerpt' ] ],
63
69
  ],
64
70
  [ 'core/query-pagination' ],
65
71
  [ 'core/query-no-results' ],
@@ -77,7 +83,7 @@ const variations = [
77
83
  {},
78
84
  [
79
85
  [ 'core/post-featured-image' ],
80
- [ 'core/post-date' ],
86
+ postDate,
81
87
  [ 'core/post-title' ],
82
88
  ],
83
89
  ],
@@ -77,7 +77,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
77
77
  ) ) {
78
78
  $p->set_attribute( 'data-wp-key', 'query-pagination-next' );
79
79
  $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' );
80
- $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' );
80
+ $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' );
81
81
  $p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' );
82
82
  $content = $p->get_updated_html();
83
83
  }
@@ -70,7 +70,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
70
70
  ) ) {
71
71
  $p->set_attribute( 'data-wp-key', 'query-pagination-previous' );
72
72
  $p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' );
73
- $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' );
73
+ $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' );
74
74
  $p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' );
75
75
  $content = $p->get_updated_html();
76
76
  }